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);
61 __webpack_require__(18);
66 /***/ function(module, exports, __webpack_require__) {
68 __webpack_require__(2);
69 module.exports = angular;
74 /***/ function(module, exports) {
77 * @license AngularJS v1.4.8
78 * (c) 2010-2015 Google, Inc. http://angularjs.org
81 (function(window, document, undefined) {'use strict';
86 * This object provides a utility for producing rich Error messages within
87 * Angular. It can be called as follows:
89 * var exampleMinErr = minErr('example');
90 * throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
92 * The above creates an instance of minErr in the example namespace. The
93 * resulting error will have a namespaced error code of example.one. The
94 * resulting error will replace {0} with the value of foo, and {1} with the
95 * value of bar. The object is not restricted in the number of arguments it can
98 * If fewer arguments are specified than necessary for interpolation, the extra
99 * interpolation markers will be preserved in the final string.
101 * Since data will be parsed statically during a build step, some restrictions
102 * are applied with respect to how minErr instances are created and called.
103 * Instances should have names of the form namespaceMinErr for a minErr created
104 * using minErr('namespace') . Error codes, namespaces and template strings
105 * should all be static strings, not variables or general expressions.
107 * @param {string} module The namespace to use for the new minErr instance.
108 * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning
109 * error from returned function, for cases when a particular type of error is useful.
110 * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance
113 function minErr(module, ErrorConstructor) {
114 ErrorConstructor = ErrorConstructor || Error;
116 var SKIP_INDEXES = 2;
118 var templateArgs = arguments,
119 code = templateArgs[0],
120 message = '[' + (module ? module + ':' : '') + code + '] ',
121 template = templateArgs[1],
124 message += template.replace(/\{\d+\}/g, function(match) {
125 var index = +match.slice(1, -1),
126 shiftedIndex = index + SKIP_INDEXES;
128 if (shiftedIndex < templateArgs.length) {
129 return toDebugString(templateArgs[shiftedIndex]);
135 message += '\nhttp://errors.angularjs.org/1.4.8/' +
136 (module ? module + '/' : '') + code;
138 for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
139 message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' +
140 encodeURIComponent(toDebugString(templateArgs[i]));
143 return new ErrorConstructor(message);
147 /* We need to tell jshint what variables are being exported */
148 /* global angular: true,
159 REGEX_STRING_REGEXP: true,
160 VALIDITY_STATE_PROPERTY: true,
164 manualLowercase: true,
165 manualUppercase: true,
198 escapeForRegexp: true,
211 toJsonReplacer: true,
214 convertTimezoneToLocal: true,
215 timezoneToOffset: true,
217 tryDecodeURIComponent: true,
220 encodeUriSegment: true,
221 encodeUriQuery: true,
224 getTestability: true,
229 assertNotHasOwnProperty: true,
232 hasOwnProperty: true,
235 NODE_TYPE_ELEMENT: true,
236 NODE_TYPE_ATTRIBUTE: true,
237 NODE_TYPE_TEXT: true,
238 NODE_TYPE_COMMENT: true,
239 NODE_TYPE_DOCUMENT: true,
240 NODE_TYPE_DOCUMENT_FRAGMENT: true,
243 ////////////////////////////////////
252 * The ng module is loaded by default when an AngularJS application is started. The module itself
253 * contains the essential components for an AngularJS application to function. The table below
254 * lists a high level breakdown of each of the services/factories, filters, directives and testing
255 * components available within this core module.
257 * <div doc-module-components="ng"></div>
260 var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/;
262 // The name of a form control's ValidityState property.
263 // This is used so that it's possible for internal tests to create mock ValidityStates.
264 var VALIDITY_STATE_PROPERTY = 'validity';
268 * @name angular.lowercase
272 * @description Converts the specified string to lowercase.
273 * @param {string} string String to be converted to lowercase.
274 * @returns {string} Lowercased string.
276 var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;};
277 var hasOwnProperty = Object.prototype.hasOwnProperty;
281 * @name angular.uppercase
285 * @description Converts the specified string to uppercase.
286 * @param {string} string String to be converted to uppercase.
287 * @returns {string} Uppercased string.
289 var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;};
292 var manualLowercase = function(s) {
293 /* jshint bitwise: false */
295 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);})
298 var manualUppercase = function(s) {
299 /* jshint bitwise: false */
301 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);})
306 // String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
307 // locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
308 // with correct but slower alternatives.
309 if ('i' !== 'I'.toLowerCase()) {
310 lowercase = manualLowercase;
311 uppercase = manualUppercase;
316 msie, // holds major version number for IE, or NaN if UA is not IE.
317 jqLite, // delay binding since jQuery could be loaded after us.
318 jQuery, // delay binding
322 toString = Object.prototype.toString,
323 getPrototypeOf = Object.getPrototypeOf,
324 ngMinErr = minErr('ng'),
327 angular = window.angular || (window.angular = {}),
332 * documentMode is an IE-only property
333 * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
335 msie = document.documentMode;
341 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments,
344 function isArrayLike(obj) {
346 // `null`, `undefined` and `window` are not array-like
347 if (obj == null || isWindow(obj)) return false;
349 // arrays, strings and jQuery/jqLite objects are array like
350 // * jqLite is either the jQuery or jqLite constructor function
351 // * we have to check the existance of jqLite first as this method is called
352 // via the forEach method when constructing the jqLite object in the first place
353 if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true;
355 // Support: iOS 8.2 (not reproducible in simulator)
356 // "length" in obj used to prevent JIT error (gh-11508)
357 var length = "length" in Object(obj) && obj.length;
359 // NodeList objects (with `item` method) and
360 // other objects with suitable length characteristics are array-like
361 return isNumber(length) &&
362 (length >= 0 && (length - 1) in obj || typeof obj.item == 'function');
367 * @name angular.forEach
372 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an
373 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value`
374 * is the value of an object property or an array element, `key` is the object property key or
375 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional.
377 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters
378 * using the `hasOwnProperty` method.
381 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),
382 * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
383 * return the value provided.
386 var values = {name: 'misko', gender: 'male'};
388 angular.forEach(values, function(value, key) {
389 this.push(key + ': ' + value);
391 expect(log).toEqual(['name: misko', 'gender: male']);
394 * @param {Object|Array} obj Object to iterate over.
395 * @param {Function} iterator Iterator function.
396 * @param {Object=} context Object to become context (`this`) for the iterator function.
397 * @returns {Object|Array} Reference to `obj`.
400 function forEach(obj, iterator, context) {
403 if (isFunction(obj)) {
405 // Need to check if hasOwnProperty exists,
406 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
407 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {
408 iterator.call(context, obj[key], key, obj);
411 } else if (isArray(obj) || isArrayLike(obj)) {
412 var isPrimitive = typeof obj !== 'object';
413 for (key = 0, length = obj.length; key < length; key++) {
414 if (isPrimitive || key in obj) {
415 iterator.call(context, obj[key], key, obj);
418 } else if (obj.forEach && obj.forEach !== forEach) {
419 obj.forEach(iterator, context, obj);
420 } else if (isBlankObject(obj)) {
421 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
423 iterator.call(context, obj[key], key, obj);
425 } else if (typeof obj.hasOwnProperty === 'function') {
426 // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed
428 if (obj.hasOwnProperty(key)) {
429 iterator.call(context, obj[key], key, obj);
433 // Slow path for objects which do not have a method `hasOwnProperty`
435 if (hasOwnProperty.call(obj, key)) {
436 iterator.call(context, obj[key], key, obj);
444 function forEachSorted(obj, iterator, context) {
445 var keys = Object.keys(obj).sort();
446 for (var i = 0; i < keys.length; i++) {
447 iterator.call(context, obj[keys[i]], keys[i]);
454 * when using forEach the params are value, key, but it is often useful to have key, value.
455 * @param {function(string, *)} iteratorFn
456 * @returns {function(*, string)}
458 function reverseParams(iteratorFn) {
459 return function(value, key) { iteratorFn(key, value); };
463 * A consistent way of creating unique IDs in angular.
465 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before
466 * we hit number precision issues in JavaScript.
468 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M
470 * @returns {number} an unique alpha-numeric string
478 * Set or clear the hashkey for an object.
480 * @param h the hashkey (!truthy to delete the hashkey)
482 function setHashKey(obj, h) {
486 delete obj.$$hashKey;
491 function baseExtend(dst, objs, deep) {
492 var h = dst.$$hashKey;
494 for (var i = 0, ii = objs.length; i < ii; ++i) {
496 if (!isObject(obj) && !isFunction(obj)) continue;
497 var keys = Object.keys(obj);
498 for (var j = 0, jj = keys.length; j < jj; j++) {
502 if (deep && isObject(src)) {
504 dst[key] = new Date(src.valueOf());
505 } else if (isRegExp(src)) {
506 dst[key] = new RegExp(src);
507 } else if (src.nodeName) {
508 dst[key] = src.cloneNode(true);
509 } else if (isElement(src)) {
510 dst[key] = src.clone();
512 if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
513 baseExtend(dst[key], [src], true);
527 * @name angular.extend
532 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
533 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
534 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
536 * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use
537 * {@link angular.merge} for this.
539 * @param {Object} dst Destination object.
540 * @param {...Object} src Source object(s).
541 * @returns {Object} Reference to `dst`.
543 function extend(dst) {
544 return baseExtend(dst, slice.call(arguments, 1), false);
550 * @name angular.merge
555 * Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
556 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
557 * by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`.
559 * Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source
560 * objects, performing a deep copy.
562 * @param {Object} dst Destination object.
563 * @param {...Object} src Source object(s).
564 * @returns {Object} Reference to `dst`.
566 function merge(dst) {
567 return baseExtend(dst, slice.call(arguments, 1), true);
572 function toInt(str) {
573 return parseInt(str, 10);
577 function inherit(parent, extra) {
578 return extend(Object.create(parent), extra);
588 * A function that performs no operations. This function can be useful when writing code in the
591 function foo(callback) {
592 var result = calculateResult();
593 (callback || angular.noop)(result);
603 * @name angular.identity
608 * A function that returns its first argument. This function is useful when writing code in the
612 function transformer(transformationFn, value) {
613 return (transformationFn || angular.identity)(value);
616 * @param {*} value to be returned.
617 * @returns {*} the value passed in.
619 function identity($) {return $;}
620 identity.$inject = [];
623 function valueFn(value) {return function() {return value;};}
625 function hasCustomToString(obj) {
626 return isFunction(obj.toString) && obj.toString !== toString;
632 * @name angular.isUndefined
637 * Determines if a reference is undefined.
639 * @param {*} value Reference to check.
640 * @returns {boolean} True if `value` is undefined.
642 function isUndefined(value) {return typeof value === 'undefined';}
647 * @name angular.isDefined
652 * Determines if a reference is defined.
654 * @param {*} value Reference to check.
655 * @returns {boolean} True if `value` is defined.
657 function isDefined(value) {return typeof value !== 'undefined';}
662 * @name angular.isObject
667 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
668 * considered to be objects. Note that JavaScript arrays are objects.
670 * @param {*} value Reference to check.
671 * @returns {boolean} True if `value` is an `Object` but not `null`.
673 function isObject(value) {
674 // http://jsperf.com/isobject4
675 return value !== null && typeof value === 'object';
680 * Determine if a value is an object with a null prototype
682 * @returns {boolean} True if `value` is an `Object` with a null prototype
684 function isBlankObject(value) {
685 return value !== null && typeof value === 'object' && !getPrototypeOf(value);
691 * @name angular.isString
696 * Determines if a reference is a `String`.
698 * @param {*} value Reference to check.
699 * @returns {boolean} True if `value` is a `String`.
701 function isString(value) {return typeof value === 'string';}
706 * @name angular.isNumber
711 * Determines if a reference is a `Number`.
713 * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`.
715 * If you wish to exclude these then you can use the native
716 * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite)
719 * @param {*} value Reference to check.
720 * @returns {boolean} True if `value` is a `Number`.
722 function isNumber(value) {return typeof value === 'number';}
727 * @name angular.isDate
732 * Determines if a value is a date.
734 * @param {*} value Reference to check.
735 * @returns {boolean} True if `value` is a `Date`.
737 function isDate(value) {
738 return toString.call(value) === '[object Date]';
744 * @name angular.isArray
749 * Determines if a reference is an `Array`.
751 * @param {*} value Reference to check.
752 * @returns {boolean} True if `value` is an `Array`.
754 var isArray = Array.isArray;
758 * @name angular.isFunction
763 * Determines if a reference is a `Function`.
765 * @param {*} value Reference to check.
766 * @returns {boolean} True if `value` is a `Function`.
768 function isFunction(value) {return typeof value === 'function';}
772 * Determines if a value is a regular expression object.
775 * @param {*} value Reference to check.
776 * @returns {boolean} True if `value` is a `RegExp`.
778 function isRegExp(value) {
779 return toString.call(value) === '[object RegExp]';
784 * Checks if `obj` is a window object.
787 * @param {*} obj Object to check
788 * @returns {boolean} True if `obj` is a window obj.
790 function isWindow(obj) {
791 return obj && obj.window === obj;
795 function isScope(obj) {
796 return obj && obj.$evalAsync && obj.$watch;
800 function isFile(obj) {
801 return toString.call(obj) === '[object File]';
805 function isFormData(obj) {
806 return toString.call(obj) === '[object FormData]';
810 function isBlob(obj) {
811 return toString.call(obj) === '[object Blob]';
815 function isBoolean(value) {
816 return typeof value === 'boolean';
820 function isPromiseLike(obj) {
821 return obj && isFunction(obj.then);
825 var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/;
826 function isTypedArray(value) {
827 return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value));
831 var trim = function(value) {
832 return isString(value) ? value.trim() : value;
836 // http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021
837 // Prereq: s is a string.
838 var escapeForRegexp = function(s) {
839 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
840 replace(/\x08/g, '\\x08');
846 * @name angular.isElement
851 * Determines if a reference is a DOM element (or wrapped jQuery element).
853 * @param {*} value Reference to check.
854 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
856 function isElement(node) {
858 (node.nodeName // we are a direct element
859 || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API
863 * @param str 'key1,key2,...'
864 * @returns {object} in the form of {key1:true, key2:true, ...}
866 function makeMap(str) {
867 var obj = {}, items = str.split(","), i;
868 for (i = 0; i < items.length; i++) {
869 obj[items[i]] = true;
875 function nodeName_(element) {
876 return lowercase(element.nodeName || (element[0] && element[0].nodeName));
879 function includes(array, obj) {
880 return Array.prototype.indexOf.call(array, obj) != -1;
883 function arrayRemove(array, value) {
884 var index = array.indexOf(value);
886 array.splice(index, 1);
898 * Creates a deep copy of `source`, which should be an object or an array.
900 * * If no destination is supplied, a copy of the object or array is created.
901 * * If a destination is provided, all of its elements (for arrays) or properties (for objects)
902 * are deleted and then all elements/properties from the source are copied to it.
903 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
904 * * If `source` is identical to 'destination' an exception will be thrown.
906 * @param {*} source The source that will be used to make a copy.
907 * Can be any type, including primitives, `null`, and `undefined`.
908 * @param {(Object|Array)=} destination Destination into which the source is copied. If
909 * provided, must be of the same type as `source`.
910 * @returns {*} The copy or updated `destination`, if `destination` was specified.
913 <example module="copyExample">
914 <file name="index.html">
915 <div ng-controller="ExampleController">
916 <form novalidate class="simple-form">
917 Name: <input type="text" ng-model="user.name" /><br />
918 E-mail: <input type="email" ng-model="user.email" /><br />
919 Gender: <input type="radio" ng-model="user.gender" value="male" />male
920 <input type="radio" ng-model="user.gender" value="female" />female<br />
921 <button ng-click="reset()">RESET</button>
922 <button ng-click="update(user)">SAVE</button>
924 <pre>form = {{user | json}}</pre>
925 <pre>master = {{master | json}}</pre>
929 angular.module('copyExample', [])
930 .controller('ExampleController', ['$scope', function($scope) {
933 $scope.update = function(user) {
934 // Example with 1 argument
935 $scope.master= angular.copy(user);
938 $scope.reset = function() {
939 // Example with 2 arguments
940 angular.copy($scope.master, $scope.user);
949 function copy(source, destination) {
950 var stackSource = [];
954 if (isTypedArray(destination)) {
955 throw ngMinErr('cpta', "Can't copy! TypedArray destination cannot be mutated.");
957 if (source === destination) {
958 throw ngMinErr('cpi', "Can't copy! Source and destination are identical.");
961 // Empty the destination object
962 if (isArray(destination)) {
963 destination.length = 0;
965 forEach(destination, function(value, key) {
966 if (key !== '$$hashKey') {
967 delete destination[key];
972 stackSource.push(source);
973 stackDest.push(destination);
974 return copyRecurse(source, destination);
977 return copyElement(source);
979 function copyRecurse(source, destination) {
980 var h = destination.$$hashKey;
982 if (isArray(source)) {
983 for (var i = 0, ii = source.length; i < ii; i++) {
984 destination.push(copyElement(source[i]));
986 } else if (isBlankObject(source)) {
987 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
988 for (key in source) {
989 destination[key] = copyElement(source[key]);
991 } else if (source && typeof source.hasOwnProperty === 'function') {
992 // Slow path, which must rely on hasOwnProperty
993 for (key in source) {
994 if (source.hasOwnProperty(key)) {
995 destination[key] = copyElement(source[key]);
999 // Slowest path --- hasOwnProperty can't be called as a method
1000 for (key in source) {
1001 if (hasOwnProperty.call(source, key)) {
1002 destination[key] = copyElement(source[key]);
1006 setHashKey(destination, h);
1010 function copyElement(source) {
1012 if (!isObject(source)) {
1016 // Already copied values
1017 var index = stackSource.indexOf(source);
1019 return stackDest[index];
1022 if (isWindow(source) || isScope(source)) {
1023 throw ngMinErr('cpws',
1024 "Can't copy! Making copies of Window or Scope instances is not supported.");
1027 var needsRecurse = false;
1030 if (isArray(source)) {
1032 needsRecurse = true;
1033 } else if (isTypedArray(source)) {
1034 destination = new source.constructor(source);
1035 } else if (isDate(source)) {
1036 destination = new Date(source.getTime());
1037 } else if (isRegExp(source)) {
1038 destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
1039 destination.lastIndex = source.lastIndex;
1040 } else if (isFunction(source.cloneNode)) {
1041 destination = source.cloneNode(true);
1043 destination = Object.create(getPrototypeOf(source));
1044 needsRecurse = true;
1047 stackSource.push(source);
1048 stackDest.push(destination);
1051 ? copyRecurse(source, destination)
1057 * Creates a shallow copy of an object, an array or a primitive.
1059 * Assumes that there are no proto properties for objects.
1061 function shallowCopy(src, dst) {
1065 for (var i = 0, ii = src.length; i < ii; i++) {
1068 } else if (isObject(src)) {
1071 for (var key in src) {
1072 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
1073 dst[key] = src[key];
1084 * @name angular.equals
1089 * Determines if two objects or two values are equivalent. Supports value types, regular
1090 * expressions, arrays and objects.
1092 * Two objects or values are considered equivalent if at least one of the following is true:
1094 * * Both objects or values pass `===` comparison.
1095 * * Both objects or values are of the same type and all of their properties are equal by
1096 * comparing them with `angular.equals`.
1097 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal)
1098 * * Both values represent the same regular expression (In JavaScript,
1099 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual
1100 * representation matches).
1102 * During a property comparison, properties of `function` type and properties with names
1103 * that begin with `$` are ignored.
1105 * Scope and DOMWindow objects are being compared only by identify (`===`).
1107 * @param {*} o1 Object or value to compare.
1108 * @param {*} o2 Object or value to compare.
1109 * @returns {boolean} True if arguments are equal.
1111 function equals(o1, o2) {
1112 if (o1 === o2) return true;
1113 if (o1 === null || o2 === null) return false;
1114 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
1115 var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
1117 if (t1 == 'object') {
1119 if (!isArray(o2)) return false;
1120 if ((length = o1.length) == o2.length) {
1121 for (key = 0; key < length; key++) {
1122 if (!equals(o1[key], o2[key])) return false;
1126 } else if (isDate(o1)) {
1127 if (!isDate(o2)) return false;
1128 return equals(o1.getTime(), o2.getTime());
1129 } else if (isRegExp(o1)) {
1130 return isRegExp(o2) ? o1.toString() == o2.toString() : false;
1132 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
1133 isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
1134 keySet = createMap();
1136 if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
1137 if (!equals(o1[key], o2[key])) return false;
1141 if (!(key in keySet) &&
1142 key.charAt(0) !== '$' &&
1143 isDefined(o2[key]) &&
1144 !isFunction(o2[key])) return false;
1153 var csp = function() {
1154 if (!isDefined(csp.rules)) {
1157 var ngCspElement = (document.querySelector('[ng-csp]') ||
1158 document.querySelector('[data-ng-csp]'));
1161 var ngCspAttribute = ngCspElement.getAttribute('ng-csp') ||
1162 ngCspElement.getAttribute('data-ng-csp');
1164 noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1),
1165 noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1)
1169 noUnsafeEval: noUnsafeEval(),
1170 noInlineStyle: false
1177 function noUnsafeEval() {
1179 /* jshint -W031, -W054 */
1181 /* jshint +W031, +W054 */
1195 * @param {string=} ngJq the name of the library available under `window`
1196 * to be used for angular.element
1198 * Use this directive to force the angular.element library. This should be
1199 * used to force either jqLite by leaving ng-jq blank or setting the name of
1200 * the jquery variable under window (eg. jQuery).
1202 * Since angular looks for this directive when it is loaded (doesn't wait for the
1203 * DOMContentLoaded event), it must be placed on an element that comes before the script
1204 * which loads angular. Also, only the first instance of `ng-jq` will be used and all
1208 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag.
1217 * This example shows how to use a jQuery based library of a different name.
1218 * The library name must be available at the top most 'window'.
1221 <html ng-app ng-jq="jQueryLib">
1227 var jq = function() {
1228 if (isDefined(jq.name_)) return jq.name_;
1230 var i, ii = ngAttrPrefixes.length, prefix, name;
1231 for (i = 0; i < ii; ++i) {
1232 prefix = ngAttrPrefixes[i];
1233 if (el = document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) {
1234 name = el.getAttribute(prefix + 'jq');
1239 return (jq.name_ = name);
1242 function concat(array1, array2, index) {
1243 return array1.concat(slice.call(array2, index));
1246 function sliceArgs(args, startIndex) {
1247 return slice.call(args, startIndex || 0);
1254 * @name angular.bind
1259 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for
1260 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also
1261 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as
1262 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application).
1264 * @param {Object} self Context which `fn` should be evaluated in.
1265 * @param {function()} fn Function to be bound.
1266 * @param {...*} args Optional arguments to be prebound to the `fn` function call.
1267 * @returns {function()} Function that wraps the `fn` with all the specified bindings.
1270 function bind(self, fn) {
1271 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : [];
1272 if (isFunction(fn) && !(fn instanceof RegExp)) {
1273 return curryArgs.length
1275 return arguments.length
1276 ? fn.apply(self, concat(curryArgs, arguments, 0))
1277 : fn.apply(self, curryArgs);
1280 return arguments.length
1281 ? fn.apply(self, arguments)
1285 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be)
1291 function toJsonReplacer(key, value) {
1294 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') {
1296 } else if (isWindow(value)) {
1298 } else if (value && document === value) {
1300 } else if (isScope(value)) {
1310 * @name angular.toJson
1315 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be
1316 * stripped since angular uses this notation internally.
1318 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
1319 * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace.
1320 * If set to an integer, the JSON output will contain that many spaces per indentation.
1321 * @returns {string|undefined} JSON-ified string representing `obj`.
1323 function toJson(obj, pretty) {
1324 if (typeof obj === 'undefined') return undefined;
1325 if (!isNumber(pretty)) {
1326 pretty = pretty ? 2 : null;
1328 return JSON.stringify(obj, toJsonReplacer, pretty);
1334 * @name angular.fromJson
1339 * Deserializes a JSON string.
1341 * @param {string} json JSON string to deserialize.
1342 * @returns {Object|Array|string|number} Deserialized JSON string.
1344 function fromJson(json) {
1345 return isString(json)
1351 function timezoneToOffset(timezone, fallback) {
1352 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
1353 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
1357 function addDateMinutes(date, minutes) {
1358 date = new Date(date.getTime());
1359 date.setMinutes(date.getMinutes() + minutes);
1364 function convertTimezoneToLocal(date, timezone, reverse) {
1365 reverse = reverse ? -1 : 1;
1366 var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
1367 return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
1372 * @returns {string} Returns the string representation of the element.
1374 function startingTag(element) {
1375 element = jqLite(element).clone();
1377 // turns out IE does not let you set .html() on elements which
1378 // are not allowed to have children. So we just ignore it.
1381 var elemHtml = jqLite('<div>').append(element).html();
1383 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
1385 match(/^(<[^>]+>)/)[1].
1386 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
1388 return lowercase(elemHtml);
1394 /////////////////////////////////////////////////
1397 * Tries to decode the URI component without throwing an exception.
1400 * @param str value potential URI component to check.
1401 * @returns {boolean} True if `value` can be decoded
1402 * with the decodeURIComponent function.
1404 function tryDecodeURIComponent(value) {
1406 return decodeURIComponent(value);
1408 // Ignore any invalid uri component
1414 * Parses an escaped url query string into key-value pairs.
1415 * @returns {Object.<string,boolean|Array>}
1417 function parseKeyValue(/**string*/keyValue) {
1419 forEach((keyValue || "").split('&'), function(keyValue) {
1420 var splitPoint, key, val;
1422 key = keyValue = keyValue.replace(/\+/g,'%20');
1423 splitPoint = keyValue.indexOf('=');
1424 if (splitPoint !== -1) {
1425 key = keyValue.substring(0, splitPoint);
1426 val = keyValue.substring(splitPoint + 1);
1428 key = tryDecodeURIComponent(key);
1429 if (isDefined(key)) {
1430 val = isDefined(val) ? tryDecodeURIComponent(val) : true;
1431 if (!hasOwnProperty.call(obj, key)) {
1433 } else if (isArray(obj[key])) {
1436 obj[key] = [obj[key],val];
1444 function toKeyValue(obj) {
1446 forEach(obj, function(value, key) {
1447 if (isArray(value)) {
1448 forEach(value, function(arrayValue) {
1449 parts.push(encodeUriQuery(key, true) +
1450 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true)));
1453 parts.push(encodeUriQuery(key, true) +
1454 (value === true ? '' : '=' + encodeUriQuery(value, true)));
1457 return parts.length ? parts.join('&') : '';
1462 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
1463 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
1466 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1467 * pct-encoded = "%" HEXDIG HEXDIG
1468 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1469 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1470 * / "*" / "+" / "," / ";" / "="
1472 function encodeUriSegment(val) {
1473 return encodeUriQuery(val, true).
1474 replace(/%26/gi, '&').
1475 replace(/%3D/gi, '=').
1476 replace(/%2B/gi, '+');
1481 * This method is intended for encoding *key* or *value* parts of query component. We need a custom
1482 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
1483 * encoded per http://tools.ietf.org/html/rfc3986:
1484 * query = *( pchar / "/" / "?" )
1485 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1486 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1487 * pct-encoded = "%" HEXDIG HEXDIG
1488 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1489 * / "*" / "+" / "," / ";" / "="
1491 function encodeUriQuery(val, pctEncodeSpaces) {
1492 return encodeURIComponent(val).
1493 replace(/%40/gi, '@').
1494 replace(/%3A/gi, ':').
1495 replace(/%24/g, '$').
1496 replace(/%2C/gi, ',').
1497 replace(/%3B/gi, ';').
1498 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
1501 var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];
1503 function getNgAttribute(element, ngAttr) {
1504 var attr, i, ii = ngAttrPrefixes.length;
1505 for (i = 0; i < ii; ++i) {
1506 attr = ngAttrPrefixes[i] + ngAttr;
1507 if (isString(attr = element.getAttribute(attr))) {
1520 * @param {angular.Module} ngApp an optional application
1521 * {@link angular.module module} name to load.
1522 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be
1523 * created in "strict-di" mode. This means that the application will fail to invoke functions which
1524 * do not use explicit function annotation (and are thus unsuitable for minification), as described
1525 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in
1526 * tracking down the root of these bugs.
1530 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive
1531 * designates the **root element** of the application and is typically placed near the root element
1532 * of the page - e.g. on the `<body>` or `<html>` tags.
1534 * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp`
1535 * found in the document will be used to define the root element to auto-bootstrap as an
1536 * application. To run multiple applications in an HTML document you must manually bootstrap them using
1537 * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other.
1539 * You can specify an **AngularJS module** to be used as the root module for the application. This
1540 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It
1541 * should contain the application code needed or have dependencies on other modules that will
1542 * contain the code. See {@link angular.module} for more information.
1544 * In the example below if the `ngApp` directive were not placed on the `html` element then the
1545 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
1546 * would not be resolved to `3`.
1548 * `ngApp` is the easiest, and most common way to bootstrap an application.
1550 <example module="ngAppDemo">
1551 <file name="index.html">
1552 <div ng-controller="ngAppDemoController">
1553 I can add: {{a}} + {{b}} = {{ a+b }}
1556 <file name="script.js">
1557 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
1564 * Using `ngStrictDi`, you would see something like this:
1566 <example ng-app-included="true">
1567 <file name="index.html">
1568 <div ng-app="ngAppStrictDemo" ng-strict-di>
1569 <div ng-controller="GoodController1">
1570 I can add: {{a}} + {{b}} = {{ a+b }}
1572 <p>This renders because the controller does not fail to
1573 instantiate, by using explicit annotation style (see
1574 script.js for details)
1578 <div ng-controller="GoodController2">
1579 Name: <input ng-model="name"><br />
1582 <p>This renders because the controller does not fail to
1583 instantiate, by using explicit annotation style
1584 (see script.js for details)
1588 <div ng-controller="BadController">
1589 I can add: {{a}} + {{b}} = {{ a+b }}
1591 <p>The controller could not be instantiated, due to relying
1592 on automatic function annotations (which are disabled in
1593 strict mode). As such, the content of this section is not
1594 interpolated, and there should be an error in your web console.
1599 <file name="script.js">
1600 angular.module('ngAppStrictDemo', [])
1601 // BadController will fail to instantiate, due to relying on automatic function annotation,
1602 // rather than an explicit annotation
1603 .controller('BadController', function($scope) {
1607 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated,
1608 // due to using explicit annotations using the array style and $inject property, respectively.
1609 .controller('GoodController1', ['$scope', function($scope) {
1613 .controller('GoodController2', GoodController2);
1614 function GoodController2($scope) {
1615 $scope.name = "World";
1617 GoodController2.$inject = ['$scope'];
1619 <file name="style.css">
1620 div[ng-controller] {
1622 -webkit-border-radius: 4px;
1627 div[ng-controller^=Good] {
1628 border-color: #d6e9c6;
1629 background-color: #dff0d8;
1632 div[ng-controller^=Bad] {
1633 border-color: #ebccd1;
1634 background-color: #f2dede;
1641 function angularInit(element, bootstrap) {
1646 // The element `element` has priority over any other element
1647 forEach(ngAttrPrefixes, function(prefix) {
1648 var name = prefix + 'app';
1650 if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
1651 appElement = element;
1652 module = element.getAttribute(name);
1655 forEach(ngAttrPrefixes, function(prefix) {
1656 var name = prefix + 'app';
1659 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) {
1660 appElement = candidate;
1661 module = candidate.getAttribute(name);
1665 config.strictDi = getNgAttribute(appElement, "strict-di") !== null;
1666 bootstrap(appElement, module ? [module] : [], config);
1672 * @name angular.bootstrap
1675 * Use this function to manually start up angular application.
1677 * See: {@link guide/bootstrap Bootstrap}
1679 * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually.
1680 * They must use {@link ng.directive:ngApp ngApp}.
1682 * Angular will detect if it has been loaded into the browser more than once and only allow the
1683 * first loaded script to be bootstrapped and will report a warning to the browser console for
1684 * each of the subsequent scripts. This prevents strange results in applications, where otherwise
1685 * multiple instances of Angular try to work on the DOM.
1691 * <div ng-controller="WelcomeController">
1695 * <script src="angular.js"></script>
1697 * var app = angular.module('demo', [])
1698 * .controller('WelcomeController', function($scope) {
1699 * $scope.greeting = 'Welcome!';
1701 * angular.bootstrap(document, ['demo']);
1707 * @param {DOMElement} element DOM element which is the root of angular application.
1708 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application.
1709 * Each item in the array should be the name of a predefined module or a (DI annotated)
1710 * function that will be invoked by the injector as a `config` block.
1711 * See: {@link angular.module modules}
1712 * @param {Object=} config an object for defining configuration options for the application. The
1713 * following keys are supported:
1715 * * `strictDi` - disable automatic function annotation for the application. This is meant to
1716 * assist in finding bugs which break minified code. Defaults to `false`.
1718 * @returns {auto.$injector} Returns the newly created injector for this app.
1720 function bootstrap(element, modules, config) {
1721 if (!isObject(config)) config = {};
1722 var defaultConfig = {
1725 config = extend(defaultConfig, config);
1726 var doBootstrap = function() {
1727 element = jqLite(element);
1729 if (element.injector()) {
1730 var tag = (element[0] === document) ? 'document' : startingTag(element);
1731 //Encode angle brackets to prevent input from being sanitized to empty string #8683
1734 "App Already Bootstrapped with this Element '{0}'",
1735 tag.replace(/</,'<').replace(/>/,'>'));
1738 modules = modules || [];
1739 modules.unshift(['$provide', function($provide) {
1740 $provide.value('$rootElement', element);
1743 if (config.debugInfoEnabled) {
1744 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
1745 modules.push(['$compileProvider', function($compileProvider) {
1746 $compileProvider.debugInfoEnabled(true);
1750 modules.unshift('ng');
1751 var injector = createInjector(modules, config.strictDi);
1752 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
1753 function bootstrapApply(scope, element, compile, injector) {
1754 scope.$apply(function() {
1755 element.data('$injector', injector);
1756 compile(element)(scope);
1763 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/;
1764 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
1766 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) {
1767 config.debugInfoEnabled = true;
1768 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, '');
1771 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
1772 return doBootstrap();
1775 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
1776 angular.resumeBootstrap = function(extraModules) {
1777 forEach(extraModules, function(module) {
1778 modules.push(module);
1780 return doBootstrap();
1783 if (isFunction(angular.resumeDeferredBootstrap)) {
1784 angular.resumeDeferredBootstrap();
1790 * @name angular.reloadWithDebugInfo
1793 * Use this function to reload the current application with debug information turned on.
1794 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`.
1796 * See {@link ng.$compileProvider#debugInfoEnabled} for more.
1798 function reloadWithDebugInfo() {
1799 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name;
1800 window.location.reload();
1804 * @name angular.getTestability
1807 * Get the testability service for the instance of Angular on the given
1809 * @param {DOMElement} element DOM element which is the root of angular application.
1811 function getTestability(rootElement) {
1812 var injector = angular.element(rootElement).injector();
1814 throw ngMinErr('test',
1815 'no injector found for element argument to getTestability');
1817 return injector.get('$$testability');
1820 var SNAKE_CASE_REGEXP = /[A-Z]/g;
1821 function snake_case(name, separator) {
1822 separator = separator || '_';
1823 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
1824 return (pos ? separator : '') + letter.toLowerCase();
1828 var bindJQueryFired = false;
1829 var skipDestroyOnNextJQueryCleanData;
1830 function bindJQuery() {
1831 var originalCleanData;
1833 if (bindJQueryFired) {
1837 // bind to jQuery if present;
1839 jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present)
1840 !jqName ? undefined : // use jqLite
1841 window[jqName]; // use jQuery specified by `ngJq`
1843 // Use jQuery if it exists with proper functionality, otherwise default to us.
1844 // Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
1845 // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older
1846 // versions. It will not work for sure with jQuery <1.7, though.
1847 if (jQuery && jQuery.fn.on) {
1850 scope: JQLitePrototype.scope,
1851 isolateScope: JQLitePrototype.isolateScope,
1852 controller: JQLitePrototype.controller,
1853 injector: JQLitePrototype.injector,
1854 inheritedData: JQLitePrototype.inheritedData
1857 // All nodes removed from the DOM via various jQuery APIs like .remove()
1858 // are passed through jQuery.cleanData. Monkey-patch this method to fire
1859 // the $destroy event on all removed nodes.
1860 originalCleanData = jQuery.cleanData;
1861 jQuery.cleanData = function(elems) {
1863 if (!skipDestroyOnNextJQueryCleanData) {
1864 for (var i = 0, elem; (elem = elems[i]) != null; i++) {
1865 events = jQuery._data(elem, "events");
1866 if (events && events.$destroy) {
1867 jQuery(elem).triggerHandler('$destroy');
1871 skipDestroyOnNextJQueryCleanData = false;
1873 originalCleanData(elems);
1879 angular.element = jqLite;
1881 // Prevent double-proxying.
1882 bindJQueryFired = true;
1886 * throw error if the argument is falsy.
1888 function assertArg(arg, name, reason) {
1890 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
1895 function assertArgFn(arg, name, acceptArrayAnnotation) {
1896 if (acceptArrayAnnotation && isArray(arg)) {
1897 arg = arg[arg.length - 1];
1900 assertArg(isFunction(arg), name, 'not a function, got ' +
1901 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg));
1906 * throw error if the name given is hasOwnProperty
1907 * @param {String} name the name to test
1908 * @param {String} context the context in which the name is used, such as module or directive
1910 function assertNotHasOwnProperty(name, context) {
1911 if (name === 'hasOwnProperty') {
1912 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context);
1917 * Return the value accessible from the object by path. Any undefined traversals are ignored
1918 * @param {Object} obj starting object
1919 * @param {String} path path to traverse
1920 * @param {boolean} [bindFnToScope=true]
1921 * @returns {Object} value as accessible by path
1923 //TODO(misko): this function needs to be removed
1924 function getter(obj, path, bindFnToScope) {
1925 if (!path) return obj;
1926 var keys = path.split('.');
1928 var lastInstance = obj;
1929 var len = keys.length;
1931 for (var i = 0; i < len; i++) {
1934 obj = (lastInstance = obj)[key];
1937 if (!bindFnToScope && isFunction(obj)) {
1938 return bind(lastInstance, obj);
1944 * Return the DOM siblings between the first and last node in the given array.
1945 * @param {Array} array like object
1946 * @returns {Array} the inputted object or a jqLite collection containing the nodes
1948 function getBlockNodes(nodes) {
1949 // TODO(perf): update `nodes` instead of creating a new object?
1950 var node = nodes[0];
1951 var endNode = nodes[nodes.length - 1];
1954 for (var i = 1; node !== endNode && (node = node.nextSibling); i++) {
1955 if (blockNodes || nodes[i] !== node) {
1957 blockNodes = jqLite(slice.call(nodes, 0, i));
1959 blockNodes.push(node);
1963 return blockNodes || nodes;
1968 * Creates a new object without a prototype. This object is useful for lookup without having to
1969 * guard against prototypically inherited properties via hasOwnProperty.
1971 * Related micro-benchmarks:
1972 * - http://jsperf.com/object-create2
1973 * - http://jsperf.com/proto-map-lookup/2
1974 * - http://jsperf.com/for-in-vs-object-keys2
1978 function createMap() {
1979 return Object.create(null);
1982 var NODE_TYPE_ELEMENT = 1;
1983 var NODE_TYPE_ATTRIBUTE = 2;
1984 var NODE_TYPE_TEXT = 3;
1985 var NODE_TYPE_COMMENT = 8;
1986 var NODE_TYPE_DOCUMENT = 9;
1987 var NODE_TYPE_DOCUMENT_FRAGMENT = 11;
1991 * @name angular.Module
1995 * Interface for configuring angular {@link angular.module modules}.
1998 function setupModuleLoader(window) {
2000 var $injectorMinErr = minErr('$injector');
2001 var ngMinErr = minErr('ng');
2003 function ensure(obj, name, factory) {
2004 return obj[name] || (obj[name] = factory());
2007 var angular = ensure(window, 'angular', Object);
2009 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
2010 angular.$$minErr = angular.$$minErr || minErr;
2012 return ensure(angular, 'module', function() {
2013 /** @type {Object.<string, angular.Module>} */
2018 * @name angular.module
2022 * The `angular.module` is a global place for creating, registering and retrieving Angular
2024 * All modules (angular core or 3rd party) that should be available to an application must be
2025 * registered using this mechanism.
2027 * Passing one argument retrieves an existing {@link angular.Module},
2028 * whereas passing more than one argument creates a new {@link angular.Module}
2033 * A module is a collection of services, directives, controllers, filters, and configuration information.
2034 * `angular.module` is used to configure the {@link auto.$injector $injector}.
2037 * // Create a new module
2038 * var myModule = angular.module('myModule', []);
2040 * // register a new service
2041 * myModule.value('appName', 'MyCoolApp');
2043 * // configure existing services inside initialization blocks.
2044 * myModule.config(['$locationProvider', function($locationProvider) {
2045 * // Configure existing providers
2046 * $locationProvider.hashPrefix('!');
2050 * Then you can create an injector and load your modules like this:
2053 * var injector = angular.injector(['ng', 'myModule'])
2056 * However it's more likely that you'll just use
2057 * {@link ng.directive:ngApp ngApp} or
2058 * {@link angular.bootstrap} to simplify this process for you.
2060 * @param {!string} name The name of the module to create or retrieve.
2061 * @param {!Array.<string>=} requires If specified then new module is being created. If
2062 * unspecified then the module is being retrieved for further configuration.
2063 * @param {Function=} configFn Optional configuration function for the module. Same as
2064 * {@link angular.Module#config Module#config()}.
2065 * @returns {module} new module with the {@link angular.Module} api.
2067 return function module(name, requires, configFn) {
2068 var assertNotHasOwnProperty = function(name, context) {
2069 if (name === 'hasOwnProperty') {
2070 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
2074 assertNotHasOwnProperty(name, 'module');
2075 if (requires && modules.hasOwnProperty(name)) {
2076 modules[name] = null;
2078 return ensure(modules, name, function() {
2080 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
2081 "the module name or forgot to load it. If registering a module ensure that you " +
2082 "specify the dependencies as the second argument.", name);
2085 /** @type {!Array.<Array.<*>>} */
2086 var invokeQueue = [];
2088 /** @type {!Array.<Function>} */
2089 var configBlocks = [];
2091 /** @type {!Array.<Function>} */
2094 var config = invokeLater('$injector', 'invoke', 'push', configBlocks);
2096 /** @type {angular.Module} */
2097 var moduleInstance = {
2099 _invokeQueue: invokeQueue,
2100 _configBlocks: configBlocks,
2101 _runBlocks: runBlocks,
2105 * @name angular.Module#requires
2109 * Holds the list of modules which the injector will load before the current module is
2116 * @name angular.Module#name
2120 * Name of the module.
2127 * @name angular.Module#provider
2129 * @param {string} name service name
2130 * @param {Function} providerType Construction function for creating new instance of the
2133 * See {@link auto.$provide#provider $provide.provider()}.
2135 provider: invokeLaterAndSetModuleName('$provide', 'provider'),
2139 * @name angular.Module#factory
2141 * @param {string} name service name
2142 * @param {Function} providerFunction Function for creating new instance of the service.
2144 * See {@link auto.$provide#factory $provide.factory()}.
2146 factory: invokeLaterAndSetModuleName('$provide', 'factory'),
2150 * @name angular.Module#service
2152 * @param {string} name service name
2153 * @param {Function} constructor A constructor function that will be instantiated.
2155 * See {@link auto.$provide#service $provide.service()}.
2157 service: invokeLaterAndSetModuleName('$provide', 'service'),
2161 * @name angular.Module#value
2163 * @param {string} name service name
2164 * @param {*} object Service instance object.
2166 * See {@link auto.$provide#value $provide.value()}.
2168 value: invokeLater('$provide', 'value'),
2172 * @name angular.Module#constant
2174 * @param {string} name constant name
2175 * @param {*} object Constant value.
2177 * Because the constants are fixed, they get applied before other provide methods.
2178 * See {@link auto.$provide#constant $provide.constant()}.
2180 constant: invokeLater('$provide', 'constant', 'unshift'),
2184 * @name angular.Module#decorator
2186 * @param {string} The name of the service to decorate.
2187 * @param {Function} This function will be invoked when the service needs to be
2188 * instantiated and should return the decorated service instance.
2190 * See {@link auto.$provide#decorator $provide.decorator()}.
2192 decorator: invokeLaterAndSetModuleName('$provide', 'decorator'),
2196 * @name angular.Module#animation
2198 * @param {string} name animation name
2199 * @param {Function} animationFactory Factory function for creating new instance of an
2203 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
2206 * Defines an animation hook that can be later used with
2207 * {@link $animate $animate} service and directives that use this service.
2210 * module.animation('.animation-name', function($inject1, $inject2) {
2212 * eventName : function(element, done) {
2213 * //code to run the animation
2214 * //once complete, then run done()
2215 * return function cancellationFunction(element) {
2216 * //code to cancel the animation
2223 * See {@link ng.$animateProvider#register $animateProvider.register()} and
2224 * {@link ngAnimate ngAnimate module} for more information.
2226 animation: invokeLaterAndSetModuleName('$animateProvider', 'register'),
2230 * @name angular.Module#filter
2232 * @param {string} name Filter name - this must be a valid angular expression identifier
2233 * @param {Function} filterFactory Factory function for creating new instance of filter.
2235 * See {@link ng.$filterProvider#register $filterProvider.register()}.
2237 * <div class="alert alert-warning">
2238 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
2239 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
2240 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
2241 * (`myapp_subsection_filterx`).
2244 filter: invokeLaterAndSetModuleName('$filterProvider', 'register'),
2248 * @name angular.Module#controller
2250 * @param {string|Object} name Controller name, or an object map of controllers where the
2251 * keys are the names and the values are the constructors.
2252 * @param {Function} constructor Controller constructor function.
2254 * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
2256 controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'),
2260 * @name angular.Module#directive
2262 * @param {string|Object} name Directive name, or an object map of directives where the
2263 * keys are the names and the values are the factories.
2264 * @param {Function} directiveFactory Factory function for creating new instance of
2267 * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
2269 directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
2273 * @name angular.Module#config
2275 * @param {Function} configFn Execute this function on module load. Useful for service
2278 * Use this method to register work which needs to be performed on module loading.
2279 * For more about how to configure services, see
2280 * {@link providers#provider-recipe Provider Recipe}.
2286 * @name angular.Module#run
2288 * @param {Function} initializationFn Execute this function after injector creation.
2289 * Useful for application initialization.
2291 * Use this method to register work which should be performed when the injector is done
2292 * loading all modules.
2294 run: function(block) {
2295 runBlocks.push(block);
2304 return moduleInstance;
2307 * @param {string} provider
2308 * @param {string} method
2309 * @param {String=} insertMethod
2310 * @returns {angular.Module}
2312 function invokeLater(provider, method, insertMethod, queue) {
2313 if (!queue) queue = invokeQueue;
2315 queue[insertMethod || 'push']([provider, method, arguments]);
2316 return moduleInstance;
2321 * @param {string} provider
2322 * @param {string} method
2323 * @returns {angular.Module}
2325 function invokeLaterAndSetModuleName(provider, method) {
2326 return function(recipeName, factoryFunction) {
2327 if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
2328 invokeQueue.push([provider, method, arguments]);
2329 return moduleInstance;
2338 /* global: toDebugString: true */
2340 function serializeObject(obj) {
2343 return JSON.stringify(obj, function(key, val) {
2344 val = toJsonReplacer(key, val);
2345 if (isObject(val)) {
2347 if (seen.indexOf(val) >= 0) return '...';
2355 function toDebugString(obj) {
2356 if (typeof obj === 'function') {
2357 return obj.toString().replace(/ \{[\s\S]*$/, '');
2358 } else if (isUndefined(obj)) {
2360 } else if (typeof obj !== 'string') {
2361 return serializeObject(obj);
2366 /* global angularModule: true,
2371 htmlAnchorDirective,
2380 ngBindHtmlDirective,
2381 ngBindTemplateDirective,
2383 ngClassEvenDirective,
2384 ngClassOddDirective,
2386 ngControllerDirective,
2391 ngIncludeFillContentDirective,
2393 ngNonBindableDirective,
2394 ngPluralizeDirective,
2399 ngSwitchWhenDirective,
2400 ngSwitchDefaultDirective,
2402 ngTranscludeDirective,
2415 ngModelOptionsDirective,
2416 ngAttributeAliasDirectives,
2419 $AnchorScrollProvider,
2421 $CoreAnimateCssProvider,
2422 $$CoreAnimateQueueProvider,
2423 $$CoreAnimateRunnerProvider,
2425 $CacheFactoryProvider,
2426 $ControllerProvider,
2428 $ExceptionHandlerProvider,
2430 $$ForceReflowProvider,
2431 $InterpolateProvider,
2435 $HttpParamSerializerProvider,
2436 $HttpParamSerializerJQLikeProvider,
2437 $HttpBackendProvider,
2438 $xhrFactoryProvider,
2445 $$SanitizeUriProvider,
2447 $SceDelegateProvider,
2449 $TemplateCacheProvider,
2450 $TemplateRequestProvider,
2451 $$TestabilityProvider,
2456 $$CookieReaderProvider
2462 * @name angular.version
2465 * An object that contains information about the current AngularJS version.
2467 * This object has the following properties:
2469 * - `full` – `{string}` – Full version string, such as "0.9.18".
2470 * - `major` – `{number}` – Major version number, such as "0".
2471 * - `minor` – `{number}` – Minor version number, such as "9".
2472 * - `dot` – `{number}` – Dot version number, such as "18".
2473 * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
2476 full: '1.4.8', // all of these placeholder strings will be replaced by grunt's
2477 major: 1, // package task
2480 codeName: 'ice-manipulation'
2484 function publishExternalAPI(angular) {
2486 'bootstrap': bootstrap,
2493 'injector': createInjector,
2497 'fromJson': fromJson,
2498 'identity': identity,
2499 'isUndefined': isUndefined,
2500 'isDefined': isDefined,
2501 'isString': isString,
2502 'isFunction': isFunction,
2503 'isObject': isObject,
2504 'isNumber': isNumber,
2505 'isElement': isElement,
2509 'lowercase': lowercase,
2510 'uppercase': uppercase,
2511 'callbacks': {counter: 0},
2512 'getTestability': getTestability,
2515 'reloadWithDebugInfo': reloadWithDebugInfo
2518 angularModule = setupModuleLoader(window);
2520 angularModule('ng', ['ngLocale'], ['$provide',
2521 function ngModule($provide) {
2522 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
2524 $$sanitizeUri: $$SanitizeUriProvider
2526 $provide.provider('$compile', $CompileProvider).
2528 a: htmlAnchorDirective,
2529 input: inputDirective,
2530 textarea: inputDirective,
2531 form: formDirective,
2532 script: scriptDirective,
2533 select: selectDirective,
2534 style: styleDirective,
2535 option: optionDirective,
2536 ngBind: ngBindDirective,
2537 ngBindHtml: ngBindHtmlDirective,
2538 ngBindTemplate: ngBindTemplateDirective,
2539 ngClass: ngClassDirective,
2540 ngClassEven: ngClassEvenDirective,
2541 ngClassOdd: ngClassOddDirective,
2542 ngCloak: ngCloakDirective,
2543 ngController: ngControllerDirective,
2544 ngForm: ngFormDirective,
2545 ngHide: ngHideDirective,
2546 ngIf: ngIfDirective,
2547 ngInclude: ngIncludeDirective,
2548 ngInit: ngInitDirective,
2549 ngNonBindable: ngNonBindableDirective,
2550 ngPluralize: ngPluralizeDirective,
2551 ngRepeat: ngRepeatDirective,
2552 ngShow: ngShowDirective,
2553 ngStyle: ngStyleDirective,
2554 ngSwitch: ngSwitchDirective,
2555 ngSwitchWhen: ngSwitchWhenDirective,
2556 ngSwitchDefault: ngSwitchDefaultDirective,
2557 ngOptions: ngOptionsDirective,
2558 ngTransclude: ngTranscludeDirective,
2559 ngModel: ngModelDirective,
2560 ngList: ngListDirective,
2561 ngChange: ngChangeDirective,
2562 pattern: patternDirective,
2563 ngPattern: patternDirective,
2564 required: requiredDirective,
2565 ngRequired: requiredDirective,
2566 minlength: minlengthDirective,
2567 ngMinlength: minlengthDirective,
2568 maxlength: maxlengthDirective,
2569 ngMaxlength: maxlengthDirective,
2570 ngValue: ngValueDirective,
2571 ngModelOptions: ngModelOptionsDirective
2574 ngInclude: ngIncludeFillContentDirective
2576 directive(ngAttributeAliasDirectives).
2577 directive(ngEventDirectives);
2579 $anchorScroll: $AnchorScrollProvider,
2580 $animate: $AnimateProvider,
2581 $animateCss: $CoreAnimateCssProvider,
2582 $$animateQueue: $$CoreAnimateQueueProvider,
2583 $$AnimateRunner: $$CoreAnimateRunnerProvider,
2584 $browser: $BrowserProvider,
2585 $cacheFactory: $CacheFactoryProvider,
2586 $controller: $ControllerProvider,
2587 $document: $DocumentProvider,
2588 $exceptionHandler: $ExceptionHandlerProvider,
2589 $filter: $FilterProvider,
2590 $$forceReflow: $$ForceReflowProvider,
2591 $interpolate: $InterpolateProvider,
2592 $interval: $IntervalProvider,
2593 $http: $HttpProvider,
2594 $httpParamSerializer: $HttpParamSerializerProvider,
2595 $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
2596 $httpBackend: $HttpBackendProvider,
2597 $xhrFactory: $xhrFactoryProvider,
2598 $location: $LocationProvider,
2600 $parse: $ParseProvider,
2601 $rootScope: $RootScopeProvider,
2605 $sceDelegate: $SceDelegateProvider,
2606 $sniffer: $SnifferProvider,
2607 $templateCache: $TemplateCacheProvider,
2608 $templateRequest: $TemplateRequestProvider,
2609 $$testability: $$TestabilityProvider,
2610 $timeout: $TimeoutProvider,
2611 $window: $WindowProvider,
2612 $$rAF: $$RAFProvider,
2613 $$jqLite: $$jqLiteProvider,
2614 $$HashMap: $$HashMapProvider,
2615 $$cookieReader: $$CookieReaderProvider
2621 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2622 * Any commits to this file should be reviewed with security in mind. *
2623 * Changes to this file can potentially create security vulnerabilities. *
2624 * An approval from 2 Core members with history of modifying *
2625 * this file is required. *
2627 * Does the change somehow allow for arbitrary javascript to be executed? *
2628 * Or allows for someone to change the prototype of built-in objects? *
2629 * Or gives undesired access to variables likes document or window? *
2630 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2632 /* global JQLitePrototype: true,
2633 addEventListenerFn: true,
2634 removeEventListenerFn: true,
2639 //////////////////////////////////
2641 //////////////////////////////////
2645 * @name angular.element
2650 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
2652 * If jQuery is available, `angular.element` is an alias for the
2653 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element`
2654 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite."
2656 * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows
2657 * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most
2658 * commonly needed functionality with the goal of having a very small footprint.</div>
2660 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file.
2662 * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or
2663 * jqLite; they are never raw DOM references.</div>
2665 * ## Angular's jqLite
2666 * jqLite provides only the following jQuery methods:
2668 * - [`addClass()`](http://api.jquery.com/addClass/)
2669 * - [`after()`](http://api.jquery.com/after/)
2670 * - [`append()`](http://api.jquery.com/append/)
2671 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
2672 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData
2673 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
2674 * - [`clone()`](http://api.jquery.com/clone/)
2675 * - [`contents()`](http://api.jquery.com/contents/)
2676 * - [`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'.
2677 * - [`data()`](http://api.jquery.com/data/)
2678 * - [`detach()`](http://api.jquery.com/detach/)
2679 * - [`empty()`](http://api.jquery.com/empty/)
2680 * - [`eq()`](http://api.jquery.com/eq/)
2681 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name
2682 * - [`hasClass()`](http://api.jquery.com/hasClass/)
2683 * - [`html()`](http://api.jquery.com/html/)
2684 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors
2685 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
2686 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter
2687 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors
2688 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors
2689 * - [`prepend()`](http://api.jquery.com/prepend/)
2690 * - [`prop()`](http://api.jquery.com/prop/)
2691 * - [`ready()`](http://api.jquery.com/ready/)
2692 * - [`remove()`](http://api.jquery.com/remove/)
2693 * - [`removeAttr()`](http://api.jquery.com/removeAttr/)
2694 * - [`removeClass()`](http://api.jquery.com/removeClass/)
2695 * - [`removeData()`](http://api.jquery.com/removeData/)
2696 * - [`replaceWith()`](http://api.jquery.com/replaceWith/)
2697 * - [`text()`](http://api.jquery.com/text/)
2698 * - [`toggleClass()`](http://api.jquery.com/toggleClass/)
2699 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers.
2700 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter
2701 * - [`val()`](http://api.jquery.com/val/)
2702 * - [`wrap()`](http://api.jquery.com/wrap/)
2704 * ## jQuery/jqLite Extras
2705 * Angular also provides the following additional methods and events to both jQuery and jqLite:
2708 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event
2709 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM
2710 * element before it is removed.
2713 * - `controller(name)` - retrieves the controller of the current element or its parent. By default
2714 * retrieves controller associated with the `ngController` directive. If `name` is provided as
2715 * camelCase directive name, then the controller for this directive will be retrieved (e.g.
2717 * - `injector()` - retrieves the injector of the current element or its parent.
2718 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current
2719 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to
2721 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the
2722 * current element. This getter should be used only on elements that contain a directive which starts a new isolate
2723 * scope. Calling `scope()` on this element always returns the original non-isolate scope.
2724 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled.
2725 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
2726 * parent element is reached.
2728 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
2729 * @returns {Object} jQuery object.
2732 JQLite.expando = 'ng339';
2734 var jqCache = JQLite.cache = {},
2736 addEventListenerFn = function(element, type, fn) {
2737 element.addEventListener(type, fn, false);
2739 removeEventListenerFn = function(element, type, fn) {
2740 element.removeEventListener(type, fn, false);
2744 * !!! This is an undocumented "private" function !!!
2746 JQLite._data = function(node) {
2747 //jQuery always returns an object on cache miss
2748 return this.cache[node[this.expando]] || {};
2751 function jqNextId() { return ++jqId; }
2754 var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
2755 var MOZ_HACK_REGEXP = /^moz([A-Z])/;
2756 var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"};
2757 var jqLiteMinErr = minErr('jqLite');
2760 * Converts snake_case to camelCase.
2761 * Also there is special case for Moz prefix starting with upper case letter.
2762 * @param name Name to normalize
2764 function camelCase(name) {
2766 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
2767 return offset ? letter.toUpperCase() : letter;
2769 replace(MOZ_HACK_REGEXP, 'Moz$1');
2772 var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/;
2773 var HTML_REGEXP = /<|&#?\w+;/;
2774 var TAG_NAME_REGEXP = /<([\w:-]+)/;
2775 var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi;
2778 'option': [1, '<select multiple="multiple">', '</select>'],
2780 'thead': [1, '<table>', '</table>'],
2781 'col': [2, '<table><colgroup>', '</colgroup></table>'],
2782 'tr': [2, '<table><tbody>', '</tbody></table>'],
2783 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'],
2784 '_default': [0, "", ""]
2787 wrapMap.optgroup = wrapMap.option;
2788 wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
2789 wrapMap.th = wrapMap.td;
2792 function jqLiteIsTextNode(html) {
2793 return !HTML_REGEXP.test(html);
2796 function jqLiteAcceptsData(node) {
2797 // The window object can accept data but has no nodeType
2798 // Otherwise we are only interested in elements (1) and documents (9)
2799 var nodeType = node.nodeType;
2800 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
2803 function jqLiteHasData(node) {
2804 for (var key in jqCache[node.ng339]) {
2810 function jqLiteBuildFragment(html, context) {
2812 fragment = context.createDocumentFragment(),
2815 if (jqLiteIsTextNode(html)) {
2816 // Convert non-html into a text node
2817 nodes.push(context.createTextNode(html));
2819 // Convert html into DOM nodes
2820 tmp = tmp || fragment.appendChild(context.createElement("div"));
2821 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase();
2822 wrap = wrapMap[tag] || wrapMap._default;
2823 tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2];
2825 // Descend through wrappers to the right content
2828 tmp = tmp.lastChild;
2831 nodes = concat(nodes, tmp.childNodes);
2833 tmp = fragment.firstChild;
2834 tmp.textContent = "";
2837 // Remove wrapper from fragment
2838 fragment.textContent = "";
2839 fragment.innerHTML = ""; // Clear inner HTML
2840 forEach(nodes, function(node) {
2841 fragment.appendChild(node);
2847 function jqLiteParseHTML(html, context) {
2848 context = context || document;
2851 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) {
2852 return [context.createElement(parsed[1])];
2855 if ((parsed = jqLiteBuildFragment(html, context))) {
2856 return parsed.childNodes;
2863 // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
2864 var jqLiteContains = Node.prototype.contains || function(arg) {
2865 // jshint bitwise: false
2866 return !!(this.compareDocumentPosition(arg) & 16);
2867 // jshint bitwise: true
2870 /////////////////////////////////////////////
2871 function JQLite(element) {
2872 if (element instanceof JQLite) {
2878 if (isString(element)) {
2879 element = trim(element);
2882 if (!(this instanceof JQLite)) {
2883 if (argIsString && element.charAt(0) != '<') {
2884 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
2886 return new JQLite(element);
2890 jqLiteAddNodes(this, jqLiteParseHTML(element));
2892 jqLiteAddNodes(this, element);
2896 function jqLiteClone(element) {
2897 return element.cloneNode(true);
2900 function jqLiteDealoc(element, onlyDescendants) {
2901 if (!onlyDescendants) jqLiteRemoveData(element);
2903 if (element.querySelectorAll) {
2904 var descendants = element.querySelectorAll('*');
2905 for (var i = 0, l = descendants.length; i < l; i++) {
2906 jqLiteRemoveData(descendants[i]);
2911 function jqLiteOff(element, type, fn, unsupported) {
2912 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument');
2914 var expandoStore = jqLiteExpandoStore(element);
2915 var events = expandoStore && expandoStore.events;
2916 var handle = expandoStore && expandoStore.handle;
2918 if (!handle) return; //no listeners registered
2921 for (type in events) {
2922 if (type !== '$destroy') {
2923 removeEventListenerFn(element, type, handle);
2925 delete events[type];
2929 var removeHandler = function(type) {
2930 var listenerFns = events[type];
2931 if (isDefined(fn)) {
2932 arrayRemove(listenerFns || [], fn);
2934 if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) {
2935 removeEventListenerFn(element, type, handle);
2936 delete events[type];
2940 forEach(type.split(' '), function(type) {
2941 removeHandler(type);
2942 if (MOUSE_EVENT_MAP[type]) {
2943 removeHandler(MOUSE_EVENT_MAP[type]);
2949 function jqLiteRemoveData(element, name) {
2950 var expandoId = element.ng339;
2951 var expandoStore = expandoId && jqCache[expandoId];
2955 delete expandoStore.data[name];
2959 if (expandoStore.handle) {
2960 if (expandoStore.events.$destroy) {
2961 expandoStore.handle({}, '$destroy');
2965 delete jqCache[expandoId];
2966 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
2971 function jqLiteExpandoStore(element, createIfNecessary) {
2972 var expandoId = element.ng339,
2973 expandoStore = expandoId && jqCache[expandoId];
2975 if (createIfNecessary && !expandoStore) {
2976 element.ng339 = expandoId = jqNextId();
2977 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined};
2980 return expandoStore;
2984 function jqLiteData(element, key, value) {
2985 if (jqLiteAcceptsData(element)) {
2987 var isSimpleSetter = isDefined(value);
2988 var isSimpleGetter = !isSimpleSetter && key && !isObject(key);
2989 var massGetter = !key;
2990 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter);
2991 var data = expandoStore && expandoStore.data;
2993 if (isSimpleSetter) { // data('key', value)
2996 if (massGetter) { // data()
2999 if (isSimpleGetter) { // data('key')
3000 // don't force creation of expandoStore if it doesn't exist yet
3001 return data && data[key];
3002 } else { // mass-setter: data({key1: val1, key2: val2})
3010 function jqLiteHasClass(element, selector) {
3011 if (!element.getAttribute) return false;
3012 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " ").
3013 indexOf(" " + selector + " ") > -1);
3016 function jqLiteRemoveClass(element, cssClasses) {
3017 if (cssClasses && element.setAttribute) {
3018 forEach(cssClasses.split(' '), function(cssClass) {
3019 element.setAttribute('class', trim(
3020 (" " + (element.getAttribute('class') || '') + " ")
3021 .replace(/[\n\t]/g, " ")
3022 .replace(" " + trim(cssClass) + " ", " "))
3028 function jqLiteAddClass(element, cssClasses) {
3029 if (cssClasses && element.setAttribute) {
3030 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ')
3031 .replace(/[\n\t]/g, " ");
3033 forEach(cssClasses.split(' '), function(cssClass) {
3034 cssClass = trim(cssClass);
3035 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
3036 existingClasses += cssClass + ' ';
3040 element.setAttribute('class', trim(existingClasses));
3045 function jqLiteAddNodes(root, elements) {
3046 // THIS CODE IS VERY HOT. Don't make changes without benchmarking.
3050 // if a Node (the most common case)
3051 if (elements.nodeType) {
3052 root[root.length++] = elements;
3054 var length = elements.length;
3056 // if an Array or NodeList and not a Window
3057 if (typeof length === 'number' && elements.window !== elements) {
3059 for (var i = 0; i < length; i++) {
3060 root[root.length++] = elements[i];
3064 root[root.length++] = elements;
3071 function jqLiteController(element, name) {
3072 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller');
3075 function jqLiteInheritedData(element, name, value) {
3076 // if element is the document object work with the html element instead
3077 // this makes $(document).scope() possible
3078 if (element.nodeType == NODE_TYPE_DOCUMENT) {
3079 element = element.documentElement;
3081 var names = isArray(name) ? name : [name];
3084 for (var i = 0, ii = names.length; i < ii; i++) {
3085 if (isDefined(value = jqLite.data(element, names[i]))) return value;
3088 // If dealing with a document fragment node with a host element, and no parent, use the host
3089 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
3090 // to lookup parent controllers.
3091 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host);
3095 function jqLiteEmpty(element) {
3096 jqLiteDealoc(element, true);
3097 while (element.firstChild) {
3098 element.removeChild(element.firstChild);
3102 function jqLiteRemove(element, keepData) {
3103 if (!keepData) jqLiteDealoc(element);
3104 var parent = element.parentNode;
3105 if (parent) parent.removeChild(element);
3109 function jqLiteDocumentLoaded(action, win) {
3110 win = win || window;
3111 if (win.document.readyState === 'complete') {
3112 // Force the action to be run async for consistent behaviour
3113 // from the action's point of view
3114 // i.e. it will definitely not be in a $apply
3115 win.setTimeout(action);
3117 // No need to unbind this handler as load is only ever called once
3118 jqLite(win).on('load', action);
3122 //////////////////////////////////////////
3123 // Functions which are declared directly.
3124 //////////////////////////////////////////
3125 var JQLitePrototype = JQLite.prototype = {
3126 ready: function(fn) {
3129 function trigger() {
3135 // check if document is already loaded
3136 if (document.readyState === 'complete') {
3137 setTimeout(trigger);
3139 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9
3140 // we can not use jqLite since we are not done loading and jQuery could be loaded later.
3142 JQLite(window).on('load', trigger); // fallback to window.onload for others
3146 toString: function() {
3148 forEach(this, function(e) { value.push('' + e);});
3149 return '[' + value.join(', ') + ']';
3152 eq: function(index) {
3153 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
3162 //////////////////////////////////////////
3163 // Functions iterating getter/setters.
3164 // these functions return self on setter and
3166 //////////////////////////////////////////
3167 var BOOLEAN_ATTR = {};
3168 forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) {
3169 BOOLEAN_ATTR[lowercase(value)] = value;
3171 var BOOLEAN_ELEMENTS = {};
3172 forEach('input,select,option,textarea,button,form,details'.split(','), function(value) {
3173 BOOLEAN_ELEMENTS[value] = true;
3175 var ALIASED_ATTR = {
3176 'ngMinlength': 'minlength',
3177 'ngMaxlength': 'maxlength',
3180 'ngPattern': 'pattern'
3183 function getBooleanAttrName(element, name) {
3184 // check dom last since we will most likely fail on name
3185 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
3187 // booleanAttr is here twice to minimize DOM access
3188 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
3191 function getAliasedAttrName(name) {
3192 return ALIASED_ATTR[name];
3197 removeData: jqLiteRemoveData,
3198 hasData: jqLiteHasData
3199 }, function(fn, name) {
3205 inheritedData: jqLiteInheritedData,
3207 scope: function(element) {
3208 // Can't use jqLiteData here directly so we stay compatible with jQuery!
3209 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']);
3212 isolateScope: function(element) {
3213 // Can't use jqLiteData here directly so we stay compatible with jQuery!
3214 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate');
3217 controller: jqLiteController,
3219 injector: function(element) {
3220 return jqLiteInheritedData(element, '$injector');
3223 removeAttr: function(element, name) {
3224 element.removeAttribute(name);
3227 hasClass: jqLiteHasClass,
3229 css: function(element, name, value) {
3230 name = camelCase(name);
3232 if (isDefined(value)) {
3233 element.style[name] = value;
3235 return element.style[name];
3239 attr: function(element, name, value) {
3240 var nodeType = element.nodeType;
3241 if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) {
3244 var lowercasedName = lowercase(name);
3245 if (BOOLEAN_ATTR[lowercasedName]) {
3246 if (isDefined(value)) {
3248 element[name] = true;
3249 element.setAttribute(name, lowercasedName);
3251 element[name] = false;
3252 element.removeAttribute(lowercasedName);
3255 return (element[name] ||
3256 (element.attributes.getNamedItem(name) || noop).specified)
3260 } else if (isDefined(value)) {
3261 element.setAttribute(name, value);
3262 } else if (element.getAttribute) {
3263 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code
3264 // some elements (e.g. Document) don't have get attribute, so return undefined
3265 var ret = element.getAttribute(name, 2);
3266 // normalize non-existing attributes to undefined (as jQuery)
3267 return ret === null ? undefined : ret;
3271 prop: function(element, name, value) {
3272 if (isDefined(value)) {
3273 element[name] = value;
3275 return element[name];
3283 function getText(element, value) {
3284 if (isUndefined(value)) {
3285 var nodeType = element.nodeType;
3286 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : '';
3288 element.textContent = value;
3292 val: function(element, value) {
3293 if (isUndefined(value)) {
3294 if (element.multiple && nodeName_(element) === 'select') {
3296 forEach(element.options, function(option) {
3297 if (option.selected) {
3298 result.push(option.value || option.text);
3301 return result.length === 0 ? null : result;
3303 return element.value;
3305 element.value = value;
3308 html: function(element, value) {
3309 if (isUndefined(value)) {
3310 return element.innerHTML;
3312 jqLiteDealoc(element, true);
3313 element.innerHTML = value;
3317 }, function(fn, name) {
3319 * Properties: writes return selection, reads return first value
3321 JQLite.prototype[name] = function(arg1, arg2) {
3323 var nodeCount = this.length;
3325 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
3326 // in a way that survives minification.
3327 // jqLiteEmpty takes no arguments but is a setter.
3328 if (fn !== jqLiteEmpty &&
3329 (isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) {
3330 if (isObject(arg1)) {
3332 // we are a write, but the object properties are the key/values
3333 for (i = 0; i < nodeCount; i++) {
3334 if (fn === jqLiteData) {
3335 // data() takes the whole object in jQuery
3339 fn(this[i], key, arg1[key]);
3343 // return self for chaining
3346 // we are a read, so read the first child.
3347 // TODO: do we still need this?
3349 // Only if we have $dv do we iterate over all, otherwise it is just the first element.
3350 var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount;
3351 for (var j = 0; j < jj; j++) {
3352 var nodeValue = fn(this[j], arg1, arg2);
3353 value = value ? value + nodeValue : nodeValue;
3358 // we are a write, so apply to all children
3359 for (i = 0; i < nodeCount; i++) {
3360 fn(this[i], arg1, arg2);
3362 // return self for chaining
3368 function createEventHandler(element, events) {
3369 var eventHandler = function(event, type) {
3370 // jQuery specific api
3371 event.isDefaultPrevented = function() {
3372 return event.defaultPrevented;
3375 var eventFns = events[type || event.type];
3376 var eventFnsLength = eventFns ? eventFns.length : 0;
3378 if (!eventFnsLength) return;
3380 if (isUndefined(event.immediatePropagationStopped)) {
3381 var originalStopImmediatePropagation = event.stopImmediatePropagation;
3382 event.stopImmediatePropagation = function() {
3383 event.immediatePropagationStopped = true;
3385 if (event.stopPropagation) {
3386 event.stopPropagation();
3389 if (originalStopImmediatePropagation) {
3390 originalStopImmediatePropagation.call(event);
3395 event.isImmediatePropagationStopped = function() {
3396 return event.immediatePropagationStopped === true;
3399 // Some events have special handlers that wrap the real handler
3400 var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper;
3402 // Copy event handlers in case event handlers array is modified during execution.
3403 if ((eventFnsLength > 1)) {
3404 eventFns = shallowCopy(eventFns);
3407 for (var i = 0; i < eventFnsLength; i++) {
3408 if (!event.isImmediatePropagationStopped()) {
3409 handlerWrapper(element, event, eventFns[i]);
3414 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all
3415 // events on `element`
3416 eventHandler.elem = element;
3417 return eventHandler;
3420 function defaultHandlerWrapper(element, event, handler) {
3421 handler.call(element, event);
3424 function specialMouseHandlerWrapper(target, event, handler) {
3425 // Refer to jQuery's implementation of mouseenter & mouseleave
3426 // Read about mouseenter and mouseleave:
3427 // http://www.quirksmode.org/js/events_mouse.html#link8
3428 var related = event.relatedTarget;
3429 // For mousenter/leave call the handler if related is outside the target.
3430 // NB: No relatedTarget if the mouse left/entered the browser window
3431 if (!related || (related !== target && !jqLiteContains.call(target, related))) {
3432 handler.call(target, event);
3436 //////////////////////////////////////////
3437 // Functions iterating traversal.
3438 // These functions chain results into a single
3440 //////////////////////////////////////////
3442 removeData: jqLiteRemoveData,
3444 on: function jqLiteOn(element, type, fn, unsupported) {
3445 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters');
3447 // Do not add event handlers to non-elements because they will not be cleaned up.
3448 if (!jqLiteAcceptsData(element)) {
3452 var expandoStore = jqLiteExpandoStore(element, true);
3453 var events = expandoStore.events;
3454 var handle = expandoStore.handle;
3457 handle = expandoStore.handle = createEventHandler(element, events);
3460 // http://jsperf.com/string-indexof-vs-split
3461 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type];
3462 var i = types.length;
3464 var addHandler = function(type, specialHandlerWrapper, noEventListener) {
3465 var eventFns = events[type];
3468 eventFns = events[type] = [];
3469 eventFns.specialHandlerWrapper = specialHandlerWrapper;
3470 if (type !== '$destroy' && !noEventListener) {
3471 addEventListenerFn(element, type, handle);
3480 if (MOUSE_EVENT_MAP[type]) {
3481 addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper);
3482 addHandler(type, undefined, true);
3491 one: function(element, type, fn) {
3492 element = jqLite(element);
3494 //add the listener twice so that when it is called
3495 //you can remove the original function and still be
3496 //able to call element.off(ev, fn) normally
3497 element.on(type, function onFn() {
3498 element.off(type, fn);
3499 element.off(type, onFn);
3501 element.on(type, fn);
3504 replaceWith: function(element, replaceNode) {
3505 var index, parent = element.parentNode;
3506 jqLiteDealoc(element);
3507 forEach(new JQLite(replaceNode), function(node) {
3509 parent.insertBefore(node, index.nextSibling);
3511 parent.replaceChild(node, element);
3517 children: function(element) {
3519 forEach(element.childNodes, function(element) {
3520 if (element.nodeType === NODE_TYPE_ELEMENT) {
3521 children.push(element);
3527 contents: function(element) {
3528 return element.contentDocument || element.childNodes || [];
3531 append: function(element, node) {
3532 var nodeType = element.nodeType;
3533 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return;
3535 node = new JQLite(node);
3537 for (var i = 0, ii = node.length; i < ii; i++) {
3538 var child = node[i];
3539 element.appendChild(child);
3543 prepend: function(element, node) {
3544 if (element.nodeType === NODE_TYPE_ELEMENT) {
3545 var index = element.firstChild;
3546 forEach(new JQLite(node), function(child) {
3547 element.insertBefore(child, index);
3552 wrap: function(element, wrapNode) {
3553 wrapNode = jqLite(wrapNode).eq(0).clone()[0];
3554 var parent = element.parentNode;
3556 parent.replaceChild(wrapNode, element);
3558 wrapNode.appendChild(element);
3561 remove: jqLiteRemove,
3563 detach: function(element) {
3564 jqLiteRemove(element, true);
3567 after: function(element, newElement) {
3568 var index = element, parent = element.parentNode;
3569 newElement = new JQLite(newElement);
3571 for (var i = 0, ii = newElement.length; i < ii; i++) {
3572 var node = newElement[i];
3573 parent.insertBefore(node, index.nextSibling);
3578 addClass: jqLiteAddClass,
3579 removeClass: jqLiteRemoveClass,
3581 toggleClass: function(element, selector, condition) {
3583 forEach(selector.split(' '), function(className) {
3584 var classCondition = condition;
3585 if (isUndefined(classCondition)) {
3586 classCondition = !jqLiteHasClass(element, className);
3588 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className);
3593 parent: function(element) {
3594 var parent = element.parentNode;
3595 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null;
3598 next: function(element) {
3599 return element.nextElementSibling;
3602 find: function(element, selector) {
3603 if (element.getElementsByTagName) {
3604 return element.getElementsByTagName(selector);
3612 triggerHandler: function(element, event, extraParameters) {
3614 var dummyEvent, eventFnsCopy, handlerArgs;
3615 var eventName = event.type || event;
3616 var expandoStore = jqLiteExpandoStore(element);
3617 var events = expandoStore && expandoStore.events;
3618 var eventFns = events && events[eventName];
3621 // Create a dummy event to pass to the handlers
3623 preventDefault: function() { this.defaultPrevented = true; },
3624 isDefaultPrevented: function() { return this.defaultPrevented === true; },
3625 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; },
3626 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; },
3627 stopPropagation: noop,
3632 // If a custom event was provided then extend our dummy event with it
3634 dummyEvent = extend(dummyEvent, event);
3637 // Copy event handlers in case event handlers array is modified during execution.
3638 eventFnsCopy = shallowCopy(eventFns);
3639 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent];
3641 forEach(eventFnsCopy, function(fn) {
3642 if (!dummyEvent.isImmediatePropagationStopped()) {
3643 fn.apply(element, handlerArgs);
3648 }, function(fn, name) {
3650 * chaining functions
3652 JQLite.prototype[name] = function(arg1, arg2, arg3) {
3655 for (var i = 0, ii = this.length; i < ii; i++) {
3656 if (isUndefined(value)) {
3657 value = fn(this[i], arg1, arg2, arg3);
3658 if (isDefined(value)) {
3659 // any function which returns a value needs to be wrapped
3660 value = jqLite(value);
3663 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3));
3666 return isDefined(value) ? value : this;
3669 // bind legacy bind/unbind to on/off
3670 JQLite.prototype.bind = JQLite.prototype.on;
3671 JQLite.prototype.unbind = JQLite.prototype.off;
3675 // Provider for private $$jqLite service
3676 function $$jqLiteProvider() {
3677 this.$get = function $$jqLite() {
3678 return extend(JQLite, {
3679 hasClass: function(node, classes) {
3680 if (node.attr) node = node[0];
3681 return jqLiteHasClass(node, classes);
3683 addClass: function(node, classes) {
3684 if (node.attr) node = node[0];
3685 return jqLiteAddClass(node, classes);
3687 removeClass: function(node, classes) {
3688 if (node.attr) node = node[0];
3689 return jqLiteRemoveClass(node, classes);
3696 * Computes a hash of an 'obj'.
3699 * number is number as string
3700 * object is either result of calling $$hashKey function on the object or uniquely generated id,
3701 * that is also assigned to the $$hashKey property of the object.
3704 * @returns {string} hash string such that the same input will have the same hash string.
3705 * The resulting string key is in 'type:hashKey' format.
3707 function hashKey(obj, nextUidFn) {
3708 var key = obj && obj.$$hashKey;
3711 if (typeof key === 'function') {
3712 key = obj.$$hashKey();
3717 var objType = typeof obj;
3718 if (objType == 'function' || (objType == 'object' && obj !== null)) {
3719 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)();
3721 key = objType + ':' + obj;
3728 * HashMap which can use objects as keys
3730 function HashMap(array, isolatedUid) {
3733 this.nextUid = function() {
3737 forEach(array, this.put, this);
3739 HashMap.prototype = {
3741 * Store key value pair
3742 * @param key key to store can be any type
3743 * @param value value to store can be any type
3745 put: function(key, value) {
3746 this[hashKey(key, this.nextUid)] = value;
3751 * @returns {Object} the value for the key
3753 get: function(key) {
3754 return this[hashKey(key, this.nextUid)];
3758 * Remove the key/value pair
3761 remove: function(key) {
3762 var value = this[key = hashKey(key, this.nextUid)];
3768 var $$HashMapProvider = [function() {
3769 this.$get = [function() {
3777 * @name angular.injector
3781 * Creates an injector object that can be used for retrieving services as well as for
3782 * dependency injection (see {@link guide/di dependency injection}).
3784 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
3785 * {@link angular.module}. The `ng` module must be explicitly added.
3786 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which
3787 * disallows argument name annotation inference.
3788 * @returns {injector} Injector object. See {@link auto.$injector $injector}.
3793 * // create an injector
3794 * var $injector = angular.injector(['ng']);
3796 * // use the injector to kick off your application
3797 * // use the type inference to auto inject arguments, or use implicit injection
3798 * $injector.invoke(function($rootScope, $compile, $document) {
3799 * $compile($document)($rootScope);
3800 * $rootScope.$digest();
3804 * Sometimes you want to get access to the injector of a currently running Angular app
3805 * from outside Angular. Perhaps, you want to inject and compile some markup after the
3806 * application has been bootstrapped. You can do this using the extra `injector()` added
3807 * to JQuery/jqLite elements. See {@link angular.element}.
3809 * *This is fairly rare but could be the case if a third party library is injecting the
3812 * In the following example a new block of HTML containing a `ng-controller`
3813 * directive is added to the end of the document body by JQuery. We then compile and link
3814 * it into the current AngularJS scope.
3817 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>');
3818 * $(document.body).append($div);
3820 * angular.element(document).injector().invoke(function($compile) {
3821 * var scope = angular.element($div).scope();
3822 * $compile($div)(scope);
3833 * Implicit module which gets automatically added to each {@link auto.$injector $injector}.
3836 var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m;
3837 var FN_ARG_SPLIT = /,/;
3838 var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
3839 var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
3840 var $injectorMinErr = minErr('$injector');
3842 function anonFn(fn) {
3843 // For anonymous functions, showing at the very least the function signature can help in
3845 var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
3846 args = fnText.match(FN_ARGS);
3848 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
3853 function annotate(fn, strictDi, name) {
3859 if (typeof fn === 'function') {
3860 if (!($inject = fn.$inject)) {
3864 if (!isString(name) || !name) {
3865 name = fn.name || anonFn(fn);
3867 throw $injectorMinErr('strictdi',
3868 '{0} is not using explicit annotation and cannot be invoked in strict mode', name);
3870 fnText = fn.toString().replace(STRIP_COMMENTS, '');
3871 argDecl = fnText.match(FN_ARGS);
3872 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
3873 arg.replace(FN_ARG, function(all, underscore, name) {
3878 fn.$inject = $inject;
3880 } else if (isArray(fn)) {
3881 last = fn.length - 1;
3882 assertArgFn(fn[last], 'fn');
3883 $inject = fn.slice(0, last);
3885 assertArgFn(fn, 'fn', true);
3890 ///////////////////////////////////////
3898 * `$injector` is used to retrieve object instances as defined by
3899 * {@link auto.$provide provider}, instantiate types, invoke methods,
3902 * The following always holds true:
3905 * var $injector = angular.injector();
3906 * expect($injector.get('$injector')).toBe($injector);
3907 * expect($injector.invoke(function($injector) {
3909 * })).toBe($injector);
3912 * # Injection Function Annotation
3914 * JavaScript does not have annotations, and annotations are needed for dependency injection. The
3915 * following are all valid ways of annotating function with injection arguments and are equivalent.
3918 * // inferred (only works if code not minified/obfuscated)
3919 * $injector.invoke(function(serviceA){});
3922 * function explicit(serviceA) {};
3923 * explicit.$inject = ['serviceA'];
3924 * $injector.invoke(explicit);
3927 * $injector.invoke(['serviceA', function(serviceA){}]);
3932 * In JavaScript calling `toString()` on a function returns the function definition. The definition
3933 * can then be parsed and the function arguments can be extracted. This method of discovering
3934 * annotations is disallowed when the injector is in strict mode.
3935 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the
3938 * ## `$inject` Annotation
3939 * By adding an `$inject` property onto a function the injection parameters can be specified.
3942 * As an array of injection names, where the last item in the array is the function to call.
3947 * @name $injector#get
3950 * Return an instance of the service.
3952 * @param {string} name The name of the instance to retrieve.
3953 * @param {string=} caller An optional string to provide the origin of the function call for error messages.
3954 * @return {*} The instance.
3959 * @name $injector#invoke
3962 * Invoke the method and supply the method arguments from the `$injector`.
3964 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are
3965 * injected according to the {@link guide/di $inject Annotation} rules.
3966 * @param {Object=} self The `this` for the invoked method.
3967 * @param {Object=} locals Optional object. If preset then any argument names are read from this
3968 * object first, before the `$injector` is consulted.
3969 * @returns {*} the value returned by the invoked `fn` function.
3974 * @name $injector#has
3977 * Allows the user to query if the particular service exists.
3979 * @param {string} name Name of the service to query.
3980 * @returns {boolean} `true` if injector has given service.
3985 * @name $injector#instantiate
3987 * Create a new instance of JS type. The method takes a constructor function, invokes the new
3988 * operator, and supplies all of the arguments to the constructor function as specified by the
3989 * constructor annotation.
3991 * @param {Function} Type Annotated constructor function.
3992 * @param {Object=} locals Optional object. If preset then any argument names are read from this
3993 * object first, before the `$injector` is consulted.
3994 * @returns {Object} new instance of `Type`.
3999 * @name $injector#annotate
4002 * Returns an array of service names which the function is requesting for injection. This API is
4003 * used by the injector to determine which services need to be injected into the function when the
4004 * function is invoked. There are three ways in which the function can be annotated with the needed
4009 * The simplest form is to extract the dependencies from the arguments of the function. This is done
4010 * by converting the function into a string using `toString()` method and extracting the argument
4014 * function MyController($scope, $route) {
4019 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
4022 * You can disallow this method by using strict injection mode.
4024 * This method does not work with code minification / obfuscation. For this reason the following
4025 * annotation strategies are supported.
4027 * # The `$inject` property
4029 * If a function has an `$inject` property and its value is an array of strings, then the strings
4030 * represent names of services to be injected into the function.
4033 * var MyController = function(obfuscatedScope, obfuscatedRoute) {
4036 * // Define function dependencies
4037 * MyController['$inject'] = ['$scope', '$route'];
4040 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
4043 * # The array notation
4045 * It is often desirable to inline Injected functions and that's when setting the `$inject` property
4046 * is very inconvenient. In these situations using the array notation to specify the dependencies in
4047 * a way that survives minification is a better choice:
4050 * // We wish to write this (not minification / obfuscation safe)
4051 * injector.invoke(function($compile, $rootScope) {
4055 * // We are forced to write break inlining
4056 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
4059 * tmpFn.$inject = ['$compile', '$rootScope'];
4060 * injector.invoke(tmpFn);
4062 * // To better support inline function the inline annotation is supported
4063 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
4068 * expect(injector.annotate(
4069 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
4070 * ).toEqual(['$compile', '$rootScope']);
4073 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to
4074 * be retrieved as described above.
4076 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference.
4078 * @returns {Array.<string>} The names of the services which the function requires.
4090 * The {@link auto.$provide $provide} service has a number of methods for registering components
4091 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on
4092 * {@link angular.Module}.
4094 * An Angular **service** is a singleton object created by a **service factory**. These **service
4095 * factories** are functions which, in turn, are created by a **service provider**.
4096 * The **service providers** are constructor functions. When instantiated they must contain a
4097 * property called `$get`, which holds the **service factory** function.
4099 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the
4100 * correct **service provider**, instantiating it and then calling its `$get` **service factory**
4101 * function to get the instance of the **service**.
4103 * Often services have no configuration options and there is no need to add methods to the service
4104 * provider. The provider will be no more than a constructor function with a `$get` property. For
4105 * these cases the {@link auto.$provide $provide} service has additional helper methods to register
4106 * services without specifying a provider.
4108 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the
4109 * {@link auto.$injector $injector}
4110 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by
4111 * providers and services.
4112 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by
4113 * services, not providers.
4114 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`,
4115 * that will be wrapped in a **service provider** object, whose `$get` property will contain the
4116 * given factory function.
4117 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class`
4118 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate
4119 * a new object using the given constructor function.
4121 * See the individual methods for more information and examples.
4126 * @name $provide#provider
4129 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions
4130 * are constructor functions, whose instances are responsible for "providing" a factory for a
4133 * Service provider names start with the name of the service they provide followed by `Provider`.
4134 * For example, the {@link ng.$log $log} service has a provider called
4135 * {@link ng.$logProvider $logProvider}.
4137 * Service provider objects can have additional methods which allow configuration of the provider
4138 * and its service. Importantly, you can configure what kind of service is created by the `$get`
4139 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a
4140 * method {@link ng.$logProvider#debugEnabled debugEnabled}
4141 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the
4144 * @param {string} name The name of the instance. NOTE: the provider will be available under `name +
4146 * @param {(Object|function())} provider If the provider is:
4148 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using
4149 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created.
4150 * - `Constructor`: a new instance of the provider will be created using
4151 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`.
4153 * @returns {Object} registered provider instance
4157 * The following example shows how to create a simple event tracking service and register it using
4158 * {@link auto.$provide#provider $provide.provider()}.
4161 * // Define the eventTracker provider
4162 * function EventTrackerProvider() {
4163 * var trackingUrl = '/track';
4165 * // A provider method for configuring where the tracked events should been saved
4166 * this.setTrackingUrl = function(url) {
4167 * trackingUrl = url;
4170 * // The service factory function
4171 * this.$get = ['$http', function($http) {
4172 * var trackedEvents = {};
4174 * // Call this to track an event
4175 * event: function(event) {
4176 * var count = trackedEvents[event] || 0;
4178 * trackedEvents[event] = count;
4181 * // Call this to save the tracked events to the trackingUrl
4182 * save: function() {
4183 * $http.post(trackingUrl, trackedEvents);
4189 * describe('eventTracker', function() {
4192 * beforeEach(module(function($provide) {
4193 * // Register the eventTracker provider
4194 * $provide.provider('eventTracker', EventTrackerProvider);
4197 * beforeEach(module(function(eventTrackerProvider) {
4198 * // Configure eventTracker provider
4199 * eventTrackerProvider.setTrackingUrl('/custom-track');
4202 * it('tracks events', inject(function(eventTracker) {
4203 * expect(eventTracker.event('login')).toEqual(1);
4204 * expect(eventTracker.event('login')).toEqual(2);
4207 * it('saves to the tracking url', inject(function(eventTracker, $http) {
4208 * postSpy = spyOn($http, 'post');
4209 * eventTracker.event('login');
4210 * eventTracker.save();
4211 * expect(postSpy).toHaveBeenCalled();
4212 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track');
4213 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track');
4214 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 });
4222 * @name $provide#factory
4225 * Register a **service factory**, which will be called to return the service instance.
4226 * This is short for registering a service where its provider consists of only a `$get` property,
4227 * which is the given service factory function.
4228 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to
4229 * configure your service in a provider.
4231 * @param {string} name The name of the instance.
4232 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation.
4233 * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`.
4234 * @returns {Object} registered provider instance
4237 * Here is an example of registering a service
4239 * $provide.factory('ping', ['$http', function($http) {
4240 * return function ping() {
4241 * return $http.send('/ping');
4245 * You would then inject and use this service like this:
4247 * someModule.controller('Ctrl', ['ping', function(ping) {
4256 * @name $provide#service
4259 * Register a **service constructor**, which will be invoked with `new` to create the service
4261 * This is short for registering a service where its provider's `$get` property is the service
4262 * constructor function that will be used to instantiate the service instance.
4264 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service
4267 * @param {string} name The name of the instance.
4268 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function)
4269 * that will be instantiated.
4270 * @returns {Object} registered provider instance
4273 * Here is an example of registering a service using
4274 * {@link auto.$provide#service $provide.service(class)}.
4276 * var Ping = function($http) {
4277 * this.$http = $http;
4280 * Ping.$inject = ['$http'];
4282 * Ping.prototype.send = function() {
4283 * return this.$http.get('/ping');
4285 * $provide.service('ping', Ping);
4287 * You would then inject and use this service like this:
4289 * someModule.controller('Ctrl', ['ping', function(ping) {
4298 * @name $provide#value
4301 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a
4302 * number, an array, an object or a function. This is short for registering a service where its
4303 * provider's `$get` property is a factory function that takes no arguments and returns the **value
4306 * Value services are similar to constant services, except that they cannot be injected into a
4307 * module configuration function (see {@link angular.Module#config}) but they can be overridden by
4309 * {@link auto.$provide#decorator decorator}.
4311 * @param {string} name The name of the instance.
4312 * @param {*} value The value.
4313 * @returns {Object} registered provider instance
4316 * Here are some examples of creating value services.
4318 * $provide.value('ADMIN_USER', 'admin');
4320 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 });
4322 * $provide.value('halfOf', function(value) {
4331 * @name $provide#constant
4334 * Register a **constant service**, such as a string, a number, an array, an object or a function,
4335 * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be
4336 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot
4337 * be overridden by an Angular {@link auto.$provide#decorator decorator}.
4339 * @param {string} name The name of the constant.
4340 * @param {*} value The constant value.
4341 * @returns {Object} registered instance
4344 * Here a some examples of creating constants:
4346 * $provide.constant('SHARD_HEIGHT', 306);
4348 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']);
4350 * $provide.constant('double', function(value) {
4359 * @name $provide#decorator
4362 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator
4363 * intercepts the creation of a service, allowing it to override or modify the behaviour of the
4364 * service. The object returned by the decorator may be the original service, or a new service
4365 * object which replaces or wraps and delegates to the original service.
4367 * @param {string} name The name of the service to decorate.
4368 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be
4369 * instantiated and should return the decorated service instance. The function is called using
4370 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable.
4371 * Local injection arguments:
4373 * * `$delegate` - The original service instance, which can be monkey patched, configured,
4374 * decorated or delegated to.
4377 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting
4378 * calls to {@link ng.$log#error $log.warn()}.
4380 * $provide.decorator('$log', ['$delegate', function($delegate) {
4381 * $delegate.warn = $delegate.error;
4388 function createInjector(modulesToLoad, strictDi) {
4389 strictDi = (strictDi === true);
4390 var INSTANTIATING = {},
4391 providerSuffix = 'Provider',
4393 loadedModules = new HashMap([], true),
4396 provider: supportObject(provider),
4397 factory: supportObject(factory),
4398 service: supportObject(service),
4399 value: supportObject(value),
4400 constant: supportObject(constant),
4401 decorator: decorator
4404 providerInjector = (providerCache.$injector =
4405 createInternalInjector(providerCache, function(serviceName, caller) {
4406 if (angular.isString(caller)) {
4409 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
4412 instanceInjector = (instanceCache.$injector =
4413 createInternalInjector(instanceCache, function(serviceName, caller) {
4414 var provider = providerInjector.get(serviceName + providerSuffix, caller);
4415 return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
4419 forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });
4421 return instanceInjector;
4423 ////////////////////////////////////
4425 ////////////////////////////////////
4427 function supportObject(delegate) {
4428 return function(key, value) {
4429 if (isObject(key)) {
4430 forEach(key, reverseParams(delegate));
4432 return delegate(key, value);
4437 function provider(name, provider_) {
4438 assertNotHasOwnProperty(name, 'service');
4439 if (isFunction(provider_) || isArray(provider_)) {
4440 provider_ = providerInjector.instantiate(provider_);
4442 if (!provider_.$get) {
4443 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name);
4445 return providerCache[name + providerSuffix] = provider_;
4448 function enforceReturnValue(name, factory) {
4449 return function enforcedReturnValue() {
4450 var result = instanceInjector.invoke(factory, this);
4451 if (isUndefined(result)) {
4452 throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
4458 function factory(name, factoryFn, enforce) {
4459 return provider(name, {
4460 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
4464 function service(name, constructor) {
4465 return factory(name, ['$injector', function($injector) {
4466 return $injector.instantiate(constructor);
4470 function value(name, val) { return factory(name, valueFn(val), false); }
4472 function constant(name, value) {
4473 assertNotHasOwnProperty(name, 'constant');
4474 providerCache[name] = value;
4475 instanceCache[name] = value;
4478 function decorator(serviceName, decorFn) {
4479 var origProvider = providerInjector.get(serviceName + providerSuffix),
4480 orig$get = origProvider.$get;
4482 origProvider.$get = function() {
4483 var origInstance = instanceInjector.invoke(orig$get, origProvider);
4484 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
4488 ////////////////////////////////////
4490 ////////////////////////////////////
4491 function loadModules(modulesToLoad) {
4492 assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array');
4493 var runBlocks = [], moduleFn;
4494 forEach(modulesToLoad, function(module) {
4495 if (loadedModules.get(module)) return;
4496 loadedModules.put(module, true);
4498 function runInvokeQueue(queue) {
4500 for (i = 0, ii = queue.length; i < ii; i++) {
4501 var invokeArgs = queue[i],
4502 provider = providerInjector.get(invokeArgs[0]);
4504 provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
4509 if (isString(module)) {
4510 moduleFn = angularModule(module);
4511 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
4512 runInvokeQueue(moduleFn._invokeQueue);
4513 runInvokeQueue(moduleFn._configBlocks);
4514 } else if (isFunction(module)) {
4515 runBlocks.push(providerInjector.invoke(module));
4516 } else if (isArray(module)) {
4517 runBlocks.push(providerInjector.invoke(module));
4519 assertArgFn(module, 'module');
4522 if (isArray(module)) {
4523 module = module[module.length - 1];
4525 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) {
4526 // Safari & FF's stack traces don't contain error.message content
4527 // unlike those of Chrome and IE
4528 // So if stack doesn't contain message, we create a new string that contains both.
4529 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
4531 e = e.message + '\n' + e.stack;
4533 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}",
4534 module, e.stack || e.message || e);
4540 ////////////////////////////////////
4541 // internal Injector
4542 ////////////////////////////////////
4544 function createInternalInjector(cache, factory) {
4546 function getService(serviceName, caller) {
4547 if (cache.hasOwnProperty(serviceName)) {
4548 if (cache[serviceName] === INSTANTIATING) {
4549 throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
4550 serviceName + ' <- ' + path.join(' <- '));
4552 return cache[serviceName];
4555 path.unshift(serviceName);
4556 cache[serviceName] = INSTANTIATING;
4557 return cache[serviceName] = factory(serviceName, caller);
4559 if (cache[serviceName] === INSTANTIATING) {
4560 delete cache[serviceName];
4569 function invoke(fn, self, locals, serviceName) {
4570 if (typeof locals === 'string') {
4571 serviceName = locals;
4576 $inject = createInjector.$$annotate(fn, strictDi, serviceName),
4580 for (i = 0, length = $inject.length; i < length; i++) {
4582 if (typeof key !== 'string') {
4583 throw $injectorMinErr('itkn',
4584 'Incorrect injection token! Expected service name as string, got {0}', key);
4587 locals && locals.hasOwnProperty(key)
4589 : getService(key, serviceName)
4596 // http://jsperf.com/angularjs-invoke-apply-vs-switch
4598 return fn.apply(self, args);
4601 function instantiate(Type, locals, serviceName) {
4602 // Check if Type is annotated and use just the given function at n-1 as parameter
4603 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
4604 // Object creation: http://jsperf.com/create-constructor/2
4605 var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
4606 var returnedValue = invoke(Type, instance, locals, serviceName);
4608 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
4613 instantiate: instantiate,
4615 annotate: createInjector.$$annotate,
4616 has: function(name) {
4617 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
4623 createInjector.$$annotate = annotate;
4627 * @name $anchorScrollProvider
4630 * Use `$anchorScrollProvider` to disable automatic scrolling whenever
4631 * {@link ng.$location#hash $location.hash()} changes.
4633 function $AnchorScrollProvider() {
4635 var autoScrollingEnabled = true;
4639 * @name $anchorScrollProvider#disableAutoScrolling
4642 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to
4643 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br />
4644 * Use this method to disable automatic scrolling.
4646 * If automatic scrolling is disabled, one must explicitly call
4647 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the
4650 this.disableAutoScrolling = function() {
4651 autoScrollingEnabled = false;
4656 * @name $anchorScroll
4659 * @requires $location
4660 * @requires $rootScope
4663 * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the
4664 * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified
4666 * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#the-indicated-part-of-the-document).
4668 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
4669 * match any anchor whenever it changes. This can be disabled by calling
4670 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}.
4672 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a
4673 * vertical scroll-offset (either fixed or dynamic).
4675 * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of
4676 * {@link ng.$location#hash $location.hash()} will be used.
4678 * @property {(number|function|jqLite)} yOffset
4679 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed
4680 * positioned elements at the top of the page, such as navbars, headers etc.
4682 * `yOffset` can be specified in various ways:
4683 * - **number**: A fixed number of pixels to be used as offset.<br /><br />
4684 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return
4685 * a number representing the offset (in pixels).<br /><br />
4686 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from
4687 * the top of the page to the element's bottom will be used as offset.<br />
4688 * **Note**: The element will be taken into account only as long as its `position` is set to
4689 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust
4690 * their height and/or positioning according to the viewport's size.
4693 * <div class="alert alert-warning">
4694 * In order for `yOffset` to work properly, scrolling should take place on the document's root and
4695 * not some child element.
4699 <example module="anchorScrollExample">
4700 <file name="index.html">
4701 <div id="scrollArea" ng-controller="ScrollController">
4702 <a ng-click="gotoBottom()">Go to bottom</a>
4703 <a id="bottom"></a> You're at the bottom!
4706 <file name="script.js">
4707 angular.module('anchorScrollExample', [])
4708 .controller('ScrollController', ['$scope', '$location', '$anchorScroll',
4709 function ($scope, $location, $anchorScroll) {
4710 $scope.gotoBottom = function() {
4711 // set the location.hash to the id of
4712 // the element you wish to scroll to.
4713 $location.hash('bottom');
4715 // call $anchorScroll()
4720 <file name="style.css">
4734 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value).
4735 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details.
4738 <example module="anchorScrollOffsetExample">
4739 <file name="index.html">
4740 <div class="fixed-header" ng-controller="headerCtrl">
4741 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]">
4745 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]">
4749 <file name="script.js">
4750 angular.module('anchorScrollOffsetExample', [])
4751 .run(['$anchorScroll', function($anchorScroll) {
4752 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels
4754 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope',
4755 function ($anchorScroll, $location, $scope) {
4756 $scope.gotoAnchor = function(x) {
4757 var newHash = 'anchor' + x;
4758 if ($location.hash() !== newHash) {
4759 // set the $location.hash to `newHash` and
4760 // $anchorScroll will automatically scroll to it
4761 $location.hash('anchor' + x);
4763 // call $anchorScroll() explicitly,
4764 // since $location.hash hasn't changed
4771 <file name="style.css">
4777 border: 2px dashed DarkOrchid;
4778 padding: 10px 10px 200px 10px;
4782 background-color: rgba(0, 0, 0, 0.2);
4785 top: 0; left: 0; right: 0;
4789 display: inline-block;
4795 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
4796 var document = $window.document;
4798 // Helper function to get first anchor from a NodeList
4799 // (using `Array#some()` instead of `angular#forEach()` since it's more performant
4800 // and working in all supported browsers.)
4801 function getFirstAnchor(list) {
4803 Array.prototype.some.call(list, function(element) {
4804 if (nodeName_(element) === 'a') {
4812 function getYOffset() {
4814 var offset = scroll.yOffset;
4816 if (isFunction(offset)) {
4818 } else if (isElement(offset)) {
4819 var elem = offset[0];
4820 var style = $window.getComputedStyle(elem);
4821 if (style.position !== 'fixed') {
4824 offset = elem.getBoundingClientRect().bottom;
4826 } else if (!isNumber(offset)) {
4833 function scrollTo(elem) {
4835 elem.scrollIntoView();
4837 var offset = getYOffset();
4840 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly.
4841 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the
4842 // top of the viewport.
4844 // IF the number of pixels from the top of `elem` to the end of the page's content is less
4845 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some
4846 // way down the page.
4848 // This is often the case for elements near the bottom of the page.
4850 // In such cases we do not need to scroll the whole `offset` up, just the difference between
4851 // the top of the element and the offset, which is enough to align the top of `elem` at the
4852 // desired position.
4853 var elemTop = elem.getBoundingClientRect().top;
4854 $window.scrollBy(0, elemTop - offset);
4857 $window.scrollTo(0, 0);
4861 function scroll(hash) {
4862 hash = isString(hash) ? hash : $location.hash();
4865 // empty hash, scroll to the top of the page
4866 if (!hash) scrollTo(null);
4868 // element with given id
4869 else if ((elm = document.getElementById(hash))) scrollTo(elm);
4871 // first anchor with given name :-D
4872 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm);
4874 // no element and hash == 'top', scroll to the top of the page
4875 else if (hash === 'top') scrollTo(null);
4878 // does not scroll when user clicks on anchor link that is currently on
4879 // (no url change, no $location.hash() change), browser native does scroll
4880 if (autoScrollingEnabled) {
4881 $rootScope.$watch(function autoScrollWatch() {return $location.hash();},
4882 function autoScrollWatchAction(newVal, oldVal) {
4883 // skip the initial scroll if $location.hash is empty
4884 if (newVal === oldVal && newVal === '') return;
4886 jqLiteDocumentLoaded(function() {
4887 $rootScope.$evalAsync(scroll);
4896 var $animateMinErr = minErr('$animate');
4897 var ELEMENT_NODE = 1;
4898 var NG_ANIMATE_CLASSNAME = 'ng-animate';
4900 function mergeClasses(a,b) {
4901 if (!a && !b) return '';
4904 if (isArray(a)) a = a.join(' ');
4905 if (isArray(b)) b = b.join(' ');
4909 function extractElementNode(element) {
4910 for (var i = 0; i < element.length; i++) {
4911 var elm = element[i];
4912 if (elm.nodeType === ELEMENT_NODE) {
4918 function splitClasses(classes) {
4919 if (isString(classes)) {
4920 classes = classes.split(' ');
4923 // Use createMap() to prevent class assumptions involving property names in
4925 var obj = createMap();
4926 forEach(classes, function(klass) {
4927 // sometimes the split leaves empty string values
4928 // incase extra spaces were applied to the options
4936 // if any other type of options value besides an Object value is
4937 // passed into the $animate.method() animation then this helper code
4938 // will be run which will ignore it. While this patch is not the
4939 // greatest solution to this, a lot of existing plugins depend on
4940 // $animate to either call the callback (< 1.2) or return a promise
4941 // that can be changed. This helper function ensures that the options
4942 // are wiped clean incase a callback function is provided.
4943 function prepareAnimateOptions(options) {
4944 return isObject(options)
4949 var $$CoreAnimateRunnerProvider = function() {
4950 this.$get = ['$q', '$$rAF', function($q, $$rAF) {
4951 function AnimateRunner() {}
4952 AnimateRunner.all = noop;
4953 AnimateRunner.chain = noop;
4954 AnimateRunner.prototype = {
4960 then: function(pass, fail) {
4961 return $q(function(resolve) {
4965 }).then(pass, fail);
4968 return AnimateRunner;
4972 // this is prefixed with Core since it conflicts with
4973 // the animateQueueProvider defined in ngAnimate/animateQueue.js
4974 var $$CoreAnimateQueueProvider = function() {
4975 var postDigestQueue = new HashMap();
4976 var postDigestElements = [];
4978 this.$get = ['$$AnimateRunner', '$rootScope',
4979 function($$AnimateRunner, $rootScope) {
4986 push: function(element, event, options, domOperation) {
4987 domOperation && domOperation();
4989 options = options || {};
4990 options.from && element.css(options.from);
4991 options.to && element.css(options.to);
4993 if (options.addClass || options.removeClass) {
4994 addRemoveClassesPostDigest(element, options.addClass, options.removeClass);
4997 return new $$AnimateRunner(); // jshint ignore:line
5002 function updateData(data, classes, value) {
5003 var changed = false;
5005 classes = isString(classes) ? classes.split(' ') :
5006 isArray(classes) ? classes : [];
5007 forEach(classes, function(className) {
5010 data[className] = value;
5017 function handleCSSClassChanges() {
5018 forEach(postDigestElements, function(element) {
5019 var data = postDigestQueue.get(element);
5021 var existing = splitClasses(element.attr('class'));
5024 forEach(data, function(status, className) {
5025 var hasClass = !!existing[className];
5026 if (status !== hasClass) {
5028 toAdd += (toAdd.length ? ' ' : '') + className;
5030 toRemove += (toRemove.length ? ' ' : '') + className;
5035 forEach(element, function(elm) {
5036 toAdd && jqLiteAddClass(elm, toAdd);
5037 toRemove && jqLiteRemoveClass(elm, toRemove);
5039 postDigestQueue.remove(element);
5042 postDigestElements.length = 0;
5046 function addRemoveClassesPostDigest(element, add, remove) {
5047 var data = postDigestQueue.get(element) || {};
5049 var classesAdded = updateData(data, add, true);
5050 var classesRemoved = updateData(data, remove, false);
5052 if (classesAdded || classesRemoved) {
5054 postDigestQueue.put(element, data);
5055 postDigestElements.push(element);
5057 if (postDigestElements.length === 1) {
5058 $rootScope.$$postDigest(handleCSSClassChanges);
5067 * @name $animateProvider
5070 * Default implementation of $animate that doesn't perform any animations, instead just
5071 * synchronously performs DOM updates and resolves the returned runner promise.
5073 * In order to enable animations the `ngAnimate` module has to be loaded.
5075 * To see the functional implementation check out `src/ngAnimate/animate.js`.
5077 var $AnimateProvider = ['$provide', function($provide) {
5078 var provider = this;
5080 this.$$registeredAnimations = Object.create(null);
5084 * @name $animateProvider#register
5087 * Registers a new injectable animation factory function. The factory function produces the
5088 * animation object which contains callback functions for each event that is expected to be
5091 * * `eventFn`: `function(element, ... , doneFunction, options)`
5092 * The element to animate, the `doneFunction` and the options fed into the animation. Depending
5093 * on the type of animation additional arguments will be injected into the animation function. The
5094 * list below explains the function signatures for the different animation methods:
5096 * - setClass: function(element, addedClasses, removedClasses, doneFunction, options)
5097 * - addClass: function(element, addedClasses, doneFunction, options)
5098 * - removeClass: function(element, removedClasses, doneFunction, options)
5099 * - enter, leave, move: function(element, doneFunction, options)
5100 * - animate: function(element, fromStyles, toStyles, doneFunction, options)
5102 * Make sure to trigger the `doneFunction` once the animation is fully complete.
5106 * //enter, leave, move signature
5107 * eventFn : function(element, done, options) {
5108 * //code to run the animation
5109 * //once complete, then run done()
5110 * return function endFunction(wasCancelled) {
5111 * //code to cancel the animation
5117 * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to).
5118 * @param {Function} factory The factory function that will be executed to return the animation
5121 this.register = function(name, factory) {
5122 if (name && name.charAt(0) !== '.') {
5123 throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name);
5126 var key = name + '-animation';
5127 provider.$$registeredAnimations[name.substr(1)] = key;
5128 $provide.factory(key, factory);
5133 * @name $animateProvider#classNameFilter
5136 * Sets and/or returns the CSS class regular expression that is checked when performing
5137 * an animation. Upon bootstrap the classNameFilter value is not set at all and will
5138 * therefore enable $animate to attempt to perform an animation on any element that is triggered.
5139 * When setting the `classNameFilter` value, animations will only be performed on elements
5140 * that successfully match the filter expression. This in turn can boost performance
5141 * for low-powered devices as well as applications containing a lot of structural operations.
5142 * @param {RegExp=} expression The className expression which will be checked against all animations
5143 * @return {RegExp} The current CSS className expression value. If null then there is no expression value
5145 this.classNameFilter = function(expression) {
5146 if (arguments.length === 1) {
5147 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null;
5148 if (this.$$classNameFilter) {
5149 var reservedRegex = new RegExp("(\\s+|\\/)" + NG_ANIMATE_CLASSNAME + "(\\s+|\\/)");
5150 if (reservedRegex.test(this.$$classNameFilter.toString())) {
5151 throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME);
5156 return this.$$classNameFilter;
5159 this.$get = ['$$animateQueue', function($$animateQueue) {
5160 function domInsert(element, parentElement, afterElement) {
5161 // if for some reason the previous element was removed
5162 // from the dom sometime before this code runs then let's
5163 // just stick to using the parent element as the anchor
5165 var afterNode = extractElementNode(afterElement);
5166 if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) {
5167 afterElement = null;
5170 afterElement ? afterElement.after(element) : parentElement.prepend(element);
5176 * @description The $animate service exposes a series of DOM utility methods that provide support
5177 * for animation hooks. The default behavior is the application of DOM operations, however,
5178 * when an animation is detected (and animations are enabled), $animate will do the heavy lifting
5179 * to ensure that animation runs with the triggered DOM operation.
5181 * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't
5182 * included and only when it is active then the animation hooks that `$animate` triggers will be
5183 * functional. Once active then all structural `ng-` directives will trigger animations as they perform
5184 * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`,
5185 * `ngShow`, `ngHide` and `ngMessages` also provide support for animations.
5187 * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives.
5189 * To learn more about enabling animation support, click here to visit the
5190 * {@link ngAnimate ngAnimate module page}.
5193 // we don't call it directly since non-existant arguments may
5194 // be interpreted as null within the sub enabled function
5201 * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...)
5202 * has fired on the given element or among any of its children. Once the listener is fired, the provided callback
5203 * is fired with the following params:
5206 * $animate.on('enter', container,
5207 * function callback(element, phase) {
5208 * // cool we detected an enter animation within the container
5213 * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...)
5214 * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself
5215 * as well as among its children
5216 * @param {Function} callback the callback function that will be fired when the listener is triggered
5218 * The arguments present in the callback function are:
5219 * * `element` - The captured DOM element that the animation was fired on.
5220 * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends).
5222 on: $$animateQueue.on,
5227 * @name $animate#off
5229 * @description Deregisters an event listener based on the event which has been associated with the provided element. This method
5230 * can be used in three different ways depending on the arguments:
5233 * // remove all the animation event listeners listening for `enter`
5234 * $animate.off('enter');
5236 * // remove all the animation event listeners listening for `enter` on the given element and its children
5237 * $animate.off('enter', container);
5239 * // remove the event listener function provided by `listenerFn` that is set
5240 * // to listen for `enter` on the given `element` as well as its children
5241 * $animate.off('enter', container, callback);
5244 * @param {string} event the animation event (e.g. enter, leave, move, addClass, removeClass, etc...)
5245 * @param {DOMElement=} container the container element the event listener was placed on
5246 * @param {Function=} callback the callback function that was registered as the listener
5248 off: $$animateQueue.off,
5252 * @name $animate#pin
5254 * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists
5255 * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the
5256 * element despite being outside the realm of the application or within another application. Say for example if the application
5257 * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated
5258 * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind
5259 * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association.
5261 * Note that this feature is only active when the `ngAnimate` module is used.
5263 * @param {DOMElement} element the external element that will be pinned
5264 * @param {DOMElement} parentElement the host parent element that will be associated with the external element
5266 pin: $$animateQueue.pin,
5271 * @name $animate#enabled
5273 * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This
5274 * function can be called in four ways:
5277 * // returns true or false
5278 * $animate.enabled();
5280 * // changes the enabled state for all animations
5281 * $animate.enabled(false);
5282 * $animate.enabled(true);
5284 * // returns true or false if animations are enabled for an element
5285 * $animate.enabled(element);
5287 * // changes the enabled state for an element and its children
5288 * $animate.enabled(element, true);
5289 * $animate.enabled(element, false);
5292 * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state
5293 * @param {boolean=} enabled whether or not the animations will be enabled for the element
5295 * @return {boolean} whether or not animations are enabled
5297 enabled: $$animateQueue.enabled,
5301 * @name $animate#cancel
5303 * @description Cancels the provided animation.
5305 * @param {Promise} animationPromise The animation promise that is returned when an animation is started.
5307 cancel: function(runner) {
5308 runner.end && runner.end();
5314 * @name $animate#enter
5316 * @description Inserts the element into the DOM either after the `after` element (if provided) or
5317 * as the first child within the `parent` element and then triggers an animation.
5318 * A promise is returned that will be resolved during the next digest once the animation
5321 * @param {DOMElement} element the element which will be inserted into the DOM
5322 * @param {DOMElement} parent the parent element which will append the element as
5323 * a child (so long as the after element is not present)
5324 * @param {DOMElement=} after the sibling element after which the element will be appended
5325 * @param {object=} options an optional collection of options/styles that will be applied to the element
5327 * @return {Promise} the animation callback promise
5329 enter: function(element, parent, after, options) {
5330 parent = parent && jqLite(parent);
5331 after = after && jqLite(after);
5332 parent = parent || after.parent();
5333 domInsert(element, parent, after);
5334 return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options));
5340 * @name $animate#move
5342 * @description Inserts (moves) the element into its new position in the DOM either after
5343 * the `after` element (if provided) or as the first child within the `parent` element
5344 * and then triggers an animation. A promise is returned that will be resolved
5345 * during the next digest once the animation has completed.
5347 * @param {DOMElement} element the element which will be moved into the new DOM position
5348 * @param {DOMElement} parent the parent element which will append the element as
5349 * a child (so long as the after element is not present)
5350 * @param {DOMElement=} after the sibling element after which the element will be appended
5351 * @param {object=} options an optional collection of options/styles that will be applied to the element
5353 * @return {Promise} the animation callback promise
5355 move: function(element, parent, after, options) {
5356 parent = parent && jqLite(parent);
5357 after = after && jqLite(after);
5358 parent = parent || after.parent();
5359 domInsert(element, parent, after);
5360 return $$animateQueue.push(element, 'move', prepareAnimateOptions(options));
5365 * @name $animate#leave
5367 * @description Triggers an animation and then removes the element from the DOM.
5368 * When the function is called a promise is returned that will be resolved during the next
5369 * digest once the animation has completed.
5371 * @param {DOMElement} element the element which will be removed from the DOM
5372 * @param {object=} options an optional collection of options/styles that will be applied to the element
5374 * @return {Promise} the animation callback promise
5376 leave: function(element, options) {
5377 return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() {
5384 * @name $animate#addClass
5387 * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon
5388 * execution, the addClass operation will only be handled after the next digest and it will not trigger an
5389 * animation if element already contains the CSS class or if the class is removed at a later step.
5390 * Note that class-based animations are treated differently compared to structural animations
5391 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5392 * depending if CSS or JavaScript animations are used.
5394 * @param {DOMElement} element the element which the CSS classes will be applied to
5395 * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces)
5396 * @param {object=} options an optional collection of options/styles that will be applied to the element
5398 * @return {Promise} the animation callback promise
5400 addClass: function(element, className, options) {
5401 options = prepareAnimateOptions(options);
5402 options.addClass = mergeClasses(options.addclass, className);
5403 return $$animateQueue.push(element, 'addClass', options);
5408 * @name $animate#removeClass
5411 * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon
5412 * execution, the removeClass operation will only be handled after the next digest and it will not trigger an
5413 * animation if element does not contain the CSS class or if the class is added at a later step.
5414 * Note that class-based animations are treated differently compared to structural animations
5415 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5416 * depending if CSS or JavaScript animations are used.
5418 * @param {DOMElement} element the element which the CSS classes will be applied to
5419 * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces)
5420 * @param {object=} options an optional collection of options/styles that will be applied to the element
5422 * @return {Promise} the animation callback promise
5424 removeClass: function(element, className, options) {
5425 options = prepareAnimateOptions(options);
5426 options.removeClass = mergeClasses(options.removeClass, className);
5427 return $$animateQueue.push(element, 'removeClass', options);
5432 * @name $animate#setClass
5435 * @description Performs both the addition and removal of a CSS classes on an element and (during the process)
5436 * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and
5437 * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has
5438 * passed. Note that class-based animations are treated differently compared to structural animations
5439 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5440 * depending if CSS or JavaScript animations are used.
5442 * @param {DOMElement} element the element which the CSS classes will be applied to
5443 * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces)
5444 * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces)
5445 * @param {object=} options an optional collection of options/styles that will be applied to the element
5447 * @return {Promise} the animation callback promise
5449 setClass: function(element, add, remove, options) {
5450 options = prepareAnimateOptions(options);
5451 options.addClass = mergeClasses(options.addClass, add);
5452 options.removeClass = mergeClasses(options.removeClass, remove);
5453 return $$animateQueue.push(element, 'setClass', options);
5458 * @name $animate#animate
5461 * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element.
5462 * If any detected CSS transition, keyframe or JavaScript matches the provided className value then the animation will take
5463 * on the provided styles. For example, if a transition animation is set for the given className then the provided from and
5464 * to styles will be applied alongside the given transition. If a JavaScript animation is detected then the provided styles
5465 * will be given in as function paramters into the `animate` method (or as apart of the `options` parameter).
5467 * @param {DOMElement} element the element which the CSS styles will be applied to
5468 * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation.
5469 * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation.
5470 * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If
5471 * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element.
5472 * (Note that if no animation is detected then this value will not be appplied to the element.)
5473 * @param {object=} options an optional collection of options/styles that will be applied to the element
5475 * @return {Promise} the animation callback promise
5477 animate: function(element, from, to, className, options) {
5478 options = prepareAnimateOptions(options);
5479 options.from = options.from ? extend(options.from, from) : from;
5480 options.to = options.to ? extend(options.to, to) : to;
5482 className = className || 'ng-inline-animate';
5483 options.tempClasses = mergeClasses(options.tempClasses, className);
5484 return $$animateQueue.push(element, 'animate', options);
5496 * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included,
5497 * then the `$animateCss` service will actually perform animations.
5499 * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}.
5501 var $CoreAnimateCssProvider = function() {
5502 this.$get = ['$$rAF', '$q', function($$rAF, $q) {
5504 var RAFPromise = function() {};
5505 RAFPromise.prototype = {
5506 done: function(cancel) {
5507 this.defer && this.defer[cancel === true ? 'reject' : 'resolve']();
5512 cancel: function() {
5515 getPromise: function() {
5517 this.defer = $q.defer();
5519 return this.defer.promise;
5521 then: function(f1,f2) {
5522 return this.getPromise().then(f1,f2);
5524 'catch': function(f1) {
5525 return this.getPromise()['catch'](f1);
5527 'finally': function(f1) {
5528 return this.getPromise()['finally'](f1);
5532 return function(element, options) {
5533 // there is no point in applying the styles since
5534 // there is no animation that goes on at all in
5535 // this version of $animateCss.
5536 if (options.cleanupStyles) {
5537 options.from = options.to = null;
5541 element.css(options.from);
5542 options.from = null;
5545 var closed, runner = new RAFPromise();
5563 if (options.addClass) {
5564 element.addClass(options.addClass);
5565 options.addClass = null;
5567 if (options.removeClass) {
5568 element.removeClass(options.removeClass);
5569 options.removeClass = null;
5572 element.css(options.to);
5580 /* global stripHash: true */
5583 * ! This is a private undocumented service !
5588 * This object has two goals:
5590 * - hide all the global state in the browser caused by the window object
5591 * - abstract away all the browser specific features and inconsistencies
5593 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser`
5594 * service, which can be used for convenient testing of the application without the interaction with
5595 * the real browser apis.
5598 * @param {object} window The global window object.
5599 * @param {object} document jQuery wrapped document.
5600 * @param {object} $log window.console or an object with the same interface.
5601 * @param {object} $sniffer $sniffer service
5603 function Browser(window, document, $log, $sniffer) {
5605 rawDocument = document[0],
5606 location = window.location,
5607 history = window.history,
5608 setTimeout = window.setTimeout,
5609 clearTimeout = window.clearTimeout,
5610 pendingDeferIds = {};
5612 self.isMock = false;
5614 var outstandingRequestCount = 0;
5615 var outstandingRequestCallbacks = [];
5617 // TODO(vojta): remove this temporary api
5618 self.$$completeOutstandingRequest = completeOutstandingRequest;
5619 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
5622 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
5623 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
5625 function completeOutstandingRequest(fn) {
5627 fn.apply(null, sliceArgs(arguments, 1));
5629 outstandingRequestCount--;
5630 if (outstandingRequestCount === 0) {
5631 while (outstandingRequestCallbacks.length) {
5633 outstandingRequestCallbacks.pop()();
5642 function getHash(url) {
5643 var index = url.indexOf('#');
5644 return index === -1 ? '' : url.substr(index);
5649 * Note: this method is used only by scenario runner
5650 * TODO(vojta): prefix this method with $$ ?
5651 * @param {function()} callback Function that will be called when no outstanding request
5653 self.notifyWhenNoOutstandingRequests = function(callback) {
5654 if (outstandingRequestCount === 0) {
5657 outstandingRequestCallbacks.push(callback);
5661 //////////////////////////////////////////////////////////////
5663 //////////////////////////////////////////////////////////////
5665 var cachedState, lastHistoryState,
5666 lastBrowserUrl = location.href,
5667 baseElement = document.find('base'),
5668 pendingLocation = null;
5671 lastHistoryState = cachedState;
5674 * @name $browser#url
5678 * Without any argument, this method just returns current value of location.href.
5681 * With at least one argument, this method sets url to new value.
5682 * If html5 history api supported, pushState/replaceState is used, otherwise
5683 * location.href/location.replace is used.
5684 * Returns its own instance to allow chaining
5686 * NOTE: this api is intended for use only by the $location service. Please use the
5687 * {@link ng.$location $location service} to change url.
5689 * @param {string} url New url (when used as setter)
5690 * @param {boolean=} replace Should new url replace current history record?
5691 * @param {object=} state object to use with pushState/replaceState
5693 self.url = function(url, replace, state) {
5694 // In modern browsers `history.state` is `null` by default; treating it separately
5695 // from `undefined` would cause `$browser.url('/foo')` to change `history.state`
5696 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here.
5697 if (isUndefined(state)) {
5701 // Android Browser BFCache causes location, history reference to become stale.
5702 if (location !== window.location) location = window.location;
5703 if (history !== window.history) history = window.history;
5707 var sameState = lastHistoryState === state;
5709 // Don't change anything if previous and current URLs and states match. This also prevents
5710 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
5711 // See https://github.com/angular/angular.js/commit/ffb2701
5712 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) {
5715 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
5716 lastBrowserUrl = url;
5717 lastHistoryState = state;
5718 // Don't use history API if only the hash changed
5719 // due to a bug in IE10/IE11 which leads
5720 // to not firing a `hashchange` nor `popstate` event
5721 // in some cases (see #9143).
5722 if ($sniffer.history && (!sameBase || !sameState)) {
5723 history[replace ? 'replaceState' : 'pushState'](state, '', url);
5725 // Do the assignment again so that those two variables are referentially identical.
5726 lastHistoryState = cachedState;
5728 if (!sameBase || pendingLocation) {
5729 pendingLocation = url;
5732 location.replace(url);
5733 } else if (!sameBase) {
5734 location.href = url;
5736 location.hash = getHash(url);
5738 if (location.href !== url) {
5739 pendingLocation = url;
5745 // - pendingLocation is needed as browsers don't allow to read out
5746 // the new location.href if a reload happened or if there is a bug like in iOS 9 (see
5747 // https://openradar.appspot.com/22186109).
5748 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
5749 return pendingLocation || location.href.replace(/%27/g,"'");
5754 * @name $browser#state
5757 * This method is a getter.
5759 * Return history.state or null if history.state is undefined.
5761 * @returns {object} state
5763 self.state = function() {
5767 var urlChangeListeners = [],
5768 urlChangeInit = false;
5770 function cacheStateAndFireUrlChange() {
5771 pendingLocation = null;
5776 function getCurrentState() {
5778 return history.state;
5780 // MSIE can reportedly throw when there is no state (UNCONFIRMED).
5784 // This variable should be used *only* inside the cacheState function.
5785 var lastCachedState = null;
5786 function cacheState() {
5787 // This should be the only place in $browser where `history.state` is read.
5788 cachedState = getCurrentState();
5789 cachedState = isUndefined(cachedState) ? null : cachedState;
5791 // Prevent callbacks fo fire twice if both hashchange & popstate were fired.
5792 if (equals(cachedState, lastCachedState)) {
5793 cachedState = lastCachedState;
5795 lastCachedState = cachedState;
5798 function fireUrlChange() {
5799 if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) {
5803 lastBrowserUrl = self.url();
5804 lastHistoryState = cachedState;
5805 forEach(urlChangeListeners, function(listener) {
5806 listener(self.url(), cachedState);
5811 * @name $browser#onUrlChange
5814 * Register callback function that will be called, when url changes.
5816 * It's only called when the url is changed from outside of angular:
5817 * - user types different url into address bar
5818 * - user clicks on history (forward/back) button
5819 * - user clicks on a link
5821 * It's not called when url is changed by $browser.url() method
5823 * The listener gets called with new url as parameter.
5825 * NOTE: this api is intended for use only by the $location service. Please use the
5826 * {@link ng.$location $location service} to monitor url changes in angular apps.
5828 * @param {function(string)} listener Listener function to be called when url changes.
5829 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous.
5831 self.onUrlChange = function(callback) {
5832 // TODO(vojta): refactor to use node's syntax for events
5833 if (!urlChangeInit) {
5834 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera)
5835 // don't fire popstate when user change the address bar and don't fire hashchange when url
5836 // changed by push/replaceState
5838 // html5 history api - popstate event
5839 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange);
5841 jqLite(window).on('hashchange', cacheStateAndFireUrlChange);
5843 urlChangeInit = true;
5846 urlChangeListeners.push(callback);
5852 * Remove popstate and hashchange handler from window.
5854 * NOTE: this api is intended for use only by $rootScope.
5856 self.$$applicationDestroyed = function() {
5857 jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange);
5861 * Checks whether the url has changed outside of Angular.
5862 * Needs to be exported to be able to check for changes that have been done in sync,
5863 * as hashchange/popstate events fire in async.
5865 self.$$checkUrlChange = fireUrlChange;
5867 //////////////////////////////////////////////////////////////
5869 //////////////////////////////////////////////////////////////
5872 * @name $browser#baseHref
5875 * Returns current <base href>
5876 * (always relative - without domain)
5878 * @returns {string} The current base href
5880 self.baseHref = function() {
5881 var href = baseElement.attr('href');
5882 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : '';
5886 * @name $browser#defer
5887 * @param {function()} fn A function, who's execution should be deferred.
5888 * @param {number=} [delay=0] of milliseconds to defer the function execution.
5889 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
5892 * Executes a fn asynchronously via `setTimeout(fn, delay)`.
5894 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
5895 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed
5896 * via `$browser.defer.flush()`.
5899 self.defer = function(fn, delay) {
5901 outstandingRequestCount++;
5902 timeoutId = setTimeout(function() {
5903 delete pendingDeferIds[timeoutId];
5904 completeOutstandingRequest(fn);
5906 pendingDeferIds[timeoutId] = true;
5912 * @name $browser#defer.cancel
5915 * Cancels a deferred task identified with `deferId`.
5917 * @param {*} deferId Token returned by the `$browser.defer` function.
5918 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
5921 self.defer.cancel = function(deferId) {
5922 if (pendingDeferIds[deferId]) {
5923 delete pendingDeferIds[deferId];
5924 clearTimeout(deferId);
5925 completeOutstandingRequest(noop);
5933 function $BrowserProvider() {
5934 this.$get = ['$window', '$log', '$sniffer', '$document',
5935 function($window, $log, $sniffer, $document) {
5936 return new Browser($window, $document, $log, $sniffer);
5942 * @name $cacheFactory
5945 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to
5950 * var cache = $cacheFactory('cacheId');
5951 * expect($cacheFactory.get('cacheId')).toBe(cache);
5952 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined();
5954 * cache.put("key", "value");
5955 * cache.put("another key", "another value");
5957 * // We've specified no options on creation
5958 * expect(cache.info()).toEqual({id: 'cacheId', size: 2});
5963 * @param {string} cacheId Name or id of the newly created cache.
5964 * @param {object=} options Options object that specifies the cache behavior. Properties:
5966 * - `{number=}` `capacity` — turns the cache into LRU cache.
5968 * @returns {object} Newly created cache object with the following set of methods:
5970 * - `{object}` `info()` — Returns id, size, and options of cache.
5971 * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns
5973 * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss.
5974 * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache.
5975 * - `{void}` `removeAll()` — Removes all cached values.
5976 * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory.
5979 <example module="cacheExampleApp">
5980 <file name="index.html">
5981 <div ng-controller="CacheController">
5982 <input ng-model="newCacheKey" placeholder="Key">
5983 <input ng-model="newCacheValue" placeholder="Value">
5984 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button>
5986 <p ng-if="keys.length">Cached Values</p>
5987 <div ng-repeat="key in keys">
5988 <span ng-bind="key"></span>
5990 <b ng-bind="cache.get(key)"></b>
5994 <div ng-repeat="(key, value) in cache.info()">
5995 <span ng-bind="key"></span>
5997 <b ng-bind="value"></b>
6001 <file name="script.js">
6002 angular.module('cacheExampleApp', []).
6003 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) {
6005 $scope.cache = $cacheFactory('cacheId');
6006 $scope.put = function(key, value) {
6007 if (angular.isUndefined($scope.cache.get(key))) {
6008 $scope.keys.push(key);
6010 $scope.cache.put(key, angular.isUndefined(value) ? null : value);
6014 <file name="style.css">
6021 function $CacheFactoryProvider() {
6023 this.$get = function() {
6026 function cacheFactory(cacheId, options) {
6027 if (cacheId in caches) {
6028 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId);
6032 stats = extend({}, options, {id: cacheId}),
6034 capacity = (options && options.capacity) || Number.MAX_VALUE,
6035 lruHash = createMap(),
6041 * @name $cacheFactory.Cache
6044 * A cache object used to store and retrieve data, primarily used by
6045 * {@link $http $http} and the {@link ng.directive:script script} directive to cache
6046 * templates and other data.
6049 * angular.module('superCache')
6050 * .factory('superCache', ['$cacheFactory', function($cacheFactory) {
6051 * return $cacheFactory('super-cache');
6058 * it('should behave like a cache', inject(function(superCache) {
6059 * superCache.put('key', 'value');
6060 * superCache.put('another key', 'another value');
6062 * expect(superCache.info()).toEqual({
6063 * id: 'super-cache',
6067 * superCache.remove('another key');
6068 * expect(superCache.get('another key')).toBeUndefined();
6070 * superCache.removeAll();
6071 * expect(superCache.info()).toEqual({
6072 * id: 'super-cache',
6078 return caches[cacheId] = {
6082 * @name $cacheFactory.Cache#put
6086 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be
6087 * retrieved later, and incrementing the size of the cache if the key was not already
6088 * present in the cache. If behaving like an LRU cache, it will also remove stale
6089 * entries from the set.
6091 * It will not insert undefined values into the cache.
6093 * @param {string} key the key under which the cached data is stored.
6094 * @param {*} value the value to store alongside the key. If it is undefined, the key
6095 * will not be stored.
6096 * @returns {*} the value stored.
6098 put: function(key, value) {
6099 if (isUndefined(value)) return;
6100 if (capacity < Number.MAX_VALUE) {
6101 var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
6106 if (!(key in data)) size++;
6109 if (size > capacity) {
6110 this.remove(staleEnd.key);
6118 * @name $cacheFactory.Cache#get
6122 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object.
6124 * @param {string} key the key of the data to be retrieved
6125 * @returns {*} the value stored.
6127 get: function(key) {
6128 if (capacity < Number.MAX_VALUE) {
6129 var lruEntry = lruHash[key];
6131 if (!lruEntry) return;
6142 * @name $cacheFactory.Cache#remove
6146 * Removes an entry from the {@link $cacheFactory.Cache Cache} object.
6148 * @param {string} key the key of the entry to be removed
6150 remove: function(key) {
6151 if (capacity < Number.MAX_VALUE) {
6152 var lruEntry = lruHash[key];
6154 if (!lruEntry) return;
6156 if (lruEntry == freshEnd) freshEnd = lruEntry.p;
6157 if (lruEntry == staleEnd) staleEnd = lruEntry.n;
6158 link(lruEntry.n,lruEntry.p);
6160 delete lruHash[key];
6163 if (!(key in data)) return;
6172 * @name $cacheFactory.Cache#removeAll
6176 * Clears the cache object of any entries.
6178 removeAll: function() {
6181 lruHash = createMap();
6182 freshEnd = staleEnd = null;
6188 * @name $cacheFactory.Cache#destroy
6192 * Destroys the {@link $cacheFactory.Cache Cache} object entirely,
6193 * removing it from the {@link $cacheFactory $cacheFactory} set.
6195 destroy: function() {
6199 delete caches[cacheId];
6205 * @name $cacheFactory.Cache#info
6209 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}.
6211 * @returns {object} an object with the following properties:
6213 * <li>**id**: the id of the cache instance</li>
6214 * <li>**size**: the number of entries kept in the cache instance</li>
6215 * <li>**...**: any additional properties from the options object when creating the
6220 return extend({}, stats, {size: size});
6226 * makes the `entry` the freshEnd of the LRU linked list
6228 function refresh(entry) {
6229 if (entry != freshEnd) {
6232 } else if (staleEnd == entry) {
6236 link(entry.n, entry.p);
6237 link(entry, freshEnd);
6245 * bidirectionally links two entries of the LRU linked list
6247 function link(nextEntry, prevEntry) {
6248 if (nextEntry != prevEntry) {
6249 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify
6250 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify
6258 * @name $cacheFactory#info
6261 * Get information about all the caches that have been created
6263 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info`
6265 cacheFactory.info = function() {
6267 forEach(caches, function(cache, cacheId) {
6268 info[cacheId] = cache.info();
6276 * @name $cacheFactory#get
6279 * Get access to a cache object by the `cacheId` used when it was created.
6281 * @param {string} cacheId Name or id of a cache to access.
6282 * @returns {object} Cache object identified by the cacheId or undefined if no such cache.
6284 cacheFactory.get = function(cacheId) {
6285 return caches[cacheId];
6289 return cacheFactory;
6295 * @name $templateCache
6298 * The first time a template is used, it is loaded in the template cache for quick retrieval. You
6299 * can load templates directly into the cache in a `script` tag, or by consuming the
6300 * `$templateCache` service directly.
6302 * Adding via the `script` tag:
6305 * <script type="text/ng-template" id="templateId.html">
6306 * <p>This is the content of the template</p>
6310 * **Note:** the `script` tag containing the template does not need to be included in the `head` of
6311 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE,
6312 * element with ng-app attribute), otherwise the template will be ignored.
6314 * Adding via the `$templateCache` service:
6317 * var myApp = angular.module('myApp', []);
6318 * myApp.run(function($templateCache) {
6319 * $templateCache.put('templateId.html', 'This is the content of the template');
6323 * To retrieve the template later, simply use it in your HTML:
6325 * <div ng-include=" 'templateId.html' "></div>
6328 * or get it via Javascript:
6330 * $templateCache.get('templateId.html')
6333 * See {@link ng.$cacheFactory $cacheFactory}.
6336 function $TemplateCacheProvider() {
6337 this.$get = ['$cacheFactory', function($cacheFactory) {
6338 return $cacheFactory('templates');
6342 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
6343 * Any commits to this file should be reviewed with security in mind. *
6344 * Changes to this file can potentially create security vulnerabilities. *
6345 * An approval from 2 Core members with history of modifying *
6346 * this file is required. *
6348 * Does the change somehow allow for arbitrary javascript to be executed? *
6349 * Or allows for someone to change the prototype of built-in objects? *
6350 * Or gives undesired access to variables likes document or window? *
6351 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
6353 /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
6355 * DOM-related variables:
6357 * - "node" - DOM Node
6358 * - "element" - DOM Element or Node
6359 * - "$node" or "$element" - jqLite-wrapped node or element
6362 * Compiler related stuff:
6364 * - "linkFn" - linking fn of a single directive
6365 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node
6366 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node
6367 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList)
6377 * Compiles an HTML string or DOM into a template and produces a template function, which
6378 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together.
6380 * The compilation is a process of walking the DOM tree and matching DOM elements to
6381 * {@link ng.$compileProvider#directive directives}.
6383 * <div class="alert alert-warning">
6384 * **Note:** This document is an in-depth reference of all directive options.
6385 * For a gentle introduction to directives with examples of common use cases,
6386 * see the {@link guide/directive directive guide}.
6389 * ## Comprehensive Directive API
6391 * There are many different options for a directive.
6393 * The difference resides in the return value of the factory function.
6394 * You can either return a "Directive Definition Object" (see below) that defines the directive properties,
6395 * or just the `postLink` function (all other properties will have the default values).
6397 * <div class="alert alert-success">
6398 * **Best Practice:** It's recommended to use the "directive definition object" form.
6401 * Here's an example directive declared with a Directive Definition Object:
6404 * var myModule = angular.module(...);
6406 * myModule.directive('directiveName', function factory(injectables) {
6407 * var directiveDefinitionObject = {
6409 * template: '<div></div>', // or // function(tElement, tAttrs) { ... },
6411 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... },
6412 * transclude: false,
6414 * templateNamespace: 'html',
6416 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
6417 * controllerAs: 'stringIdentifier',
6418 * bindToController: false,
6419 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
6420 * compile: function compile(tElement, tAttrs, transclude) {
6422 * pre: function preLink(scope, iElement, iAttrs, controller) { ... },
6423 * post: function postLink(scope, iElement, iAttrs, controller) { ... }
6426 * // return function postLink( ... ) { ... }
6430 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... },
6431 * // post: function postLink(scope, iElement, iAttrs, controller) { ... }
6434 * // link: function postLink( ... ) { ... }
6436 * return directiveDefinitionObject;
6440 * <div class="alert alert-warning">
6441 * **Note:** Any unspecified options will use the default value. You can see the default values below.
6444 * Therefore the above can be simplified as:
6447 * var myModule = angular.module(...);
6449 * myModule.directive('directiveName', function factory(injectables) {
6450 * var directiveDefinitionObject = {
6451 * link: function postLink(scope, iElement, iAttrs) { ... }
6453 * return directiveDefinitionObject;
6455 * // return function postLink(scope, iElement, iAttrs) { ... }
6461 * ### Directive Definition Object
6463 * The directive definition object provides instructions to the {@link ng.$compile
6464 * compiler}. The attributes are:
6466 * #### `multiElement`
6467 * When this property is set to true, the HTML compiler will collect DOM nodes between
6468 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
6469 * together as the directive elements. It is recommended that this feature be used on directives
6470 * which are not strictly behavioural (such as {@link ngClick}), and which
6471 * do not manipulate or replace child nodes (such as {@link ngInclude}).
6474 * When there are multiple directives defined on a single DOM element, sometimes it
6475 * is necessary to specify the order in which the directives are applied. The `priority` is used
6476 * to sort the directives before their `compile` functions get called. Priority is defined as a
6477 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions
6478 * are also run in priority order, but post-link functions are run in reverse order. The order
6479 * of directives with the same priority is undefined. The default priority is `0`.
6482 * If set to true then the current `priority` will be the last set of directives
6483 * which will execute (any directives at the current priority will still execute
6484 * as the order of execution on same `priority` is undefined). Note that expressions
6485 * and other directives used in the directive's template will also be excluded from execution.
6488 * The scope property can be `true`, an object or a falsy value:
6490 * * **falsy:** No scope will be created for the directive. The directive will use its parent's scope.
6492 * * **`true`:** A new child scope that prototypically inherits from its parent will be created for
6493 * the directive's element. If multiple directives on the same element request a new scope,
6494 * only one new scope is created. The new scope rule does not apply for the root of the template
6495 * since the root of the template always gets a new scope.
6497 * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's element. The
6498 * 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent
6499 * scope. This is useful when creating reusable components, which should not accidentally read or modify
6500 * data in the parent scope.
6502 * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the
6503 * directive's element. These local properties are useful for aliasing values for templates. The keys in
6504 * the object hash map to the name of the property on the isolate scope; the values define how the property
6505 * is bound to the parent scope, via matching attributes on the directive's element:
6507 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
6508 * always a string since DOM attributes are strings. If no `attr` name is specified then the
6509 * attribute name is assumed to be the same as the local name.
6510 * Given `<widget my-attr="hello {{name}}">` and widget definition
6511 * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect
6512 * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the
6513 * `localName` property on the widget scope. The `name` is read from the parent scope (not
6516 * * `=` or `=attr` - set up bi-directional binding between a local scope property and the
6517 * parent scope property of name defined via the value of the `attr` attribute. If no `attr`
6518 * name is specified then the attribute name is assumed to be the same as the local name.
6519 * Given `<widget my-attr="parentModel">` and widget definition of
6520 * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the
6521 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
6522 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent
6523 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You
6524 * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If
6525 * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use
6526 * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional).
6528 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope.
6529 * If no `attr` name is specified then the attribute name is assumed to be the same as the
6530 * local name. Given `<widget my-attr="count = count + value">` and widget definition of
6531 * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to
6532 * a function wrapper for the `count = count + value` expression. Often it's desirable to
6533 * pass data from the isolated scope via an expression to the parent scope, this can be
6534 * done by passing a map of local variable names and values into the expression wrapper fn.
6535 * For example, if the expression is `increment(amount)` then we can specify the amount value
6536 * by calling the `localFn` as `localFn({amount: 22})`.
6538 * In general it's possible to apply more than one directive to one element, but there might be limitations
6539 * depending on the type of scope required by the directives. The following points will help explain these limitations.
6540 * For simplicity only two directives are taken into account, but it is also applicable for several directives:
6542 * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope
6543 * * **child scope** + **no scope** => Both directives will share one single child scope
6544 * * **child scope** + **child scope** => Both directives will share one single child scope
6545 * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use
6546 * its parent's scope
6547 * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot
6548 * be applied to the same element.
6549 * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives
6550 * cannot be applied to the same element.
6553 * #### `bindToController`
6554 * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will
6555 * allow a component to have its properties bound to the controller, rather than to scope. When the controller
6556 * is instantiated, the initial values of the isolate scope bindings are already available.
6559 * Controller constructor function. The controller is instantiated before the
6560 * pre-linking phase and can be accessed by other directives (see
6561 * `require` attribute). This allows the directives to communicate with each other and augment
6562 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals:
6564 * * `$scope` - Current scope associated with the element
6565 * * `$element` - Current element
6566 * * `$attrs` - Current attributes object for the element
6567 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope:
6568 * `function([scope], cloneLinkingFn, futureParentElement)`.
6569 * * `scope`: optional argument to override the scope.
6570 * * `cloneLinkingFn`: optional argument to create clones of the original transcluded content.
6571 * * `futureParentElement`:
6572 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements.
6573 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`.
6574 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements)
6575 * and when the `cloneLinkinFn` is passed,
6576 * as those elements need to created and cloned in a special way when they are defined outside their
6577 * usual containers (e.g. like `<svg>`).
6578 * * See also the `directive.templateNamespace` property.
6582 * Require another directive and inject its controller as the fourth argument to the linking function. The
6583 * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the
6584 * injected argument will be an array in corresponding order. If no such directive can be
6585 * found, or if the directive does not have a controller, then an error is raised (unless no link function
6586 * is specified, in which case error checking is skipped). The name can be prefixed with:
6588 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
6589 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
6590 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found.
6591 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found.
6592 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass
6593 * `null` to the `link` fn if not found.
6594 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass
6595 * `null` to the `link` fn if not found.
6598 * #### `controllerAs`
6599 * Identifier name for a reference to the controller in the directive's scope.
6600 * This allows the controller to be referenced from the directive template. This is especially
6601 * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible
6602 * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the
6603 * `controllerAs` reference might overwrite a property that already exists on the parent scope.
6607 * String of subset of `EACM` which restricts the directive to a specific directive
6608 * declaration style. If omitted, the defaults (elements and attributes) are used.
6610 * * `E` - Element name (default): `<my-directive></my-directive>`
6611 * * `A` - Attribute (default): `<div my-directive="exp"></div>`
6612 * * `C` - Class: `<div class="my-directive: exp;"></div>`
6613 * * `M` - Comment: `<!-- directive: my-directive exp -->`
6616 * #### `templateNamespace`
6617 * String representing the document type used by the markup in the template.
6618 * AngularJS needs this information as those elements need to be created and cloned
6619 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`.
6621 * * `html` - All root nodes in the template are HTML. Root nodes may also be
6622 * top-level elements such as `<svg>` or `<math>`.
6623 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`).
6624 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`).
6626 * If no `templateNamespace` is specified, then the namespace is considered to be `html`.
6629 * HTML markup that may:
6630 * * Replace the contents of the directive's element (default).
6631 * * Replace the directive's element itself (if `replace` is true - DEPRECATED).
6632 * * Wrap the contents of the directive's element (if `transclude` is true).
6636 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`.
6637 * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile`
6638 * function api below) and returns a string value.
6641 * #### `templateUrl`
6642 * This is similar to `template` but the template is loaded from the specified URL, asynchronously.
6644 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element
6645 * for later when the template has been resolved. In the meantime it will continue to compile and link
6646 * sibling and parent elements as though this element had not contained any directives.
6648 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this
6649 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the
6650 * case when only one deeply nested directive has `templateUrl`.
6652 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache}
6654 * You can specify `templateUrl` as a string representing the URL or as a function which takes two
6655 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns
6656 * a string value representing the url. In either case, the template URL is passed through {@link
6657 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}.
6660 * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0)
6661 * specify what the template should replace. Defaults to `false`.
6663 * * `true` - the template will replace the directive's element.
6664 * * `false` - the template will replace the contents of the directive's element.
6666 * The replacement process migrates all of the attributes / classes from the old element to the new
6667 * one. See the {@link guide/directive#template-expanding-directive
6668 * Directives Guide} for an example.
6670 * There are very few scenarios where element replacement is required for the application function,
6671 * the main one being reusable custom components that are used within SVG contexts
6672 * (because SVG doesn't work with custom elements in the DOM tree).
6675 * Extract the contents of the element where the directive appears and make it available to the directive.
6676 * The contents are compiled and provided to the directive as a **transclusion function**. See the
6677 * {@link $compile#transclusion Transclusion} section below.
6679 * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the
6680 * directive's element or the entire element:
6682 * * `true` - transclude the content (i.e. the child nodes) of the directive's element.
6683 * * `'element'` - transclude the whole of the directive's element including any directives on this
6684 * element that defined at a lower priority than this directive. When used, the `template`
6685 * property is ignored.
6691 * function compile(tElement, tAttrs, transclude) { ... }
6694 * The compile function deals with transforming the template DOM. Since most directives do not do
6695 * template transformation, it is not used often. The compile function takes the following arguments:
6697 * * `tElement` - template element - The element where the directive has been declared. It is
6698 * safe to do template transformation on the element and child elements only.
6700 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared
6701 * between all directive compile functions.
6703 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)`
6705 * <div class="alert alert-warning">
6706 * **Note:** The template instance and the link instance may be different objects if the template has
6707 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that
6708 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
6709 * should be done in a linking function rather than in a compile function.
6712 * <div class="alert alert-warning">
6713 * **Note:** The compile function cannot handle directives that recursively use themselves in their
6714 * own templates or compile functions. Compiling these directives results in an infinite loop and a
6715 * stack overflow errors.
6717 * This can be avoided by manually using $compile in the postLink function to imperatively compile
6718 * a directive's template instead of relying on automatic template compilation via `template` or
6719 * `templateUrl` declaration or manual compilation inside the compile function.
6722 * <div class="alert alert-danger">
6723 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it
6724 * e.g. does not know about the right outer scope. Please use the transclude function that is passed
6725 * to the link function instead.
6728 * A compile function can have a return value which can be either a function or an object.
6730 * * returning a (post-link) function - is equivalent to registering the linking function via the
6731 * `link` property of the config object when the compile function is empty.
6733 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to
6734 * control when a linking function should be called during the linking phase. See info about
6735 * pre-linking and post-linking functions below.
6739 * This property is used only if the `compile` property is not defined.
6742 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }
6745 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is
6746 * executed after the template has been cloned. This is where most of the directive logic will be
6749 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the
6750 * directive for registering {@link ng.$rootScope.Scope#$watch watches}.
6752 * * `iElement` - instance element - The element where the directive is to be used. It is safe to
6753 * manipulate the children of the element only in `postLink` function since the children have
6754 * already been linked.
6756 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
6757 * between all directive linking functions.
6759 * * `controller` - the directive's required controller instance(s) - Instances are shared
6760 * among all directives, which allows the directives to use the controllers as a communication
6761 * channel. The exact value depends on the directive's `require` property:
6762 * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one
6763 * * `string`: the controller instance
6764 * * `array`: array of controller instances
6766 * If a required controller cannot be found, and it is optional, the instance is `null`,
6767 * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown.
6769 * Note that you can also require the directive's own controller - it will be made available like
6770 * any other controller.
6772 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
6773 * This is the same as the `$transclude`
6774 * parameter of directive controllers, see there for details.
6775 * `function([scope], cloneLinkingFn, futureParentElement)`.
6777 * #### Pre-linking function
6779 * Executed before the child elements are linked. Not safe to do DOM transformation since the
6780 * compiler linking function will fail to locate the correct elements for linking.
6782 * #### Post-linking function
6784 * Executed after the child elements are linked.
6786 * Note that child elements that contain `templateUrl` directives will not have been compiled
6787 * and linked since they are waiting for their template to load asynchronously and their own
6788 * compilation and linking has been suspended until that occurs.
6790 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting
6791 * for their async templates to be resolved.
6796 * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and
6797 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS
6798 * scope from where they were taken.
6800 * Transclusion is used (often with {@link ngTransclude}) to insert the
6801 * original contents of a directive's element into a specified place in the template of the directive.
6802 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded
6803 * content has access to the properties on the scope from which it was taken, even if the directive
6804 * has isolated scope.
6805 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}.
6807 * This makes it possible for the widget to have private state for its template, while the transcluded
6808 * content has access to its originating scope.
6810 * <div class="alert alert-warning">
6811 * **Note:** When testing an element transclude directive you must not place the directive at the root of the
6812 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
6813 * Testing Transclusion Directives}.
6816 * #### Transclusion Functions
6818 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion
6819 * function** to the directive's `link` function and `controller`. This transclusion function is a special
6820 * **linking function** that will return the compiled contents linked to a new transclusion scope.
6822 * <div class="alert alert-info">
6823 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since
6824 * ngTransclude will deal with it for us.
6827 * If you want to manually control the insertion and removal of the transcluded content in your directive
6828 * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery
6829 * object that contains the compiled DOM, which is linked to the correct transclusion scope.
6831 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts
6832 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded
6833 * content and the `scope` is the newly created transclusion scope, to which the clone is bound.
6835 * <div class="alert alert-info">
6836 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function
6837 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope.
6840 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone
6841 * attach function**:
6844 * var transcludedContent, transclusionScope;
6846 * $transclude(function(clone, scope) {
6847 * element.append(clone);
6848 * transcludedContent = clone;
6849 * transclusionScope = scope;
6853 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the
6854 * associated transclusion scope:
6857 * transcludedContent.remove();
6858 * transclusionScope.$destroy();
6861 * <div class="alert alert-info">
6862 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive
6863 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it),
6864 * then you are also responsible for calling `$destroy` on the transclusion scope.
6867 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat}
6868 * automatically destroy their transluded clones as necessary so you do not need to worry about this if
6869 * you are simply using {@link ngTransclude} to inject the transclusion into your directive.
6872 * #### Transclusion Scopes
6874 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion
6875 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed
6876 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it
6879 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look
6885 * <div transclusion>
6891 * The `$parent` scope hierarchy will look like this:
6899 * but the scopes will inherit prototypically from different scopes to their `$parent`.
6910 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
6911 * `link()` or `compile()` functions. It has a variety of uses.
6913 * accessing *Normalized attribute names:*
6914 * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'.
6915 * the attributes object allows for normalized access to
6918 * * *Directive inter-communication:* All directives share the same instance of the attributes
6919 * object which allows the directives to use the attributes object as inter directive
6922 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object
6923 * allowing other directives to read the interpolated value.
6925 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes
6926 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also
6927 * the only way to easily get the actual value because during the linking phase the interpolation
6928 * hasn't been evaluated yet and so the value is at this time set to `undefined`.
6931 * function linkingFn(scope, elm, attrs, ctrl) {
6932 * // get the attribute value
6933 * console.log(attrs.ngModel);
6935 * // change the attribute
6936 * attrs.$set('ngModel', 'new value');
6938 * // observe changes to interpolated attribute
6939 * attrs.$observe('ngModel', function(value) {
6940 * console.log('ngModel has changed value to ' + value);
6947 * <div class="alert alert-warning">
6948 * **Note**: Typically directives are registered with `module.directive`. The example below is
6949 * to illustrate how `$compile` works.
6952 <example module="compileExample">
6953 <file name="index.html">
6955 angular.module('compileExample', [], function($compileProvider) {
6956 // configure new 'compile' directive by passing a directive
6957 // factory function. The factory function injects the '$compile'
6958 $compileProvider.directive('compile', function($compile) {
6959 // directive factory creates a link function
6960 return function(scope, element, attrs) {
6963 // watch the 'compile' expression for changes
6964 return scope.$eval(attrs.compile);
6967 // when the 'compile' expression changes
6968 // assign it into the current DOM
6969 element.html(value);
6971 // compile the new DOM and link it to the current
6973 // NOTE: we only compile .childNodes so that
6974 // we don't get into infinite loop compiling ourselves
6975 $compile(element.contents())(scope);
6981 .controller('GreeterController', ['$scope', function($scope) {
6982 $scope.name = 'Angular';
6983 $scope.html = 'Hello {{name}}';
6986 <div ng-controller="GreeterController">
6987 <input ng-model="name"> <br/>
6988 <textarea ng-model="html"></textarea> <br/>
6989 <div compile="html"></div>
6992 <file name="protractor.js" type="protractor">
6993 it('should auto compile', function() {
6994 var textarea = $('textarea');
6995 var output = $('div[compile]');
6996 // The initial state reads 'Hello Angular'.
6997 expect(output.getText()).toBe('Hello Angular');
6999 textarea.sendKeys('{{name}}!');
7000 expect(output.getText()).toBe('Angular!');
7007 * @param {string|DOMElement} element Element or HTML string to compile into a template function.
7008 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
7010 * <div class="alert alert-danger">
7011 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
7012 * e.g. will not use the right outer scope. Please pass the transclude function as a
7013 * `parentBoundTranscludeFn` to the link function instead.
7016 * @param {number} maxPriority only apply directives lower than given priority (Only effects the
7017 * root element(s), not their children)
7018 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template
7019 * (a DOM element/tree) to a scope. Where:
7021 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
7022 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
7023 * `template` and call the `cloneAttachFn` function allowing the caller to attach the
7024 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
7025 * called as: <br/> `cloneAttachFn(clonedElement, scope)` where:
7027 * * `clonedElement` - is a clone of the original `element` passed into the compiler.
7028 * * `scope` - is the current scope with which the linking function is working with.
7030 * * `options` - An optional object hash with linking options. If `options` is provided, then the following
7031 * keys may be used to control linking behavior:
7033 * * `parentBoundTranscludeFn` - the transclude function made available to
7034 * directives; if given, it will be passed through to the link functions of
7035 * directives found in `element` during compilation.
7036 * * `transcludeControllers` - an object hash with keys that map controller names
7037 * to controller instances; if given, it will make the controllers
7038 * available to directives.
7039 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add
7040 * the cloned elements; only needed for transcludes that are allowed to contain non html
7041 * elements (e.g. SVG elements). See also the directive.controller property.
7043 * Calling the linking function returns the element of the template. It is either the original
7044 * element passed in, or the clone of the element if the `cloneAttachFn` is provided.
7046 * After linking the view is not updated until after a call to $digest which typically is done by
7047 * Angular automatically.
7049 * If you need access to the bound view, there are two ways to do it:
7051 * - If you are not asking the linking function to clone the template, create the DOM element(s)
7052 * before you send them to the compiler and keep this reference around.
7054 * var element = $compile('<p>{{total}}</p>')(scope);
7057 * - if on the other hand, you need the element to be cloned, the view reference from the original
7058 * example would not point to the clone, but rather to the original template that was cloned. In
7059 * this case, you can access the clone via the cloneAttachFn:
7061 * var templateElement = angular.element('<p>{{total}}</p>'),
7064 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) {
7065 * //attach the clone to DOM document at the right place
7068 * //now we have reference to the cloned DOM via `clonedElement`
7072 * For information on how the compiler works, see the
7073 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
7076 var $compileMinErr = minErr('$compile');
7080 * @name $compileProvider
7084 $CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider'];
7085 function $CompileProvider($provide, $$sanitizeUriProvider) {
7086 var hasDirectives = {},
7087 Suffix = 'Directive',
7088 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/,
7089 CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/,
7090 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
7091 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
7093 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
7094 // The assumption is that future DOM event attribute names will begin with
7095 // 'on' and be composed of only English letters.
7096 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
7098 function parseIsolateBindings(scope, directiveName, isController) {
7099 var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/;
7103 forEach(scope, function(definition, scopeName) {
7104 var match = definition.match(LOCAL_REGEXP);
7107 throw $compileMinErr('iscp',
7108 "Invalid {3} for directive '{0}'." +
7109 " Definition: {... {1}: '{2}' ...}",
7110 directiveName, scopeName, definition,
7111 (isController ? "controller bindings definition" :
7112 "isolate scope definition"));
7115 bindings[scopeName] = {
7117 collection: match[2] === '*',
7118 optional: match[3] === '?',
7119 attrName: match[4] || scopeName
7126 function parseDirectiveBindings(directive, directiveName) {
7129 bindToController: null
7131 if (isObject(directive.scope)) {
7132 if (directive.bindToController === true) {
7133 bindings.bindToController = parseIsolateBindings(directive.scope,
7134 directiveName, true);
7135 bindings.isolateScope = {};
7137 bindings.isolateScope = parseIsolateBindings(directive.scope,
7138 directiveName, false);
7141 if (isObject(directive.bindToController)) {
7142 bindings.bindToController =
7143 parseIsolateBindings(directive.bindToController, directiveName, true);
7145 if (isObject(bindings.bindToController)) {
7146 var controller = directive.controller;
7147 var controllerAs = directive.controllerAs;
7149 // There is no controller, there may or may not be a controllerAs property
7150 throw $compileMinErr('noctrl',
7151 "Cannot bind to controller without directive '{0}'s controller.",
7153 } else if (!identifierForController(controller, controllerAs)) {
7154 // There is a controller, but no identifier or controllerAs property
7155 throw $compileMinErr('noident',
7156 "Cannot bind to controller without identifier for directive '{0}'.",
7163 function assertValidDirectiveName(name) {
7164 var letter = name.charAt(0);
7165 if (!letter || letter !== lowercase(letter)) {
7166 throw $compileMinErr('baddir', "Directive name '{0}' is invalid. The first character must be a lowercase letter", name);
7168 if (name !== name.trim()) {
7169 throw $compileMinErr('baddir',
7170 "Directive name '{0}' is invalid. The name should not contain leading or trailing whitespaces",
7177 * @name $compileProvider#directive
7181 * Register a new directive with the compiler.
7183 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which
7184 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the
7185 * names and the values are the factories.
7186 * @param {Function|Array} directiveFactory An injectable directive factory function. See
7187 * {@link guide/directive} for more info.
7188 * @returns {ng.$compileProvider} Self for chaining.
7190 this.directive = function registerDirective(name, directiveFactory) {
7191 assertNotHasOwnProperty(name, 'directive');
7192 if (isString(name)) {
7193 assertValidDirectiveName(name);
7194 assertArg(directiveFactory, 'directiveFactory');
7195 if (!hasDirectives.hasOwnProperty(name)) {
7196 hasDirectives[name] = [];
7197 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
7198 function($injector, $exceptionHandler) {
7199 var directives = [];
7200 forEach(hasDirectives[name], function(directiveFactory, index) {
7202 var directive = $injector.invoke(directiveFactory);
7203 if (isFunction(directive)) {
7204 directive = { compile: valueFn(directive) };
7205 } else if (!directive.compile && directive.link) {
7206 directive.compile = valueFn(directive.link);
7208 directive.priority = directive.priority || 0;
7209 directive.index = index;
7210 directive.name = directive.name || name;
7211 directive.require = directive.require || (directive.controller && directive.name);
7212 directive.restrict = directive.restrict || 'EA';
7213 var bindings = directive.$$bindings =
7214 parseDirectiveBindings(directive, directive.name);
7215 if (isObject(bindings.isolateScope)) {
7216 directive.$$isolateBindings = bindings.isolateScope;
7218 directive.$$moduleName = directiveFactory.$$moduleName;
7219 directives.push(directive);
7221 $exceptionHandler(e);
7227 hasDirectives[name].push(directiveFactory);
7229 forEach(name, reverseParams(registerDirective));
7237 * @name $compileProvider#aHrefSanitizationWhitelist
7241 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
7242 * urls during a[href] sanitization.
7244 * The sanitization is a security measure aimed at preventing XSS attacks via html links.
7246 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
7247 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
7248 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
7249 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
7251 * @param {RegExp=} regexp New regexp to whitelist urls with.
7252 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
7253 * chaining otherwise.
7255 this.aHrefSanitizationWhitelist = function(regexp) {
7256 if (isDefined(regexp)) {
7257 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp);
7260 return $$sanitizeUriProvider.aHrefSanitizationWhitelist();
7267 * @name $compileProvider#imgSrcSanitizationWhitelist
7271 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
7272 * urls during img[src] sanitization.
7274 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
7276 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
7277 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
7278 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
7279 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
7281 * @param {RegExp=} regexp New regexp to whitelist urls with.
7282 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
7283 * chaining otherwise.
7285 this.imgSrcSanitizationWhitelist = function(regexp) {
7286 if (isDefined(regexp)) {
7287 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp);
7290 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist();
7296 * @name $compileProvider#debugInfoEnabled
7298 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the
7299 * current debugInfoEnabled state
7300 * @returns {*} current value if used as getter or itself (chaining) if used as setter
7305 * Call this method to enable/disable various debug runtime information in the compiler such as adding
7306 * binding information and a reference to the current scope on to DOM elements.
7307 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope
7308 * * `ng-binding` CSS class
7309 * * `$binding` data property containing an array of the binding expressions
7311 * You may want to disable this in production for a significant performance boost. See
7312 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more.
7314 * The default value is true.
7316 var debugInfoEnabled = true;
7317 this.debugInfoEnabled = function(enabled) {
7318 if (isDefined(enabled)) {
7319 debugInfoEnabled = enabled;
7322 return debugInfoEnabled;
7326 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
7327 '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri',
7328 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse,
7329 $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) {
7331 var Attributes = function(element, attributesToCopy) {
7332 if (attributesToCopy) {
7333 var keys = Object.keys(attributesToCopy);
7336 for (i = 0, l = keys.length; i < l; i++) {
7338 this[key] = attributesToCopy[key];
7344 this.$$element = element;
7347 Attributes.prototype = {
7350 * @name $compile.directive.Attributes#$normalize
7354 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
7355 * `data-`) to its normalized, camelCase form.
7357 * Also there is special case for Moz prefix starting with upper case letter.
7359 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
7361 * @param {string} name Name to normalize
7363 $normalize: directiveNormalize,
7368 * @name $compile.directive.Attributes#$addClass
7372 * Adds the CSS class value specified by the classVal parameter to the element. If animations
7373 * are enabled then an animation will be triggered for the class addition.
7375 * @param {string} classVal The className value that will be added to the element
7377 $addClass: function(classVal) {
7378 if (classVal && classVal.length > 0) {
7379 $animate.addClass(this.$$element, classVal);
7385 * @name $compile.directive.Attributes#$removeClass
7389 * Removes the CSS class value specified by the classVal parameter from the element. If
7390 * animations are enabled then an animation will be triggered for the class removal.
7392 * @param {string} classVal The className value that will be removed from the element
7394 $removeClass: function(classVal) {
7395 if (classVal && classVal.length > 0) {
7396 $animate.removeClass(this.$$element, classVal);
7402 * @name $compile.directive.Attributes#$updateClass
7406 * Adds and removes the appropriate CSS class values to the element based on the difference
7407 * between the new and old CSS class values (specified as newClasses and oldClasses).
7409 * @param {string} newClasses The current CSS className value
7410 * @param {string} oldClasses The former CSS className value
7412 $updateClass: function(newClasses, oldClasses) {
7413 var toAdd = tokenDifference(newClasses, oldClasses);
7414 if (toAdd && toAdd.length) {
7415 $animate.addClass(this.$$element, toAdd);
7418 var toRemove = tokenDifference(oldClasses, newClasses);
7419 if (toRemove && toRemove.length) {
7420 $animate.removeClass(this.$$element, toRemove);
7425 * Set a normalized attribute on the element in a way such that all directives
7426 * can share the attribute. This function properly handles boolean attributes.
7427 * @param {string} key Normalized key. (ie ngAttribute)
7428 * @param {string|boolean} value The value to set. If `null` attribute will be deleted.
7429 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
7431 * @param {string=} attrName Optional none normalized name. Defaults to key.
7433 $set: function(key, value, writeAttr, attrName) {
7434 // TODO: decide whether or not to throw an error if "class"
7435 //is set through this function since it may cause $updateClass to
7438 var node = this.$$element[0],
7439 booleanKey = getBooleanAttrName(node, key),
7440 aliasedKey = getAliasedAttrName(key),
7445 this.$$element.prop(key, value);
7446 attrName = booleanKey;
7447 } else if (aliasedKey) {
7448 this[aliasedKey] = value;
7449 observer = aliasedKey;
7454 // translate normalized key to actual key
7456 this.$attr[key] = attrName;
7458 attrName = this.$attr[key];
7460 this.$attr[key] = attrName = snake_case(key, '-');
7464 nodeName = nodeName_(this.$$element);
7466 if ((nodeName === 'a' && key === 'href') ||
7467 (nodeName === 'img' && key === 'src')) {
7468 // sanitize a[href] and img[src] values
7469 this[key] = value = $$sanitizeUri(value, key === 'src');
7470 } else if (nodeName === 'img' && key === 'srcset') {
7471 // sanitize img[srcset] values
7474 // first check if there are spaces because it's not the same pattern
7475 var trimmedSrcset = trim(value);
7476 // ( 999x ,| 999w ,| ,|, )
7477 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
7478 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
7480 // split srcset into tuple of uri and descriptor except for the last item
7481 var rawUris = trimmedSrcset.split(pattern);
7484 var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
7485 for (var i = 0; i < nbrUrisWith2parts; i++) {
7486 var innerIdx = i * 2;
7488 result += $$sanitizeUri(trim(rawUris[innerIdx]), true);
7489 // add the descriptor
7490 result += (" " + trim(rawUris[innerIdx + 1]));
7493 // split the last item into uri and descriptor
7494 var lastTuple = trim(rawUris[i * 2]).split(/\s/);
7496 // sanitize the last uri
7497 result += $$sanitizeUri(trim(lastTuple[0]), true);
7499 // and add the last descriptor if any
7500 if (lastTuple.length === 2) {
7501 result += (" " + trim(lastTuple[1]));
7503 this[key] = value = result;
7506 if (writeAttr !== false) {
7507 if (value === null || isUndefined(value)) {
7508 this.$$element.removeAttr(attrName);
7510 this.$$element.attr(attrName, value);
7515 var $$observers = this.$$observers;
7516 $$observers && forEach($$observers[observer], function(fn) {
7520 $exceptionHandler(e);
7528 * @name $compile.directive.Attributes#$observe
7532 * Observes an interpolated attribute.
7534 * The observer function will be invoked once during the next `$digest` following
7535 * compilation. The observer is then invoked whenever the interpolated value
7538 * @param {string} key Normalized key. (ie ngAttribute) .
7539 * @param {function(interpolatedValue)} fn Function that will be called whenever
7540 the interpolated value of the attribute changes.
7541 * See the {@link guide/directive#text-and-attribute-bindings Directives} guide for more info.
7542 * @returns {function()} Returns a deregistration function for this observer.
7544 $observe: function(key, fn) {
7546 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())),
7547 listeners = ($$observers[key] || ($$observers[key] = []));
7550 $rootScope.$evalAsync(function() {
7551 if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) {
7552 // no one registered attribute interpolation function, so lets call it manually
7558 arrayRemove(listeners, fn);
7564 function safeAddClass($element, className) {
7566 $element.addClass(className);
7568 // ignore, since it means that we are trying to set class on
7569 // SVG element, where class name is read-only.
7574 var startSymbol = $interpolate.startSymbol(),
7575 endSymbol = $interpolate.endSymbol(),
7576 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
7578 : function denormalizeTemplate(template) {
7579 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
7581 NG_ATTR_BINDING = /^ngAttr[A-Z]/;
7582 var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/;
7584 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) {
7585 var bindings = $element.data('$binding') || [];
7587 if (isArray(binding)) {
7588 bindings = bindings.concat(binding);
7590 bindings.push(binding);
7593 $element.data('$binding', bindings);
7596 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) {
7597 safeAddClass($element, 'ng-binding');
7600 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) {
7601 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope';
7602 $element.data(dataName, scope);
7605 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) {
7606 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope');
7611 //================================
7613 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective,
7614 previousCompileContext) {
7615 if (!($compileNodes instanceof jqLite)) {
7616 // jquery always rewraps, whereas we need to preserve the original selector so that we can
7618 $compileNodes = jqLite($compileNodes);
7620 // We can not compile top level text elements since text nodes can be merged and we will
7621 // not be able to attach scope data to them, so we will wrap them in <span>
7622 forEach($compileNodes, function(node, index) {
7623 if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) {
7624 $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
7627 var compositeLinkFn =
7628 compileNodes($compileNodes, transcludeFn, $compileNodes,
7629 maxPriority, ignoreDirective, previousCompileContext);
7630 compile.$$addScopeClass($compileNodes);
7631 var namespace = null;
7632 return function publicLinkFn(scope, cloneConnectFn, options) {
7633 assertArg(scope, 'scope');
7635 if (previousCompileContext && previousCompileContext.needsNewScope) {
7636 // A parent directive did a replace and a directive on this element asked
7637 // for transclusion, which caused us to lose a layer of element on which
7638 // we could hold the new transclusion scope, so we will create it manually
7640 scope = scope.$parent.$new();
7643 options = options || {};
7644 var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
7645 transcludeControllers = options.transcludeControllers,
7646 futureParentElement = options.futureParentElement;
7648 // When `parentBoundTranscludeFn` is passed, it is a
7649 // `controllersBoundTransclude` function (it was previously passed
7650 // as `transclude` to directive.link) so we must unwrap it to get
7651 // its `boundTranscludeFn`
7652 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) {
7653 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;
7657 namespace = detectNamespaceForChildElements(futureParentElement);
7660 if (namespace !== 'html') {
7661 // When using a directive with replace:true and templateUrl the $compileNodes
7662 // (or a child element inside of them)
7663 // might change, so we need to recreate the namespace adapted compileNodes
7664 // for call to the link function.
7665 // Note: This will already clone the nodes...
7667 wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html())
7669 } else if (cloneConnectFn) {
7670 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
7671 // and sometimes changes the structure of the DOM.
7672 $linkNode = JQLitePrototype.clone.call($compileNodes);
7674 $linkNode = $compileNodes;
7677 if (transcludeControllers) {
7678 for (var controllerName in transcludeControllers) {
7679 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance);
7683 compile.$$addScopeInfo($linkNode, scope);
7685 if (cloneConnectFn) cloneConnectFn($linkNode, scope);
7686 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);
7691 function detectNamespaceForChildElements(parentElement) {
7692 // TODO: Make this detect MathML as well...
7693 var node = parentElement && parentElement[0];
7697 return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html';
7702 * Compile function matches each node in nodeList against the directives. Once all directives
7703 * for a particular node are collected their compile functions are executed. The compile
7704 * functions return values - the linking functions - are combined into a composite linking
7705 * function, which is the a linking function for the node.
7707 * @param {NodeList} nodeList an array of nodes or NodeList to compile
7708 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
7709 * scope argument is auto-generated to the new child of the transcluded parent scope.
7710 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then
7711 * the rootElement must be set the jqLite collection of the compile root. This is
7712 * needed so that the jqLite collection items can be replaced with widgets.
7713 * @param {number=} maxPriority Max directive priority.
7714 * @returns {Function} A composite linking function of all of the matched directives or null.
7716 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective,
7717 previousCompileContext) {
7719 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound;
7721 for (var i = 0; i < nodeList.length; i++) {
7722 attrs = new Attributes();
7724 // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
7725 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined,
7728 nodeLinkFn = (directives.length)
7729 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement,
7730 null, [], [], previousCompileContext)
7733 if (nodeLinkFn && nodeLinkFn.scope) {
7734 compile.$$addScopeClass(attrs.$$element);
7737 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal ||
7738 !(childNodes = nodeList[i].childNodes) ||
7741 : compileNodes(childNodes,
7743 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement)
7744 && nodeLinkFn.transclude) : transcludeFn);
7746 if (nodeLinkFn || childLinkFn) {
7747 linkFns.push(i, nodeLinkFn, childLinkFn);
7749 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn;
7752 //use the previous context only for the first element in the virtual group
7753 previousCompileContext = null;
7756 // return a linking function if we have found anything, null otherwise
7757 return linkFnFound ? compositeLinkFn : null;
7759 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) {
7760 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn;
7764 if (nodeLinkFnFound) {
7765 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our
7766 // offsets don't get screwed up
7767 var nodeListLength = nodeList.length;
7768 stableNodeList = new Array(nodeListLength);
7770 // create a sparse array by only copying the elements which have a linkFn
7771 for (i = 0; i < linkFns.length; i+=3) {
7773 stableNodeList[idx] = nodeList[idx];
7776 stableNodeList = nodeList;
7779 for (i = 0, ii = linkFns.length; i < ii;) {
7780 node = stableNodeList[linkFns[i++]];
7781 nodeLinkFn = linkFns[i++];
7782 childLinkFn = linkFns[i++];
7785 if (nodeLinkFn.scope) {
7786 childScope = scope.$new();
7787 compile.$$addScopeInfo(jqLite(node), childScope);
7792 if (nodeLinkFn.transcludeOnThisElement) {
7793 childBoundTranscludeFn = createBoundTranscludeFn(
7794 scope, nodeLinkFn.transclude, parentBoundTranscludeFn);
7796 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) {
7797 childBoundTranscludeFn = parentBoundTranscludeFn;
7799 } else if (!parentBoundTranscludeFn && transcludeFn) {
7800 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn);
7803 childBoundTranscludeFn = null;
7806 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
7808 } else if (childLinkFn) {
7809 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
7815 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) {
7817 var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
7819 if (!transcludedScope) {
7820 transcludedScope = scope.$new(false, containingScope);
7821 transcludedScope.$$transcluded = true;
7824 return transcludeFn(transcludedScope, cloneFn, {
7825 parentBoundTranscludeFn: previousBoundTranscludeFn,
7826 transcludeControllers: controllers,
7827 futureParentElement: futureParentElement
7831 return boundTranscludeFn;
7835 * Looks for directives on the given node and adds them to the directive collection which is
7838 * @param node Node to search.
7839 * @param directives An array to which the directives are added to. This array is sorted before
7840 * the function returns.
7841 * @param attrs The shared attrs object which is used to populate the normalized attributes.
7842 * @param {number=} maxPriority Max directive priority.
7844 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
7845 var nodeType = node.nodeType,
7846 attrsMap = attrs.$attr,
7851 case NODE_TYPE_ELEMENT: /* Element */
7852 // use the node name: <directive>
7853 addDirective(directives,
7854 directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective);
7856 // iterate over the attributes
7857 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes,
7858 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
7859 var attrStartName = false;
7860 var attrEndName = false;
7864 value = trim(attr.value);
7866 // support ngAttr attribute binding
7867 ngAttrName = directiveNormalize(name);
7868 if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
7869 name = name.replace(PREFIX_REGEXP, '')
7870 .substr(8).replace(/_(.)/g, function(match, letter) {
7871 return letter.toUpperCase();
7875 var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE);
7876 if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) {
7877 attrStartName = name;
7878 attrEndName = name.substr(0, name.length - 5) + 'end';
7879 name = name.substr(0, name.length - 6);
7882 nName = directiveNormalize(name.toLowerCase());
7883 attrsMap[nName] = name;
7884 if (isNgAttr || !attrs.hasOwnProperty(nName)) {
7885 attrs[nName] = value;
7886 if (getBooleanAttrName(node, nName)) {
7887 attrs[nName] = true; // presence means true
7890 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
7891 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
7895 // use class as directive
7896 className = node.className;
7897 if (isObject(className)) {
7898 // Maybe SVGAnimatedString
7899 className = className.animVal;
7901 if (isString(className) && className !== '') {
7902 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
7903 nName = directiveNormalize(match[2]);
7904 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) {
7905 attrs[nName] = trim(match[3]);
7907 className = className.substr(match.index + match[0].length);
7911 case NODE_TYPE_TEXT: /* Text Node */
7913 // Workaround for #11781
7914 while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) {
7915 node.nodeValue = node.nodeValue + node.nextSibling.nodeValue;
7916 node.parentNode.removeChild(node.nextSibling);
7919 addTextInterpolateDirective(directives, node.nodeValue);
7921 case NODE_TYPE_COMMENT: /* Comment */
7923 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
7925 nName = directiveNormalize(match[1]);
7926 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) {
7927 attrs[nName] = trim(match[2]);
7931 // turns out that under some circumstances IE9 throws errors when one attempts to read
7932 // comment's node value.
7933 // Just ignore it and continue. (Can't seem to reproduce in test case.)
7938 directives.sort(byPriority);
7943 * Given a node with an directive-start it collects all of the siblings until it finds
7950 function groupScan(node, attrStart, attrEnd) {
7953 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
7956 throw $compileMinErr('uterdir',
7957 "Unterminated attribute, found '{0}' but no matching '{1}' found.",
7958 attrStart, attrEnd);
7960 if (node.nodeType == NODE_TYPE_ELEMENT) {
7961 if (node.hasAttribute(attrStart)) depth++;
7962 if (node.hasAttribute(attrEnd)) depth--;
7965 node = node.nextSibling;
7966 } while (depth > 0);
7971 return jqLite(nodes);
7975 * Wrapper for linking function which converts normal linking function into a grouped
7980 * @returns {Function}
7982 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
7983 return function(scope, element, attrs, controllers, transcludeFn) {
7984 element = groupScan(element[0], attrStart, attrEnd);
7985 return linkFn(scope, element, attrs, controllers, transcludeFn);
7990 * Once the directives have been collected, their compile functions are executed. This method
7991 * is responsible for inlining directive templates as well as terminating the application
7992 * of the directives if the terminal directive has been reached.
7994 * @param {Array} directives Array of collected directives to execute their compile function.
7995 * this needs to be pre-sorted by priority order.
7996 * @param {Node} compileNode The raw DOM node to apply the compile functions to
7997 * @param {Object} templateAttrs The shared attribute function
7998 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
7999 * scope argument is auto-generated to the new
8000 * child of the transcluded parent scope.
8001 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this
8002 * argument has the root jqLite array so that we can replace nodes
8004 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when
8005 * compiling the transclusion.
8006 * @param {Array.<Function>} preLinkFns
8007 * @param {Array.<Function>} postLinkFns
8008 * @param {Object} previousCompileContext Context used for previous compilation of the current
8010 * @returns {Function} linkFn
8012 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn,
8013 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns,
8014 previousCompileContext) {
8015 previousCompileContext = previousCompileContext || {};
8017 var terminalPriority = -Number.MAX_VALUE,
8018 newScopeDirective = previousCompileContext.newScopeDirective,
8019 controllerDirectives = previousCompileContext.controllerDirectives,
8020 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,
8021 templateDirective = previousCompileContext.templateDirective,
8022 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
8023 hasTranscludeDirective = false,
8024 hasTemplate = false,
8025 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective,
8026 $compileNode = templateAttrs.$$element = jqLite(compileNode),
8030 replaceDirective = originalReplaceDirective,
8031 childTranscludeFn = transcludeFn,
8035 // executes all directives on the current element
8036 for (var i = 0, ii = directives.length; i < ii; i++) {
8037 directive = directives[i];
8038 var attrStart = directive.$$start;
8039 var attrEnd = directive.$$end;
8041 // collect multiblock sections
8043 $compileNode = groupScan(compileNode, attrStart, attrEnd);
8045 $template = undefined;
8047 if (terminalPriority > directive.priority) {
8048 break; // prevent further processing of directives
8051 if (directiveValue = directive.scope) {
8053 // skip the check for directives with async templates, we'll check the derived sync
8054 // directive when the template arrives
8055 if (!directive.templateUrl) {
8056 if (isObject(directiveValue)) {
8057 // This directive is trying to add an isolated scope.
8058 // Check that there is no scope of any kind already
8059 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective,
8060 directive, $compileNode);
8061 newIsolateScopeDirective = directive;
8063 // This directive is trying to add a child scope.
8064 // Check that there is no isolated scope already
8065 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive,
8070 newScopeDirective = newScopeDirective || directive;
8073 directiveName = directive.name;
8075 if (!directive.templateUrl && directive.controller) {
8076 directiveValue = directive.controller;
8077 controllerDirectives = controllerDirectives || createMap();
8078 assertNoDuplicate("'" + directiveName + "' controller",
8079 controllerDirectives[directiveName], directive, $compileNode);
8080 controllerDirectives[directiveName] = directive;
8083 if (directiveValue = directive.transclude) {
8084 hasTranscludeDirective = true;
8086 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion.
8087 // This option should only be used by directives that know how to safely handle element transclusion,
8088 // where the transcluded nodes are added or replaced after linking.
8089 if (!directive.$$tlb) {
8090 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode);
8091 nonTlbTranscludeDirective = directive;
8094 if (directiveValue == 'element') {
8095 hasElementTranscludeDirective = true;
8096 terminalPriority = directive.priority;
8097 $template = $compileNode;
8098 $compileNode = templateAttrs.$$element =
8099 jqLite(document.createComment(' ' + directiveName + ': ' +
8100 templateAttrs[directiveName] + ' '));
8101 compileNode = $compileNode[0];
8102 replaceWith(jqCollection, sliceArgs($template), compileNode);
8104 childTranscludeFn = compile($template, transcludeFn, terminalPriority,
8105 replaceDirective && replaceDirective.name, {
8107 // - controllerDirectives - otherwise we'll create duplicates controllers
8108 // - newIsolateScopeDirective or templateDirective - combining templates with
8109 // element transclusion doesn't make sense.
8111 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion
8112 // on the same element more than once.
8113 nonTlbTranscludeDirective: nonTlbTranscludeDirective
8116 $template = jqLite(jqLiteClone(compileNode)).contents();
8117 $compileNode.empty(); // clear contents
8118 childTranscludeFn = compile($template, transcludeFn, undefined,
8119 undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope});
8123 if (directive.template) {
8125 assertNoDuplicate('template', templateDirective, directive, $compileNode);
8126 templateDirective = directive;
8128 directiveValue = (isFunction(directive.template))
8129 ? directive.template($compileNode, templateAttrs)
8130 : directive.template;
8132 directiveValue = denormalizeTemplate(directiveValue);
8134 if (directive.replace) {
8135 replaceDirective = directive;
8136 if (jqLiteIsTextNode(directiveValue)) {
8139 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
8141 compileNode = $template[0];
8143 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
8144 throw $compileMinErr('tplrt',
8145 "Template for directive '{0}' must have exactly one root element. {1}",
8149 replaceWith(jqCollection, $compileNode, compileNode);
8151 var newTemplateAttrs = {$attr: {}};
8153 // combine directives from the original node and from the template:
8154 // - take the array of directives for this element
8155 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed)
8156 // - collect directives from the template and sort them by priority
8157 // - combine directives as: processed + template + unprocessed
8158 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs);
8159 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1));
8161 if (newIsolateScopeDirective || newScopeDirective) {
8162 // The original directive caused the current element to be replaced but this element
8163 // also needs to have a new scope, so we need to tell the template directives
8164 // that they would need to get their scope from further up, if they require transclusion
8165 markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective);
8167 directives = directives.concat(templateDirectives).concat(unprocessedDirectives);
8168 mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
8170 ii = directives.length;
8172 $compileNode.html(directiveValue);
8176 if (directive.templateUrl) {
8178 assertNoDuplicate('template', templateDirective, directive, $compileNode);
8179 templateDirective = directive;
8181 if (directive.replace) {
8182 replaceDirective = directive;
8185 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
8186 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, {
8187 controllerDirectives: controllerDirectives,
8188 newScopeDirective: (newScopeDirective !== directive) && newScopeDirective,
8189 newIsolateScopeDirective: newIsolateScopeDirective,
8190 templateDirective: templateDirective,
8191 nonTlbTranscludeDirective: nonTlbTranscludeDirective
8193 ii = directives.length;
8194 } else if (directive.compile) {
8196 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
8197 if (isFunction(linkFn)) {
8198 addLinkFns(null, linkFn, attrStart, attrEnd);
8199 } else if (linkFn) {
8200 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd);
8203 $exceptionHandler(e, startingTag($compileNode));
8207 if (directive.terminal) {
8208 nodeLinkFn.terminal = true;
8209 terminalPriority = Math.max(terminalPriority, directive.priority);
8214 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
8215 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective;
8216 nodeLinkFn.templateOnThisElement = hasTemplate;
8217 nodeLinkFn.transclude = childTranscludeFn;
8219 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective;
8221 // might be normal or delayed nodeLinkFn depending on if templateUrl is present
8224 ////////////////////
8226 function addLinkFns(pre, post, attrStart, attrEnd) {
8228 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd);
8229 pre.require = directive.require;
8230 pre.directiveName = directiveName;
8231 if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
8232 pre = cloneAndAnnotateFn(pre, {isolateScope: true});
8234 preLinkFns.push(pre);
8237 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd);
8238 post.require = directive.require;
8239 post.directiveName = directiveName;
8240 if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
8241 post = cloneAndAnnotateFn(post, {isolateScope: true});
8243 postLinkFns.push(post);
8248 function getControllers(directiveName, require, $element, elementControllers) {
8251 if (isString(require)) {
8252 var match = require.match(REQUIRE_PREFIX_REGEXP);
8253 var name = require.substring(match[0].length);
8254 var inheritType = match[1] || match[3];
8255 var optional = match[2] === '?';
8257 //If only parents then start at the parent element
8258 if (inheritType === '^^') {
8259 $element = $element.parent();
8260 //Otherwise attempt getting the controller from elementControllers in case
8261 //the element is transcluded (and has no data) and to avoid .data if possible
8263 value = elementControllers && elementControllers[name];
8264 value = value && value.instance;
8268 var dataName = '$' + name + 'Controller';
8269 value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName);
8272 if (!value && !optional) {
8273 throw $compileMinErr('ctreq',
8274 "Controller '{0}', required by directive '{1}', can't be found!",
8275 name, directiveName);
8277 } else if (isArray(require)) {
8279 for (var i = 0, ii = require.length; i < ii; i++) {
8280 value[i] = getControllers(directiveName, require[i], $element, elementControllers);
8284 return value || null;
8287 function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope) {
8288 var elementControllers = createMap();
8289 for (var controllerKey in controllerDirectives) {
8290 var directive = controllerDirectives[controllerKey];
8292 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
8295 $transclude: transcludeFn
8298 var controller = directive.controller;
8299 if (controller == '@') {
8300 controller = attrs[directive.name];
8303 var controllerInstance = $controller(controller, locals, true, directive.controllerAs);
8305 // For directives with element transclusion the element is a comment,
8306 // but jQuery .data doesn't support attaching data to comment nodes as it's hard to
8307 // clean up (http://bugs.jquery.com/ticket/8335).
8308 // Instead, we save the controllers for the element in a local hash and attach to .data
8309 // later, once we have the actual element.
8310 elementControllers[directive.name] = controllerInstance;
8311 if (!hasElementTranscludeDirective) {
8312 $element.data('$' + directive.name + 'Controller', controllerInstance.instance);
8315 return elementControllers;
8318 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
8319 var linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element,
8320 attrs, removeScopeBindingWatches, removeControllerBindingWatches;
8322 if (compileNode === linkNode) {
8323 attrs = templateAttrs;
8324 $element = templateAttrs.$$element;
8326 $element = jqLite(linkNode);
8327 attrs = new Attributes($element, templateAttrs);
8330 controllerScope = scope;
8331 if (newIsolateScopeDirective) {
8332 isolateScope = scope.$new(true);
8333 } else if (newScopeDirective) {
8334 controllerScope = scope.$parent;
8337 if (boundTranscludeFn) {
8338 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
8339 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
8340 transcludeFn = controllersBoundTransclude;
8341 transcludeFn.$$boundTransclude = boundTranscludeFn;
8344 if (controllerDirectives) {
8345 elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope);
8348 if (newIsolateScopeDirective) {
8349 // Initialize isolate scope bindings for new isolate scope directive.
8350 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
8351 templateDirective === newIsolateScopeDirective.$$originalDirective)));
8352 compile.$$addScopeClass($element, true);
8353 isolateScope.$$isolateBindings =
8354 newIsolateScopeDirective.$$isolateBindings;
8355 removeScopeBindingWatches = initializeDirectiveBindings(scope, attrs, isolateScope,
8356 isolateScope.$$isolateBindings,
8357 newIsolateScopeDirective);
8358 if (removeScopeBindingWatches) {
8359 isolateScope.$on('$destroy', removeScopeBindingWatches);
8363 // Initialize bindToController bindings
8364 for (var name in elementControllers) {
8365 var controllerDirective = controllerDirectives[name];
8366 var controller = elementControllers[name];
8367 var bindings = controllerDirective.$$bindings.bindToController;
8369 if (controller.identifier && bindings) {
8370 removeControllerBindingWatches =
8371 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
8374 var controllerResult = controller();
8375 if (controllerResult !== controller.instance) {
8376 // If the controller constructor has a return value, overwrite the instance
8377 // from setupControllers
8378 controller.instance = controllerResult;
8379 $element.data('$' + controllerDirective.name + 'Controller', controllerResult);
8380 removeControllerBindingWatches && removeControllerBindingWatches();
8381 removeControllerBindingWatches =
8382 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
8387 for (i = 0, ii = preLinkFns.length; i < ii; i++) {
8388 linkFn = preLinkFns[i];
8389 invokeLinkFn(linkFn,
8390 linkFn.isolateScope ? isolateScope : scope,
8393 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
8399 // We only pass the isolate scope, if the isolate directive has a template,
8400 // otherwise the child elements do not belong to the isolate directive.
8401 var scopeToChild = scope;
8402 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) {
8403 scopeToChild = isolateScope;
8405 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
8408 for (i = postLinkFns.length - 1; i >= 0; i--) {
8409 linkFn = postLinkFns[i];
8410 invokeLinkFn(linkFn,
8411 linkFn.isolateScope ? isolateScope : scope,
8414 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
8419 // This is the function that is injected as `$transclude`.
8420 // Note: all arguments are optional!
8421 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) {
8422 var transcludeControllers;
8424 // No scope passed in:
8425 if (!isScope(scope)) {
8426 futureParentElement = cloneAttachFn;
8427 cloneAttachFn = scope;
8431 if (hasElementTranscludeDirective) {
8432 transcludeControllers = elementControllers;
8434 if (!futureParentElement) {
8435 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
8437 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
8442 // Depending upon the context in which a directive finds itself it might need to have a new isolated
8443 // or child scope created. For instance:
8444 // * if the directive has been pulled into a template because another directive with a higher priority
8445 // asked for element transclusion
8446 // * if the directive itself asks for transclusion but it is at the root of a template and the original
8447 // element was replaced. See https://github.com/angular/angular.js/issues/12936
8448 function markDirectiveScope(directives, isolateScope, newScope) {
8449 for (var j = 0, jj = directives.length; j < jj; j++) {
8450 directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope});
8455 * looks up the directive and decorates it with exception handling and proper parameters. We
8456 * call this the boundDirective.
8458 * @param {string} name name of the directive to look up.
8459 * @param {string} location The directive must be found in specific format.
8460 * String containing any of theses characters:
8462 * * `E`: element name
8466 * @returns {boolean} true if directive was added.
8468 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName,
8470 if (name === ignoreDirective) return null;
8472 if (hasDirectives.hasOwnProperty(name)) {
8473 for (var directive, directives = $injector.get(name + Suffix),
8474 i = 0, ii = directives.length; i < ii; i++) {
8476 directive = directives[i];
8477 if ((isUndefined(maxPriority) || maxPriority > directive.priority) &&
8478 directive.restrict.indexOf(location) != -1) {
8479 if (startAttrName) {
8480 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
8482 tDirectives.push(directive);
8485 } catch (e) { $exceptionHandler(e); }
8493 * looks up the directive and returns true if it is a multi-element directive,
8494 * and therefore requires DOM nodes between -start and -end markers to be grouped
8497 * @param {string} name name of the directive to look up.
8498 * @returns true if directive was registered as multi-element.
8500 function directiveIsMultiElement(name) {
8501 if (hasDirectives.hasOwnProperty(name)) {
8502 for (var directive, directives = $injector.get(name + Suffix),
8503 i = 0, ii = directives.length; i < ii; i++) {
8504 directive = directives[i];
8505 if (directive.multiElement) {
8514 * When the element is replaced with HTML template then the new attributes
8515 * on the template need to be merged with the existing attributes in the DOM.
8516 * The desired effect is to have both of the attributes present.
8518 * @param {object} dst destination attributes (original DOM)
8519 * @param {object} src source attributes (from the directive template)
8521 function mergeTemplateAttributes(dst, src) {
8522 var srcAttr = src.$attr,
8523 dstAttr = dst.$attr,
8524 $element = dst.$$element;
8526 // reapply the old attributes to the new element
8527 forEach(dst, function(value, key) {
8528 if (key.charAt(0) != '$') {
8529 if (src[key] && src[key] !== value) {
8530 value += (key === 'style' ? ';' : ' ') + src[key];
8532 dst.$set(key, value, true, srcAttr[key]);
8536 // copy the new attributes on the old attrs object
8537 forEach(src, function(value, key) {
8538 if (key == 'class') {
8539 safeAddClass($element, value);
8540 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value;
8541 } else if (key == 'style') {
8542 $element.attr('style', $element.attr('style') + ';' + value);
8543 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value;
8544 // `dst` will never contain hasOwnProperty as DOM parser won't let it.
8545 // You will get an "InvalidCharacterError: DOM Exception 5" error if you
8546 // have an attribute like "has-own-property" or "data-has-own-property", etc.
8547 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) {
8549 dstAttr[key] = srcAttr[key];
8555 function compileTemplateUrl(directives, $compileNode, tAttrs,
8556 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) {
8558 afterTemplateNodeLinkFn,
8559 afterTemplateChildLinkFn,
8560 beforeTemplateCompileNode = $compileNode[0],
8561 origAsyncDirective = directives.shift(),
8562 derivedSyncDirective = inherit(origAsyncDirective, {
8563 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
8565 templateUrl = (isFunction(origAsyncDirective.templateUrl))
8566 ? origAsyncDirective.templateUrl($compileNode, tAttrs)
8567 : origAsyncDirective.templateUrl,
8568 templateNamespace = origAsyncDirective.templateNamespace;
8570 $compileNode.empty();
8572 $templateRequest(templateUrl)
8573 .then(function(content) {
8574 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn;
8576 content = denormalizeTemplate(content);
8578 if (origAsyncDirective.replace) {
8579 if (jqLiteIsTextNode(content)) {
8582 $template = removeComments(wrapTemplate(templateNamespace, trim(content)));
8584 compileNode = $template[0];
8586 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
8587 throw $compileMinErr('tplrt',
8588 "Template for directive '{0}' must have exactly one root element. {1}",
8589 origAsyncDirective.name, templateUrl);
8592 tempTemplateAttrs = {$attr: {}};
8593 replaceWith($rootElement, $compileNode, compileNode);
8594 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs);
8596 if (isObject(origAsyncDirective.scope)) {
8597 // the original directive that caused the template to be loaded async required
8599 markDirectiveScope(templateDirectives, true);
8601 directives = templateDirectives.concat(directives);
8602 mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
8604 compileNode = beforeTemplateCompileNode;
8605 $compileNode.html(content);
8608 directives.unshift(derivedSyncDirective);
8610 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs,
8611 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns,
8612 previousCompileContext);
8613 forEach($rootElement, function(node, i) {
8614 if (node == compileNode) {
8615 $rootElement[i] = $compileNode[0];
8618 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
8620 while (linkQueue.length) {
8621 var scope = linkQueue.shift(),
8622 beforeTemplateLinkNode = linkQueue.shift(),
8623 linkRootElement = linkQueue.shift(),
8624 boundTranscludeFn = linkQueue.shift(),
8625 linkNode = $compileNode[0];
8627 if (scope.$$destroyed) continue;
8629 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
8630 var oldClasses = beforeTemplateLinkNode.className;
8632 if (!(previousCompileContext.hasElementTranscludeDirective &&
8633 origAsyncDirective.replace)) {
8634 // it was cloned therefore we have to clone as well.
8635 linkNode = jqLiteClone(compileNode);
8637 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
8639 // Copy in CSS classes from original node
8640 safeAddClass(jqLite(linkNode), oldClasses);
8642 if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
8643 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
8645 childBoundTranscludeFn = boundTranscludeFn;
8647 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
8648 childBoundTranscludeFn);
8653 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
8654 var childBoundTranscludeFn = boundTranscludeFn;
8655 if (scope.$$destroyed) return;
8657 linkQueue.push(scope,
8660 childBoundTranscludeFn);
8662 if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
8663 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
8665 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn);
8672 * Sorting function for bound directives.
8674 function byPriority(a, b) {
8675 var diff = b.priority - a.priority;
8676 if (diff !== 0) return diff;
8677 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1;
8678 return a.index - b.index;
8681 function assertNoDuplicate(what, previousDirective, directive, element) {
8683 function wrapModuleNameIfDefined(moduleName) {
8685 (' (module: ' + moduleName + ')') :
8689 if (previousDirective) {
8690 throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}',
8691 previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName),
8692 directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element));
8697 function addTextInterpolateDirective(directives, text) {
8698 var interpolateFn = $interpolate(text, true);
8699 if (interpolateFn) {
8702 compile: function textInterpolateCompileFn(templateNode) {
8703 var templateNodeParent = templateNode.parent(),
8704 hasCompileParent = !!templateNodeParent.length;
8706 // When transcluding a template that has bindings in the root
8707 // we don't have a parent and thus need to add the class during linking fn.
8708 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent);
8710 return function textInterpolateLinkFn(scope, node) {
8711 var parent = node.parent();
8712 if (!hasCompileParent) compile.$$addBindingClass(parent);
8713 compile.$$addBindingInfo(parent, interpolateFn.expressions);
8714 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
8715 node[0].nodeValue = value;
8724 function wrapTemplate(type, template) {
8725 type = lowercase(type || 'html');
8729 var wrapper = document.createElement('div');
8730 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>';
8731 return wrapper.childNodes[0].childNodes;
8738 function getTrustedContext(node, attrNormalizedName) {
8739 if (attrNormalizedName == "srcdoc") {
8742 var tag = nodeName_(node);
8743 // maction[xlink:href] can source SVG. It's not limited to <maction>.
8744 if (attrNormalizedName == "xlinkHref" ||
8745 (tag == "form" && attrNormalizedName == "action") ||
8746 (tag != "img" && (attrNormalizedName == "src" ||
8747 attrNormalizedName == "ngSrc"))) {
8748 return $sce.RESOURCE_URL;
8753 function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) {
8754 var trustedContext = getTrustedContext(node, name);
8755 allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing;
8757 var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing);
8759 // no interpolation found -> ignore
8760 if (!interpolateFn) return;
8763 if (name === "multiple" && nodeName_(node) === "select") {
8764 throw $compileMinErr("selmulti",
8765 "Binding to the 'multiple' attribute is not supported. Element: {0}",
8771 compile: function() {
8773 pre: function attrInterpolatePreLinkFn(scope, element, attr) {
8774 var $$observers = (attr.$$observers || (attr.$$observers = createMap()));
8776 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
8777 throw $compileMinErr('nodomevents',
8778 "Interpolations for HTML DOM event attributes are disallowed. Please use the " +
8779 "ng- versions (such as ng-click instead of onclick) instead.");
8782 // If the attribute has changed since last $interpolate()ed
8783 var newValue = attr[name];
8784 if (newValue !== value) {
8785 // we need to interpolate again since the attribute value has been updated
8786 // (e.g. by another directive's compile function)
8787 // ensure unset/empty values make interpolateFn falsy
8788 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing);
8792 // if attribute was updated so that there is no interpolation going on we don't want to
8793 // register any observers
8794 if (!interpolateFn) return;
8796 // initialize attr object so that it's ready in case we need the value for isolate
8797 // scope initialization, otherwise the value would not be available from isolate
8798 // directive's linking fn during linking phase
8799 attr[name] = interpolateFn(scope);
8801 ($$observers[name] || ($$observers[name] = [])).$$inter = true;
8802 (attr.$$observers && attr.$$observers[name].$$scope || scope).
8803 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) {
8804 //special case for class attribute addition + removal
8805 //so that class changes can tap into the animation
8806 //hooks provided by the $animate service. Be sure to
8807 //skip animations when the first digest occurs (when
8808 //both the new and the old values are the same) since
8809 //the CSS classes are the non-interpolated values
8810 if (name === 'class' && newValue != oldValue) {
8811 attr.$updateClass(newValue, oldValue);
8813 attr.$set(name, newValue);
8824 * This is a special jqLite.replaceWith, which can replace items which
8825 * have no parents, provided that the containing jqLite collection is provided.
8827 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
8828 * in the root of the tree.
8829 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep
8830 * the shell, but replace its DOM node reference.
8831 * @param {Node} newNode The new DOM node.
8833 function replaceWith($rootElement, elementsToRemove, newNode) {
8834 var firstElementToRemove = elementsToRemove[0],
8835 removeCount = elementsToRemove.length,
8836 parent = firstElementToRemove.parentNode,
8840 for (i = 0, ii = $rootElement.length; i < ii; i++) {
8841 if ($rootElement[i] == firstElementToRemove) {
8842 $rootElement[i++] = newNode;
8843 for (var j = i, j2 = j + removeCount - 1,
8844 jj = $rootElement.length;
8845 j < jj; j++, j2++) {
8847 $rootElement[j] = $rootElement[j2];
8849 delete $rootElement[j];
8852 $rootElement.length -= removeCount - 1;
8854 // If the replaced element is also the jQuery .context then replace it
8855 // .context is a deprecated jQuery api, so we should set it only when jQuery set it
8856 // http://api.jquery.com/context/
8857 if ($rootElement.context === firstElementToRemove) {
8858 $rootElement.context = newNode;
8866 parent.replaceChild(newNode, firstElementToRemove);
8869 // TODO(perf): what's this document fragment for? is it needed? can we at least reuse it?
8870 var fragment = document.createDocumentFragment();
8871 fragment.appendChild(firstElementToRemove);
8873 if (jqLite.hasData(firstElementToRemove)) {
8874 // Copy over user data (that includes Angular's $scope etc.). Don't copy private
8875 // data here because there's no public interface in jQuery to do that and copying over
8876 // event listeners (which is the main use of private data) wouldn't work anyway.
8877 jqLite.data(newNode, jqLite.data(firstElementToRemove));
8879 // Remove data of the replaced element. We cannot just call .remove()
8880 // on the element it since that would deallocate scope that is needed
8881 // for the new node. Instead, remove the data "manually".
8883 delete jqLite.cache[firstElementToRemove[jqLite.expando]];
8885 // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after
8886 // the replaced element. The cleanData version monkey-patched by Angular would cause
8887 // the scope to be trashed and we do need the very same scope to work with the new
8888 // element. However, we cannot just cache the non-patched version and use it here as
8889 // that would break if another library patches the method after Angular does (one
8890 // example is jQuery UI). Instead, set a flag indicating scope destroying should be
8891 // skipped this one time.
8892 skipDestroyOnNextJQueryCleanData = true;
8893 jQuery.cleanData([firstElementToRemove]);
8897 for (var k = 1, kk = elementsToRemove.length; k < kk; k++) {
8898 var element = elementsToRemove[k];
8899 jqLite(element).remove(); // must do this way to clean up expando
8900 fragment.appendChild(element);
8901 delete elementsToRemove[k];
8904 elementsToRemove[0] = newNode;
8905 elementsToRemove.length = 1;
8909 function cloneAndAnnotateFn(fn, annotation) {
8910 return extend(function() { return fn.apply(null, arguments); }, fn, annotation);
8914 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) {
8916 linkFn(scope, $element, attrs, controllers, transcludeFn);
8918 $exceptionHandler(e, startingTag($element));
8923 // Set up $watches for isolate scope and controller bindings. This process
8924 // only occurs for isolate scopes and new scopes with controllerAs.
8925 function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
8926 var removeWatchCollection = [];
8927 forEach(bindings, function(definition, scopeName) {
8928 var attrName = definition.attrName,
8929 optional = definition.optional,
8930 mode = definition.mode, // @, =, or &
8932 parentGet, parentSet, compare;
8937 if (!optional && !hasOwnProperty.call(attrs, attrName)) {
8938 destination[scopeName] = attrs[attrName] = void 0;
8940 attrs.$observe(attrName, function(value) {
8941 if (isString(value)) {
8942 destination[scopeName] = value;
8945 attrs.$$observers[attrName].$$scope = scope;
8946 if (isString(attrs[attrName])) {
8947 // If the attribute has been provided then we trigger an interpolation to ensure
8948 // the value is there for use in the link fn
8949 destination[scopeName] = $interpolate(attrs[attrName])(scope);
8954 if (!hasOwnProperty.call(attrs, attrName)) {
8955 if (optional) break;
8956 attrs[attrName] = void 0;
8958 if (optional && !attrs[attrName]) break;
8960 parentGet = $parse(attrs[attrName]);
8961 if (parentGet.literal) {
8964 compare = function(a, b) { return a === b || (a !== a && b !== b); };
8966 parentSet = parentGet.assign || function() {
8967 // reset the change, or we will throw this exception on every $digest
8968 lastValue = destination[scopeName] = parentGet(scope);
8969 throw $compileMinErr('nonassign',
8970 "Expression '{0}' used with directive '{1}' is non-assignable!",
8971 attrs[attrName], directive.name);
8973 lastValue = destination[scopeName] = parentGet(scope);
8974 var parentValueWatch = function parentValueWatch(parentValue) {
8975 if (!compare(parentValue, destination[scopeName])) {
8976 // we are out of sync and need to copy
8977 if (!compare(parentValue, lastValue)) {
8978 // parent changed and it has precedence
8979 destination[scopeName] = parentValue;
8981 // if the parent can be assigned then do so
8982 parentSet(scope, parentValue = destination[scopeName]);
8985 return lastValue = parentValue;
8987 parentValueWatch.$stateful = true;
8989 if (definition.collection) {
8990 removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
8992 removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
8994 removeWatchCollection.push(removeWatch);
8998 // Don't assign Object.prototype method to scope
8999 parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;
9001 // Don't assign noop to destination if expression is not valid
9002 if (parentGet === noop && optional) break;
9004 destination[scopeName] = function(locals) {
9005 return parentGet(scope, locals);
9011 return removeWatchCollection.length && function removeWatches() {
9012 for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) {
9013 removeWatchCollection[i]();
9020 var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
9022 * Converts all accepted directives format into proper directive name.
9023 * @param name Name to normalize
9025 function directiveNormalize(name) {
9026 return camelCase(name.replace(PREFIX_REGEXP, ''));
9031 * @name $compile.directive.Attributes
9034 * A shared object between directive compile / linking functions which contains normalized DOM
9035 * element attributes. The values reflect current binding state `{{ }}`. The normalization is
9036 * needed since all of these are treated as equivalent in Angular:
9039 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
9045 * @name $compile.directive.Attributes#$attr
9048 * A map of DOM element attribute names to the normalized name. This is
9049 * needed to do reverse lookup from normalized name back to actual name.
9055 * @name $compile.directive.Attributes#$set
9059 * Set DOM element attribute value.
9062 * @param {string} name Normalized element attribute name of the property to modify. The name is
9063 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
9064 * property to the original name.
9065 * @param {string} value Value to set the attribute to. The value can be an interpolated string.
9071 * Closure compiler type information
9074 function nodesetLinkingFn(
9075 /* angular.Scope */ scope,
9076 /* NodeList */ nodeList,
9077 /* Element */ rootElement,
9078 /* function(Function) */ boundTranscludeFn
9081 function directiveLinkingFn(
9082 /* nodesetLinkingFn */ nodesetLinkingFn,
9083 /* angular.Scope */ scope,
9085 /* Element */ rootElement,
9086 /* function(Function) */ boundTranscludeFn
9089 function tokenDifference(str1, str2) {
9091 tokens1 = str1.split(/\s+/),
9092 tokens2 = str2.split(/\s+/);
9095 for (var i = 0; i < tokens1.length; i++) {
9096 var token = tokens1[i];
9097 for (var j = 0; j < tokens2.length; j++) {
9098 if (token == tokens2[j]) continue outer;
9100 values += (values.length > 0 ? ' ' : '') + token;
9105 function removeComments(jqNodes) {
9106 jqNodes = jqLite(jqNodes);
9107 var i = jqNodes.length;
9114 var node = jqNodes[i];
9115 if (node.nodeType === NODE_TYPE_COMMENT) {
9116 splice.call(jqNodes, i, 1);
9122 var $controllerMinErr = minErr('$controller');
9125 var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
9126 function identifierForController(controller, ident) {
9127 if (ident && isString(ident)) return ident;
9128 if (isString(controller)) {
9129 var match = CNTRL_REG.exec(controller);
9130 if (match) return match[3];
9137 * @name $controllerProvider
9139 * The {@link ng.$controller $controller service} is used by Angular to create new
9142 * This provider allows controller registration via the
9143 * {@link ng.$controllerProvider#register register} method.
9145 function $ControllerProvider() {
9146 var controllers = {},
9151 * @name $controllerProvider#register
9152 * @param {string|Object} name Controller name, or an object map of controllers where the keys are
9153 * the names and the values are the constructors.
9154 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI
9155 * annotations in the array notation).
9157 this.register = function(name, constructor) {
9158 assertNotHasOwnProperty(name, 'controller');
9159 if (isObject(name)) {
9160 extend(controllers, name);
9162 controllers[name] = constructor;
9168 * @name $controllerProvider#allowGlobals
9169 * @description If called, allows `$controller` to find controller constructors on `window`
9171 this.allowGlobals = function() {
9176 this.$get = ['$injector', '$window', function($injector, $window) {
9181 * @requires $injector
9183 * @param {Function|string} constructor If called with a function then it's considered to be the
9184 * controller constructor function. Otherwise it's considered to be a string which is used
9185 * to retrieve the controller constructor using the following steps:
9187 * * check if a controller with given name is registered via `$controllerProvider`
9188 * * check if evaluating the string on the current scope returns a constructor
9189 * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
9190 * `window` object (not recommended)
9192 * The string can use the `controller as property` syntax, where the controller instance is published
9193 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this
9194 * to work correctly.
9196 * @param {Object} locals Injection locals for Controller.
9197 * @return {Object} Instance of given controller.
9200 * `$controller` service is responsible for instantiating controllers.
9202 * It's just a simple call to {@link auto.$injector $injector}, but extracted into
9203 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788).
9205 return function(expression, locals, later, ident) {
9207 // param `later` --- indicates that the controller's constructor is invoked at a later time.
9208 // If true, $controller will allocate the object with the correct
9209 // prototype chain, but will not invoke the controller until a returned
9210 // callback is invoked.
9211 // param `ident` --- An optional label which overrides the label parsed from the controller
9212 // expression, if any.
9213 var instance, match, constructor, identifier;
9214 later = later === true;
9215 if (ident && isString(ident)) {
9219 if (isString(expression)) {
9220 match = expression.match(CNTRL_REG);
9222 throw $controllerMinErr('ctrlfmt',
9223 "Badly formed controller string '{0}'. " +
9224 "Must match `__name__ as __id__` or `__name__`.", expression);
9226 constructor = match[1],
9227 identifier = identifier || match[3];
9228 expression = controllers.hasOwnProperty(constructor)
9229 ? controllers[constructor]
9230 : getter(locals.$scope, constructor, true) ||
9231 (globals ? getter($window, constructor, true) : undefined);
9233 assertArgFn(expression, constructor, true);
9237 // Instantiate controller later:
9238 // This machinery is used to create an instance of the object before calling the
9239 // controller's constructor itself.
9241 // This allows properties to be added to the controller before the constructor is
9242 // invoked. Primarily, this is used for isolate scope bindings in $compile.
9244 // This feature is not intended for use by applications, and is thus not documented
9246 // Object creation: http://jsperf.com/create-constructor/2
9247 var controllerPrototype = (isArray(expression) ?
9248 expression[expression.length - 1] : expression).prototype;
9249 instance = Object.create(controllerPrototype || null);
9252 addIdentifier(locals, identifier, instance, constructor || expression.name);
9256 return instantiate = extend(function() {
9257 var result = $injector.invoke(expression, instance, locals, constructor);
9258 if (result !== instance && (isObject(result) || isFunction(result))) {
9261 // If result changed, re-assign controllerAs value to scope.
9262 addIdentifier(locals, identifier, instance, constructor || expression.name);
9268 identifier: identifier
9272 instance = $injector.instantiate(expression, locals, constructor);
9275 addIdentifier(locals, identifier, instance, constructor || expression.name);
9281 function addIdentifier(locals, identifier, instance, name) {
9282 if (!(locals && isObject(locals.$scope))) {
9283 throw minErr('$controller')('noscp',
9284 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.",
9288 locals.$scope[identifier] = instance;
9299 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object.
9302 <example module="documentExample">
9303 <file name="index.html">
9304 <div ng-controller="ExampleController">
9305 <p>$document title: <b ng-bind="title"></b></p>
9306 <p>window.document title: <b ng-bind="windowTitle"></b></p>
9309 <file name="script.js">
9310 angular.module('documentExample', [])
9311 .controller('ExampleController', ['$scope', '$document', function($scope, $document) {
9312 $scope.title = $document[0].title;
9313 $scope.windowTitle = angular.element(window.document)[0].title;
9318 function $DocumentProvider() {
9319 this.$get = ['$window', function(window) {
9320 return jqLite(window.document);
9326 * @name $exceptionHandler
9330 * Any uncaught exception in angular expressions is delegated to this service.
9331 * The default implementation simply delegates to `$log.error` which logs it into
9332 * the browser console.
9334 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by
9335 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing.
9340 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() {
9341 * return function(exception, cause) {
9342 * exception.message += ' (caused by "' + cause + '")';
9348 * This example will override the normal action of `$exceptionHandler`, to make angular
9349 * exceptions fail hard when they happen, instead of just logging to the console.
9352 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind`
9353 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler}
9354 * (unless executed during a digest).
9356 * If you wish, you can manually delegate exceptions, e.g.
9357 * `try { ... } catch(e) { $exceptionHandler(e); }`
9359 * @param {Error} exception Exception associated with the error.
9360 * @param {string=} cause optional information about the context in which
9361 * the error was thrown.
9364 function $ExceptionHandlerProvider() {
9365 this.$get = ['$log', function($log) {
9366 return function(exception, cause) {
9367 $log.error.apply($log, arguments);
9372 var $$ForceReflowProvider = function() {
9373 this.$get = ['$document', function($document) {
9374 return function(domNode) {
9375 //the line below will force the browser to perform a repaint so
9376 //that all the animated elements within the animation frame will
9377 //be properly updated and drawn on screen. This is required to
9378 //ensure that the preparation animation is properly flushed so that
9379 //the active state picks up from there. DO NOT REMOVE THIS LINE.
9380 //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH
9381 //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND
9382 //WILL TAKE YEARS AWAY FROM YOUR LIFE.
9384 if (!domNode.nodeType && domNode instanceof jqLite) {
9385 domNode = domNode[0];
9388 domNode = $document[0].body;
9390 return domNode.offsetWidth + 1;
9395 var APPLICATION_JSON = 'application/json';
9396 var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
9397 var JSON_START = /^\[|^\{(?!\{)/;
9402 var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
9403 var $httpMinErr = minErr('$http');
9404 var $httpMinErrLegacyFn = function(method) {
9406 throw $httpMinErr('legacy', 'The method `{0}` on the promise returned from `$http` has been disabled.', method);
9410 function serializeValue(v) {
9412 return isDate(v) ? v.toISOString() : toJson(v);
9418 function $HttpParamSerializerProvider() {
9421 * @name $httpParamSerializer
9424 * Default {@link $http `$http`} params serializer that converts objects to strings
9425 * according to the following rules:
9427 * * `{'foo': 'bar'}` results in `foo=bar`
9428 * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object)
9429 * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element)
9430 * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object)
9432 * Note that serializer will sort the request parameters alphabetically.
9435 this.$get = function() {
9436 return function ngParamSerializer(params) {
9437 if (!params) return '';
9439 forEachSorted(params, function(value, key) {
9440 if (value === null || isUndefined(value)) return;
9441 if (isArray(value)) {
9442 forEach(value, function(v, k) {
9443 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v)));
9446 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
9450 return parts.join('&');
9455 function $HttpParamSerializerJQLikeProvider() {
9458 * @name $httpParamSerializerJQLike
9461 * Alternative {@link $http `$http`} params serializer that follows
9462 * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic.
9463 * The serializer will also sort the params alphabetically.
9465 * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property:
9472 * paramSerializer: '$httpParamSerializerJQLike'
9476 * It is also possible to set it as the default `paramSerializer` in the
9477 * {@link $httpProvider#defaults `$httpProvider`}.
9479 * Additionally, you can inject the serializer and use it explicitly, for example to serialize
9480 * form data for submission:
9483 * .controller(function($http, $httpParamSerializerJQLike) {
9489 * data: $httpParamSerializerJQLike(myData),
9491 * 'Content-Type': 'application/x-www-form-urlencoded'
9499 this.$get = function() {
9500 return function jQueryLikeParamSerializer(params) {
9501 if (!params) return '';
9503 serialize(params, '', true);
9504 return parts.join('&');
9506 function serialize(toSerialize, prefix, topLevel) {
9507 if (toSerialize === null || isUndefined(toSerialize)) return;
9508 if (isArray(toSerialize)) {
9509 forEach(toSerialize, function(value, index) {
9510 serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']');
9512 } else if (isObject(toSerialize) && !isDate(toSerialize)) {
9513 forEachSorted(toSerialize, function(value, key) {
9514 serialize(value, prefix +
9515 (topLevel ? '' : '[') +
9517 (topLevel ? '' : ']'));
9520 parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize)));
9527 function defaultHttpResponseTransform(data, headers) {
9528 if (isString(data)) {
9529 // Strip json vulnerability protection prefix and trim whitespace
9530 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim();
9533 var contentType = headers('Content-Type');
9534 if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) {
9535 data = fromJson(tempData);
9543 function isJsonLike(str) {
9544 var jsonStart = str.match(JSON_START);
9545 return jsonStart && JSON_ENDS[jsonStart[0]].test(str);
9549 * Parse headers into key value object
9551 * @param {string} headers Raw headers as a string
9552 * @returns {Object} Parsed headers as key value object
9554 function parseHeaders(headers) {
9555 var parsed = createMap(), i;
9557 function fillInParsed(key, val) {
9559 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
9563 if (isString(headers)) {
9564 forEach(headers.split('\n'), function(line) {
9565 i = line.indexOf(':');
9566 fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1)));
9568 } else if (isObject(headers)) {
9569 forEach(headers, function(headerVal, headerKey) {
9570 fillInParsed(lowercase(headerKey), trim(headerVal));
9579 * Returns a function that provides access to parsed headers.
9581 * Headers are lazy parsed when first requested.
9584 * @param {(string|Object)} headers Headers to provide access to.
9585 * @returns {function(string=)} Returns a getter function which if called with:
9587 * - if called with single an argument returns a single header value or null
9588 * - if called with no arguments returns an object containing all headers.
9590 function headersGetter(headers) {
9593 return function(name) {
9594 if (!headersObj) headersObj = parseHeaders(headers);
9597 var value = headersObj[lowercase(name)];
9598 if (value === void 0) {
9610 * Chain all given functions
9612 * This function is used for both request and response transforming
9614 * @param {*} data Data to transform.
9615 * @param {function(string=)} headers HTTP headers getter fn.
9616 * @param {number} status HTTP status code of the response.
9617 * @param {(Function|Array.<Function>)} fns Function or an array of functions.
9618 * @returns {*} Transformed data.
9620 function transformData(data, headers, status, fns) {
9621 if (isFunction(fns)) {
9622 return fns(data, headers, status);
9625 forEach(fns, function(fn) {
9626 data = fn(data, headers, status);
9633 function isSuccess(status) {
9634 return 200 <= status && status < 300;
9640 * @name $httpProvider
9642 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service.
9644 function $HttpProvider() {
9647 * @name $httpProvider#defaults
9650 * Object containing default values for all {@link ng.$http $http} requests.
9652 * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`}
9653 * that will provide the cache for all requests who set their `cache` property to `true`.
9654 * If you set the `defaults.cache = false` then only requests that specify their own custom
9655 * cache object will be cached. See {@link $http#caching $http Caching} for more information.
9657 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token.
9658 * Defaults value is `'XSRF-TOKEN'`.
9660 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the
9661 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`.
9663 * - **`defaults.headers`** - {Object} - Default headers for all $http requests.
9664 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on
9665 * setting default headers.
9666 * - **`defaults.headers.common`**
9667 * - **`defaults.headers.post`**
9668 * - **`defaults.headers.put`**
9669 * - **`defaults.headers.patch`**
9672 * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function
9673 * used to the prepare string representation of request parameters (specified as an object).
9674 * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}.
9675 * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}.
9678 var defaults = this.defaults = {
9679 // transform incoming response data
9680 transformResponse: [defaultHttpResponseTransform],
9682 // transform outgoing request data
9683 transformRequest: [function(d) {
9684 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d;
9690 'Accept': 'application/json, text/plain, */*'
9692 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
9693 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
9694 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON)
9697 xsrfCookieName: 'XSRF-TOKEN',
9698 xsrfHeaderName: 'X-XSRF-TOKEN',
9700 paramSerializer: '$httpParamSerializer'
9703 var useApplyAsync = false;
9706 * @name $httpProvider#useApplyAsync
9709 * Configure $http service to combine processing of multiple http responses received at around
9710 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in
9711 * significant performance improvement for bigger applications that make many HTTP requests
9712 * concurrently (common during application bootstrap).
9714 * Defaults to false. If no value is specified, returns the current configured value.
9716 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred
9717 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window
9718 * to load and share the same digest cycle.
9720 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
9721 * otherwise, returns the current configured value.
9723 this.useApplyAsync = function(value) {
9724 if (isDefined(value)) {
9725 useApplyAsync = !!value;
9728 return useApplyAsync;
9731 var useLegacyPromise = true;
9734 * @name $httpProvider#useLegacyPromiseExtensions
9737 * Configure `$http` service to return promises without the shorthand methods `success` and `error`.
9738 * This should be used to make sure that applications work without these methods.
9740 * Defaults to true. If no value is specified, returns the current configured value.
9742 * @param {boolean=} value If true, `$http` will return a promise with the deprecated legacy `success` and `error` methods.
9744 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
9745 * otherwise, returns the current configured value.
9747 this.useLegacyPromiseExtensions = function(value) {
9748 if (isDefined(value)) {
9749 useLegacyPromise = !!value;
9752 return useLegacyPromise;
9757 * @name $httpProvider#interceptors
9760 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http}
9761 * pre-processing of request or postprocessing of responses.
9763 * These service factories are ordered by request, i.e. they are applied in the same order as the
9764 * array, on request, but reverse order, on response.
9766 * {@link ng.$http#interceptors Interceptors detailed info}
9768 var interceptorFactories = this.interceptors = [];
9770 this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector',
9771 function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) {
9773 var defaultCache = $cacheFactory('$http');
9776 * Make sure that default param serializer is exposed as a function
9778 defaults.paramSerializer = isString(defaults.paramSerializer) ?
9779 $injector.get(defaults.paramSerializer) : defaults.paramSerializer;
9782 * Interceptors stored in reverse order. Inner interceptors before outer interceptors.
9783 * The reversal is needed so that we can build up the interception chain around the
9786 var reversedInterceptors = [];
9788 forEach(interceptorFactories, function(interceptorFactory) {
9789 reversedInterceptors.unshift(isString(interceptorFactory)
9790 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory));
9797 * @requires ng.$httpBackend
9798 * @requires $cacheFactory
9799 * @requires $rootScope
9801 * @requires $injector
9804 * The `$http` service is a core Angular service that facilitates communication with the remote
9805 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest)
9806 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP).
9808 * For unit testing applications that use `$http` service, see
9809 * {@link ngMock.$httpBackend $httpBackend mock}.
9811 * For a higher level of abstraction, please check out the {@link ngResource.$resource
9812 * $resource} service.
9814 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by
9815 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage
9816 * it is important to familiarize yourself with these APIs and the guarantees they provide.
9820 * The `$http` service is a function which takes a single argument — a {@link $http#usage configuration object} —
9821 * that is used to generate an HTTP request and returns a {@link ng.$q promise}.
9824 * // Simple GET request example:
9828 * }).then(function successCallback(response) {
9829 * // this callback will be called asynchronously
9830 * // when the response is available
9831 * }, function errorCallback(response) {
9832 * // called asynchronously if an error occurs
9833 * // or server returns response with an error status.
9837 * The response object has these properties:
9839 * - **data** – `{string|Object}` – The response body transformed with the transform
9841 * - **status** – `{number}` – HTTP status code of the response.
9842 * - **headers** – `{function([headerName])}` – Header getter function.
9843 * - **config** – `{Object}` – The configuration object that was used to generate the request.
9844 * - **statusText** – `{string}` – HTTP status text of the response.
9846 * A response status code between 200 and 299 is considered a success status and
9847 * will result in the success callback being called. Note that if the response is a redirect,
9848 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be
9849 * called for such responses.
9852 * ## Shortcut methods
9854 * Shortcut methods are also available. All shortcut methods require passing in the URL, and
9855 * request data must be passed in for POST/PUT requests. An optional config can be passed as the
9859 * $http.get('/someUrl', config).then(successCallback, errorCallback);
9860 * $http.post('/someUrl', data, config).then(successCallback, errorCallback);
9863 * Complete list of shortcut methods:
9865 * - {@link ng.$http#get $http.get}
9866 * - {@link ng.$http#head $http.head}
9867 * - {@link ng.$http#post $http.post}
9868 * - {@link ng.$http#put $http.put}
9869 * - {@link ng.$http#delete $http.delete}
9870 * - {@link ng.$http#jsonp $http.jsonp}
9871 * - {@link ng.$http#patch $http.patch}
9874 * ## Writing Unit Tests that use $http
9875 * When unit testing (using {@link ngMock ngMock}), it is necessary to call
9876 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending
9877 * request using trained responses.
9880 * $httpBackend.expectGET(...);
9882 * $httpBackend.flush();
9885 * ## Deprecation Notice
9886 * <div class="alert alert-danger">
9887 * The `$http` legacy promise methods `success` and `error` have been deprecated.
9888 * Use the standard `then` method instead.
9889 * If {@link $httpProvider#useLegacyPromiseExtensions `$httpProvider.useLegacyPromiseExtensions`} is set to
9890 * `false` then these methods will throw {@link $http:legacy `$http/legacy`} error.
9893 * ## Setting HTTP Headers
9895 * The $http service will automatically add certain HTTP headers to all requests. These defaults
9896 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration
9897 * object, which currently contains this default configuration:
9899 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
9900 * - `Accept: application/json, text/plain, * / *`
9901 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests)
9902 * - `Content-Type: application/json`
9903 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests)
9904 * - `Content-Type: application/json`
9906 * To add or overwrite these defaults, simply add or remove a property from these configuration
9907 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
9908 * with the lowercased HTTP method name as the key, e.g.
9909 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`.
9911 * The defaults can also be set at runtime via the `$http.defaults` object in the same
9912 * fashion. For example:
9915 * module.run(function($http) {
9916 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'
9920 * In addition, you can supply a `headers` property in the config object passed when
9921 * calling `$http(config)`, which overrides the defaults without changing them globally.
9923 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis,
9924 * Use the `headers` property, setting the desired header to `undefined`. For example:
9929 * url: 'http://example.com',
9931 * 'Content-Type': undefined
9933 * data: { test: 'test' }
9936 * $http(req).then(function(){...}, function(){...});
9939 * ## Transforming Requests and Responses
9941 * Both requests and responses can be transformed using transformation functions: `transformRequest`
9942 * and `transformResponse`. These properties can be a single function that returns
9943 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions,
9944 * which allows you to `push` or `unshift` a new transformation function into the transformation chain.
9946 * ### Default Transformations
9948 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and
9949 * `defaults.transformResponse` properties. If a request does not provide its own transformations
9950 * then these will be applied.
9952 * You can augment or replace the default transformations by modifying these properties by adding to or
9953 * replacing the array.
9955 * Angular provides the following default transformations:
9957 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`):
9959 * - If the `data` property of the request configuration object contains an object, serialize it
9962 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`):
9964 * - If XSRF prefix is detected, strip it (see Security Considerations section below).
9965 * - If JSON response is detected, deserialize it using a JSON parser.
9968 * ### Overriding the Default Transformations Per Request
9970 * If you wish override the request/response transformations only for a single request then provide
9971 * `transformRequest` and/or `transformResponse` properties on the configuration object passed
9974 * Note that if you provide these properties on the config object the default transformations will be
9975 * overwritten. If you wish to augment the default transformations then you must include them in your
9976 * local transformation array.
9978 * The following code demonstrates adding a new response transformation to be run after the default response
9979 * transformations have been run.
9982 * function appendTransform(defaults, transform) {
9984 * // We can't guarantee that the default transformation is an array
9985 * defaults = angular.isArray(defaults) ? defaults : [defaults];
9987 * // Append the new transformation to the defaults
9988 * return defaults.concat(transform);
9994 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) {
9995 * return doTransform(value);
10003 * To enable caching, set the request configuration `cache` property to `true` (to use default
10004 * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}).
10005 * When the cache is enabled, `$http` stores the response from the server in the specified
10006 * cache. The next time the same request is made, the response is served from the cache without
10007 * sending a request to the server.
10009 * Note that even if the response is served from cache, delivery of the data is asynchronous in
10010 * the same way that real requests are.
10012 * If there are multiple GET requests for the same URL that should be cached using the same
10013 * cache, but the cache is not populated yet, only one request to the server will be made and
10014 * the remaining requests will be fulfilled using the response from the first request.
10016 * You can change the default cache to a new object (built with
10017 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the
10018 * {@link ng.$http#defaults `$http.defaults.cache`} property. All requests who set
10019 * their `cache` property to `true` will now use this cache object.
10021 * If you set the default cache to `false` then only requests that specify their own custom
10022 * cache object will be cached.
10026 * Before you start creating interceptors, be sure to understand the
10027 * {@link ng.$q $q and deferred/promise APIs}.
10029 * For purposes of global error handling, authentication, or any kind of synchronous or
10030 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be
10031 * able to intercept requests before they are handed to the server and
10032 * responses before they are handed over to the application code that
10033 * initiated these requests. The interceptors leverage the {@link ng.$q
10034 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing.
10036 * The interceptors are service factories that are registered with the `$httpProvider` by
10037 * adding them to the `$httpProvider.interceptors` array. The factory is called and
10038 * injected with dependencies (if specified) and returns the interceptor.
10040 * There are two kinds of interceptors (and two kinds of rejection interceptors):
10042 * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to
10043 * modify the `config` object or create a new one. The function needs to return the `config`
10044 * object directly, or a promise containing the `config` or a new `config` object.
10045 * * `requestError`: interceptor gets called when a previous interceptor threw an error or
10046 * resolved with a rejection.
10047 * * `response`: interceptors get called with http `response` object. The function is free to
10048 * modify the `response` object or create a new one. The function needs to return the `response`
10049 * object directly, or as a promise containing the `response` or a new `response` object.
10050 * * `responseError`: interceptor gets called when a previous interceptor threw an error or
10051 * resolved with a rejection.
10055 * // register the interceptor as a service
10056 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
10058 * // optional method
10059 * 'request': function(config) {
10060 * // do something on success
10064 * // optional method
10065 * 'requestError': function(rejection) {
10066 * // do something on error
10067 * if (canRecover(rejection)) {
10068 * return responseOrNewPromise
10070 * return $q.reject(rejection);
10075 * // optional method
10076 * 'response': function(response) {
10077 * // do something on success
10081 * // optional method
10082 * 'responseError': function(rejection) {
10083 * // do something on error
10084 * if (canRecover(rejection)) {
10085 * return responseOrNewPromise
10087 * return $q.reject(rejection);
10092 * $httpProvider.interceptors.push('myHttpInterceptor');
10095 * // alternatively, register the interceptor via an anonymous factory
10096 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) {
10098 * 'request': function(config) {
10102 * 'response': function(response) {
10109 * ## Security Considerations
10111 * When designing web applications, consider security threats from:
10113 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
10114 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery)
10116 * Both server and the client must cooperate in order to eliminate these threats. Angular comes
10117 * pre-configured with strategies that address these issues, but for this to work backend server
10118 * cooperation is required.
10120 * ### JSON Vulnerability Protection
10122 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
10123 * allows third party website to turn your JSON resource URL into
10124 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To
10125 * counter this your server can prefix all JSON requests with following string `")]}',\n"`.
10126 * Angular will automatically strip the prefix before processing it as JSON.
10128 * For example if your server needs to return:
10133 * which is vulnerable to attack, your server can return:
10139 * Angular will strip the prefix, before processing the JSON.
10142 * ### Cross Site Request Forgery (XSRF) Protection
10144 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which
10145 * an unauthorized site can gain your user's private data. Angular provides a mechanism
10146 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie
10147 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only
10148 * JavaScript that runs on your domain could read the cookie, your server can be assured that
10149 * the XHR came from JavaScript running on your domain. The header will not be set for
10150 * cross-domain requests.
10152 * To take advantage of this, your server needs to set a token in a JavaScript readable session
10153 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the
10154 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure
10155 * that only JavaScript running on your domain could have sent the request. The token must be
10156 * unique for each user and must be verifiable by the server (to prevent the JavaScript from
10157 * making up its own tokens). We recommend that the token is a digest of your site's
10158 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography))
10159 * for added security.
10161 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName
10162 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time,
10163 * or the per-request config object.
10165 * In order to prevent collisions in environments where multiple Angular apps share the
10166 * same domain or subdomain, we recommend that each application uses unique cookie name.
10168 * @param {object} config Object describing the request to be made and how it should be
10169 * processed. The object has following properties:
10171 * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
10172 * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested.
10173 * - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be serialized
10174 * with the `paramSerializer` and appended as GET parameters.
10175 * - **data** – `{string|Object}` – Data to be sent as the request message data.
10176 * - **headers** – `{Object}` – Map of strings or functions which return strings representing
10177 * HTTP headers to send to the server. If the return value of a function is null, the
10178 * header will not be sent. Functions accept a config object as an argument.
10179 * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token.
10180 * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token.
10181 * - **transformRequest** –
10182 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
10183 * transform function or an array of such functions. The transform function takes the http
10184 * request body and headers and returns its transformed (typically serialized) version.
10185 * See {@link ng.$http#overriding-the-default-transformations-per-request
10186 * Overriding the Default Transformations}
10187 * - **transformResponse** –
10188 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` –
10189 * transform function or an array of such functions. The transform function takes the http
10190 * response body, headers and status and returns its transformed (typically deserialized) version.
10191 * See {@link ng.$http#overriding-the-default-transformations-per-request
10192 * Overriding the Default TransformationjqLiks}
10193 * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to
10194 * prepare the string representation of request parameters (specified as an object).
10195 * If specified as string, it is interpreted as function registered with the
10196 * {@link $injector $injector}, which means you can create your own serializer
10197 * by registering it as a {@link auto.$provide#service service}.
10198 * The default serializer is the {@link $httpParamSerializer $httpParamSerializer};
10199 * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike}
10200 * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
10201 * GET request, otherwise if a cache instance built with
10202 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
10204 * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise}
10205 * that should abort the request when resolved.
10206 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the
10207 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials)
10208 * for more information.
10209 * - **responseType** - `{string}` - see
10210 * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype).
10212 * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object
10213 * when the request succeeds or fails.
10216 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending
10217 * requests. This is primarily meant to be used for debugging purposes.
10221 <example module="httpExample">
10222 <file name="index.html">
10223 <div ng-controller="FetchController">
10224 <select ng-model="method" aria-label="Request method">
10225 <option>GET</option>
10226 <option>JSONP</option>
10228 <input type="text" ng-model="url" size="80" aria-label="URL" />
10229 <button id="fetchbtn" ng-click="fetch()">fetch</button><br>
10230 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
10231 <button id="samplejsonpbtn"
10232 ng-click="updateModel('JSONP',
10233 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')">
10236 <button id="invalidjsonpbtn"
10237 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')">
10240 <pre>http status code: {{status}}</pre>
10241 <pre>http response data: {{data}}</pre>
10244 <file name="script.js">
10245 angular.module('httpExample', [])
10246 .controller('FetchController', ['$scope', '$http', '$templateCache',
10247 function($scope, $http, $templateCache) {
10248 $scope.method = 'GET';
10249 $scope.url = 'http-hello.html';
10251 $scope.fetch = function() {
10252 $scope.code = null;
10253 $scope.response = null;
10255 $http({method: $scope.method, url: $scope.url, cache: $templateCache}).
10256 then(function(response) {
10257 $scope.status = response.status;
10258 $scope.data = response.data;
10259 }, function(response) {
10260 $scope.data = response.data || "Request failed";
10261 $scope.status = response.status;
10265 $scope.updateModel = function(method, url) {
10266 $scope.method = method;
10271 <file name="http-hello.html">
10274 <file name="protractor.js" type="protractor">
10275 var status = element(by.binding('status'));
10276 var data = element(by.binding('data'));
10277 var fetchBtn = element(by.id('fetchbtn'));
10278 var sampleGetBtn = element(by.id('samplegetbtn'));
10279 var sampleJsonpBtn = element(by.id('samplejsonpbtn'));
10280 var invalidJsonpBtn = element(by.id('invalidjsonpbtn'));
10282 it('should make an xhr GET request', function() {
10283 sampleGetBtn.click();
10285 expect(status.getText()).toMatch('200');
10286 expect(data.getText()).toMatch(/Hello, \$http!/);
10289 // Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185
10290 // it('should make a JSONP request to angularjs.org', function() {
10291 // sampleJsonpBtn.click();
10292 // fetchBtn.click();
10293 // expect(status.getText()).toMatch('200');
10294 // expect(data.getText()).toMatch(/Super Hero!/);
10297 it('should make JSONP request to invalid URL and invoke the error handler',
10299 invalidJsonpBtn.click();
10301 expect(status.getText()).toMatch('0');
10302 expect(data.getText()).toMatch('Request failed');
10307 function $http(requestConfig) {
10309 if (!angular.isObject(requestConfig)) {
10310 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig);
10313 var config = extend({
10315 transformRequest: defaults.transformRequest,
10316 transformResponse: defaults.transformResponse,
10317 paramSerializer: defaults.paramSerializer
10320 config.headers = mergeHeaders(requestConfig);
10321 config.method = uppercase(config.method);
10322 config.paramSerializer = isString(config.paramSerializer) ?
10323 $injector.get(config.paramSerializer) : config.paramSerializer;
10325 var serverRequest = function(config) {
10326 var headers = config.headers;
10327 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest);
10329 // strip content-type if data is undefined
10330 if (isUndefined(reqData)) {
10331 forEach(headers, function(value, header) {
10332 if (lowercase(header) === 'content-type') {
10333 delete headers[header];
10338 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
10339 config.withCredentials = defaults.withCredentials;
10343 return sendReq(config, reqData).then(transformResponse, transformResponse);
10346 var chain = [serverRequest, undefined];
10347 var promise = $q.when(config);
10349 // apply interceptors
10350 forEach(reversedInterceptors, function(interceptor) {
10351 if (interceptor.request || interceptor.requestError) {
10352 chain.unshift(interceptor.request, interceptor.requestError);
10354 if (interceptor.response || interceptor.responseError) {
10355 chain.push(interceptor.response, interceptor.responseError);
10359 while (chain.length) {
10360 var thenFn = chain.shift();
10361 var rejectFn = chain.shift();
10363 promise = promise.then(thenFn, rejectFn);
10366 if (useLegacyPromise) {
10367 promise.success = function(fn) {
10368 assertArgFn(fn, 'fn');
10370 promise.then(function(response) {
10371 fn(response.data, response.status, response.headers, config);
10376 promise.error = function(fn) {
10377 assertArgFn(fn, 'fn');
10379 promise.then(null, function(response) {
10380 fn(response.data, response.status, response.headers, config);
10385 promise.success = $httpMinErrLegacyFn('success');
10386 promise.error = $httpMinErrLegacyFn('error');
10391 function transformResponse(response) {
10392 // make a copy since the response must be cacheable
10393 var resp = extend({}, response);
10394 resp.data = transformData(response.data, response.headers, response.status,
10395 config.transformResponse);
10396 return (isSuccess(response.status))
10401 function executeHeaderFns(headers, config) {
10402 var headerContent, processedHeaders = {};
10404 forEach(headers, function(headerFn, header) {
10405 if (isFunction(headerFn)) {
10406 headerContent = headerFn(config);
10407 if (headerContent != null) {
10408 processedHeaders[header] = headerContent;
10411 processedHeaders[header] = headerFn;
10415 return processedHeaders;
10418 function mergeHeaders(config) {
10419 var defHeaders = defaults.headers,
10420 reqHeaders = extend({}, config.headers),
10421 defHeaderName, lowercaseDefHeaderName, reqHeaderName;
10423 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]);
10425 // using for-in instead of forEach to avoid unecessary iteration after header has been found
10426 defaultHeadersIteration:
10427 for (defHeaderName in defHeaders) {
10428 lowercaseDefHeaderName = lowercase(defHeaderName);
10430 for (reqHeaderName in reqHeaders) {
10431 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) {
10432 continue defaultHeadersIteration;
10436 reqHeaders[defHeaderName] = defHeaders[defHeaderName];
10439 // execute if header value is a function for merged headers
10440 return executeHeaderFns(reqHeaders, shallowCopy(config));
10444 $http.pendingRequests = [];
10451 * Shortcut method to perform `GET` request.
10453 * @param {string} url Relative or absolute URL specifying the destination of the request
10454 * @param {Object=} config Optional configuration object
10455 * @returns {HttpPromise} Future object
10460 * @name $http#delete
10463 * Shortcut method to perform `DELETE` request.
10465 * @param {string} url Relative or absolute URL specifying the destination of the request
10466 * @param {Object=} config Optional configuration object
10467 * @returns {HttpPromise} Future object
10475 * Shortcut method to perform `HEAD` request.
10477 * @param {string} url Relative or absolute URL specifying the destination of the request
10478 * @param {Object=} config Optional configuration object
10479 * @returns {HttpPromise} Future object
10484 * @name $http#jsonp
10487 * Shortcut method to perform `JSONP` request.
10489 * @param {string} url Relative or absolute URL specifying the destination of the request.
10490 * The name of the callback should be the string `JSON_CALLBACK`.
10491 * @param {Object=} config Optional configuration object
10492 * @returns {HttpPromise} Future object
10494 createShortMethods('get', 'delete', 'head', 'jsonp');
10501 * Shortcut method to perform `POST` request.
10503 * @param {string} url Relative or absolute URL specifying the destination of the request
10504 * @param {*} data Request content
10505 * @param {Object=} config Optional configuration object
10506 * @returns {HttpPromise} Future object
10514 * Shortcut method to perform `PUT` request.
10516 * @param {string} url Relative or absolute URL specifying the destination of the request
10517 * @param {*} data Request content
10518 * @param {Object=} config Optional configuration object
10519 * @returns {HttpPromise} Future object
10524 * @name $http#patch
10527 * Shortcut method to perform `PATCH` request.
10529 * @param {string} url Relative or absolute URL specifying the destination of the request
10530 * @param {*} data Request content
10531 * @param {Object=} config Optional configuration object
10532 * @returns {HttpPromise} Future object
10534 createShortMethodsWithData('post', 'put', 'patch');
10538 * @name $http#defaults
10541 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
10542 * default headers, withCredentials as well as request and response transformations.
10544 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
10546 $http.defaults = defaults;
10552 function createShortMethods(names) {
10553 forEach(arguments, function(name) {
10554 $http[name] = function(url, config) {
10555 return $http(extend({}, config || {}, {
10564 function createShortMethodsWithData(name) {
10565 forEach(arguments, function(name) {
10566 $http[name] = function(url, data, config) {
10567 return $http(extend({}, config || {}, {
10578 * Makes the request.
10580 * !!! ACCESSES CLOSURE VARS:
10581 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests
10583 function sendReq(config, reqData) {
10584 var deferred = $q.defer(),
10585 promise = deferred.promise,
10588 reqHeaders = config.headers,
10589 url = buildUrl(config.url, config.paramSerializer(config.params));
10591 $http.pendingRequests.push(config);
10592 promise.then(removePendingReq, removePendingReq);
10595 if ((config.cache || defaults.cache) && config.cache !== false &&
10596 (config.method === 'GET' || config.method === 'JSONP')) {
10597 cache = isObject(config.cache) ? config.cache
10598 : isObject(defaults.cache) ? defaults.cache
10603 cachedResp = cache.get(url);
10604 if (isDefined(cachedResp)) {
10605 if (isPromiseLike(cachedResp)) {
10606 // cached request has already been sent, but there is no response yet
10607 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult);
10609 // serving from cache
10610 if (isArray(cachedResp)) {
10611 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]);
10613 resolvePromise(cachedResp, 200, {}, 'OK');
10617 // put the promise for the non-transformed response into cache as a placeholder
10618 cache.put(url, promise);
10623 // if we won't have the response in cache, set the xsrf headers and
10624 // send the request to the backend
10625 if (isUndefined(cachedResp)) {
10626 var xsrfValue = urlIsSameOrigin(config.url)
10627 ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName]
10630 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
10633 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
10634 config.withCredentials, config.responseType);
10641 * Callback registered to $httpBackend():
10642 * - caches the response if desired
10643 * - resolves the raw $http promise
10646 function done(status, response, headersString, statusText) {
10648 if (isSuccess(status)) {
10649 cache.put(url, [status, response, parseHeaders(headersString), statusText]);
10651 // remove promise from the cache
10656 function resolveHttpPromise() {
10657 resolvePromise(response, status, headersString, statusText);
10660 if (useApplyAsync) {
10661 $rootScope.$applyAsync(resolveHttpPromise);
10663 resolveHttpPromise();
10664 if (!$rootScope.$$phase) $rootScope.$apply();
10670 * Resolves the raw $http promise.
10672 function resolvePromise(response, status, headers, statusText) {
10673 //status: HTTP response status code, 0, -1 (aborted by timeout / promise)
10674 status = status >= -1 ? status : 0;
10676 (isSuccess(status) ? deferred.resolve : deferred.reject)({
10679 headers: headersGetter(headers),
10681 statusText: statusText
10685 function resolvePromiseWithResult(result) {
10686 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText);
10689 function removePendingReq() {
10690 var idx = $http.pendingRequests.indexOf(config);
10691 if (idx !== -1) $http.pendingRequests.splice(idx, 1);
10696 function buildUrl(url, serializedParams) {
10697 if (serializedParams.length > 0) {
10698 url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams;
10707 * @name $xhrFactory
10710 * Factory function used to create XMLHttpRequest objects.
10712 * Replace or decorate this service to create your own custom XMLHttpRequest objects.
10715 * angular.module('myApp', [])
10716 * .factory('$xhrFactory', function() {
10717 * return function createXhr(method, url) {
10718 * return new window.XMLHttpRequest({mozSystem: true});
10723 * @param {string} method HTTP method of the request (GET, POST, PUT, ..)
10724 * @param {string} url URL of the request.
10726 function $xhrFactoryProvider() {
10727 this.$get = function() {
10728 return function createXhr() {
10729 return new window.XMLHttpRequest();
10736 * @name $httpBackend
10737 * @requires $window
10738 * @requires $document
10739 * @requires $xhrFactory
10742 * HTTP backend used by the {@link ng.$http service} that delegates to
10743 * XMLHttpRequest object or JSONP and deals with browser incompatibilities.
10745 * You should never need to use this service directly, instead use the higher-level abstractions:
10746 * {@link ng.$http $http} or {@link ngResource.$resource $resource}.
10748 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock
10749 * $httpBackend} which can be trained with responses.
10751 function $HttpBackendProvider() {
10752 this.$get = ['$browser', '$window', '$document', '$xhrFactory', function($browser, $window, $document, $xhrFactory) {
10753 return createHttpBackend($browser, $xhrFactory, $browser.defer, $window.angular.callbacks, $document[0]);
10757 function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
10758 // TODO(vojta): fix the signature
10759 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
10760 $browser.$$incOutstandingRequestCount();
10761 url = url || $browser.url();
10763 if (lowercase(method) == 'jsonp') {
10764 var callbackId = '_' + (callbacks.counter++).toString(36);
10765 callbacks[callbackId] = function(data) {
10766 callbacks[callbackId].data = data;
10767 callbacks[callbackId].called = true;
10770 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId),
10771 callbackId, function(status, text) {
10772 completeRequest(callback, status, callbacks[callbackId].data, "", text);
10773 callbacks[callbackId] = noop;
10777 var xhr = createXhr(method, url);
10779 xhr.open(method, url, true);
10780 forEach(headers, function(value, key) {
10781 if (isDefined(value)) {
10782 xhr.setRequestHeader(key, value);
10786 xhr.onload = function requestLoaded() {
10787 var statusText = xhr.statusText || '';
10789 // responseText is the old-school way of retrieving response (supported by IE9)
10790 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
10791 var response = ('response' in xhr) ? xhr.response : xhr.responseText;
10793 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
10794 var status = xhr.status === 1223 ? 204 : xhr.status;
10796 // fix status code when it is 0 (0 status is undocumented).
10797 // Occurs when accessing file resources or on Android 4.1 stock browser
10798 // while retrieving files from application cache.
10799 if (status === 0) {
10800 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0;
10803 completeRequest(callback,
10806 xhr.getAllResponseHeaders(),
10810 var requestError = function() {
10811 // The response is always empty
10812 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
10813 completeRequest(callback, -1, null, null, '');
10816 xhr.onerror = requestError;
10817 xhr.onabort = requestError;
10819 if (withCredentials) {
10820 xhr.withCredentials = true;
10823 if (responseType) {
10825 xhr.responseType = responseType;
10827 // WebKit added support for the json responseType value on 09/03/2013
10828 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are
10829 // known to throw when setting the value "json" as the response type. Other older
10830 // browsers implementing the responseType
10832 // The json response type can be ignored if not supported, because JSON payloads are
10833 // parsed on the client-side regardless.
10834 if (responseType !== 'json') {
10840 xhr.send(isUndefined(post) ? null : post);
10844 var timeoutId = $browserDefer(timeoutRequest, timeout);
10845 } else if (isPromiseLike(timeout)) {
10846 timeout.then(timeoutRequest);
10850 function timeoutRequest() {
10851 jsonpDone && jsonpDone();
10852 xhr && xhr.abort();
10855 function completeRequest(callback, status, response, headersString, statusText) {
10856 // cancel timeout and subsequent timeout promise resolution
10857 if (isDefined(timeoutId)) {
10858 $browserDefer.cancel(timeoutId);
10860 jsonpDone = xhr = null;
10862 callback(status, response, headersString, statusText);
10863 $browser.$$completeOutstandingRequest(noop);
10867 function jsonpReq(url, callbackId, done) {
10868 // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.:
10869 // - fetches local scripts via XHR and evals them
10870 // - adds and immediately removes script elements from the document
10871 var script = rawDocument.createElement('script'), callback = null;
10872 script.type = "text/javascript";
10874 script.async = true;
10876 callback = function(event) {
10877 removeEventListenerFn(script, "load", callback);
10878 removeEventListenerFn(script, "error", callback);
10879 rawDocument.body.removeChild(script);
10882 var text = "unknown";
10885 if (event.type === "load" && !callbacks[callbackId].called) {
10886 event = { type: "error" };
10889 status = event.type === "error" ? 404 : 200;
10893 done(status, text);
10897 addEventListenerFn(script, "load", callback);
10898 addEventListenerFn(script, "error", callback);
10899 rawDocument.body.appendChild(script);
10904 var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate');
10905 $interpolateMinErr.throwNoconcat = function(text) {
10906 throw $interpolateMinErr('noconcat',
10907 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
10908 "interpolations that concatenate multiple expressions when a trusted value is " +
10909 "required. See http://docs.angularjs.org/api/ng.$sce", text);
10912 $interpolateMinErr.interr = function(text, err) {
10913 return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString());
10918 * @name $interpolateProvider
10922 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
10925 <example module="customInterpolationApp">
10926 <file name="index.html">
10928 var customInterpolationApp = angular.module('customInterpolationApp', []);
10930 customInterpolationApp.config(function($interpolateProvider) {
10931 $interpolateProvider.startSymbol('//');
10932 $interpolateProvider.endSymbol('//');
10936 customInterpolationApp.controller('DemoController', function() {
10937 this.label = "This binding is brought you by // interpolation symbols.";
10940 <div ng-app="App" ng-controller="DemoController as demo">
10944 <file name="protractor.js" type="protractor">
10945 it('should interpolate binding with custom symbols', function() {
10946 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
10951 function $InterpolateProvider() {
10952 var startSymbol = '{{';
10953 var endSymbol = '}}';
10957 * @name $interpolateProvider#startSymbol
10959 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
10961 * @param {string=} value new value to set the starting symbol to.
10962 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
10964 this.startSymbol = function(value) {
10966 startSymbol = value;
10969 return startSymbol;
10975 * @name $interpolateProvider#endSymbol
10977 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
10979 * @param {string=} value new value to set the ending symbol to.
10980 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
10982 this.endSymbol = function(value) {
10992 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
10993 var startSymbolLength = startSymbol.length,
10994 endSymbolLength = endSymbol.length,
10995 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'),
10996 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g');
10998 function escape(ch) {
10999 return '\\\\\\' + ch;
11002 function unescapeText(text) {
11003 return text.replace(escapedStartRegexp, startSymbol).
11004 replace(escapedEndRegexp, endSymbol);
11007 function stringify(value) {
11008 if (value == null) { // null || undefined
11011 switch (typeof value) {
11015 value = '' + value;
11018 value = toJson(value);
11026 * @name $interpolate
11034 * Compiles a string with markup into an interpolation function. This service is used by the
11035 * HTML {@link ng.$compile $compile} service for data binding. See
11036 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
11037 * interpolation markup.
11041 * var $interpolate = ...; // injected
11042 * var exp = $interpolate('Hello {{name | uppercase}}!');
11043 * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!');
11046 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is
11047 * `true`, the interpolation function will return `undefined` unless all embedded expressions
11048 * evaluate to a value other than `undefined`.
11051 * var $interpolate = ...; // injected
11052 * var context = {greeting: 'Hello', name: undefined };
11054 * // default "forgiving" mode
11055 * var exp = $interpolate('{{greeting}} {{name}}!');
11056 * expect(exp(context)).toEqual('Hello !');
11058 * // "allOrNothing" mode
11059 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true);
11060 * expect(exp(context)).toBeUndefined();
11061 * context.name = 'Angular';
11062 * expect(exp(context)).toEqual('Hello Angular!');
11065 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior.
11067 * ####Escaped Interpolation
11068 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers
11069 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash).
11070 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression
11073 * This enables web-servers to prevent script injection attacks and defacing attacks, to some
11074 * degree, while also enabling code examples to work without relying on the
11075 * {@link ng.directive:ngNonBindable ngNonBindable} directive.
11077 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data,
11078 * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all
11079 * interpolation start/end markers with their escaped counterparts.**
11081 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered
11082 * output when the $interpolate service processes the text. So, for HTML elements interpolated
11083 * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter
11084 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such,
11085 * this is typically useful only when user-data is used in rendering a template from the server, or
11086 * when otherwise untrusted data is used by a directive.
11089 * <file name="index.html">
11090 * <div ng-init="username='A user'">
11091 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\}
11093 * <p><strong>{{username}}</strong> attempts to inject code which will deface the
11094 * application, but fails to accomplish their task, because the server has correctly
11095 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash)
11097 * <p>Instead, the result of the attempted script injection is visible, and can be removed
11098 * from the database by an administrator.</p>
11103 * @param {string} text The text with markup to interpolate.
11104 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
11105 * embedded expression in order to return an interpolation function. Strings with no
11106 * embedded expression will return null for the interpolation function.
11107 * @param {string=} trustedContext when provided, the returned function passes the interpolated
11108 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult,
11109 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that
11110 * provides Strict Contextual Escaping for details.
11111 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined
11112 * unless all embedded expressions evaluate to a value other than `undefined`.
11113 * @returns {function(context)} an interpolation function which is used to compute the
11114 * interpolated string. The function has these parameters:
11116 * - `context`: evaluation context for all expressions embedded in the interpolated text
11118 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
11119 allOrNothing = !!allOrNothing;
11125 textLength = text.length,
11128 expressionPositions = [];
11130 while (index < textLength) {
11131 if (((startIndex = text.indexOf(startSymbol, index)) != -1) &&
11132 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) {
11133 if (index !== startIndex) {
11134 concat.push(unescapeText(text.substring(index, startIndex)));
11136 exp = text.substring(startIndex + startSymbolLength, endIndex);
11137 expressions.push(exp);
11138 parseFns.push($parse(exp, parseStringifyInterceptor));
11139 index = endIndex + endSymbolLength;
11140 expressionPositions.push(concat.length);
11143 // we did not find an interpolation, so we have to add the remainder to the separators array
11144 if (index !== textLength) {
11145 concat.push(unescapeText(text.substring(index)));
11151 // Concatenating expressions makes it hard to reason about whether some combination of
11152 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a
11153 // single expression be used for iframe[src], object[src], etc., we ensure that the value
11154 // that's used is assigned or constructed by some JS code somewhere that is more testable or
11155 // make it obvious that you bound the value to some user controlled value. This helps reduce
11156 // the load when auditing for XSS issues.
11157 if (trustedContext && concat.length > 1) {
11158 $interpolateMinErr.throwNoconcat(text);
11161 if (!mustHaveExpression || expressions.length) {
11162 var compute = function(values) {
11163 for (var i = 0, ii = expressions.length; i < ii; i++) {
11164 if (allOrNothing && isUndefined(values[i])) return;
11165 concat[expressionPositions[i]] = values[i];
11167 return concat.join('');
11170 var getValue = function(value) {
11171 return trustedContext ?
11172 $sce.getTrusted(trustedContext, value) :
11173 $sce.valueOf(value);
11176 return extend(function interpolationFn(context) {
11178 var ii = expressions.length;
11179 var values = new Array(ii);
11182 for (; i < ii; i++) {
11183 values[i] = parseFns[i](context);
11186 return compute(values);
11188 $exceptionHandler($interpolateMinErr.interr(text, err));
11192 // all of these properties are undocumented for now
11193 exp: text, //just for compatibility with regular watchers created via $watch
11194 expressions: expressions,
11195 $$watchDelegate: function(scope, listener) {
11197 return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) {
11198 var currValue = compute(values);
11199 if (isFunction(listener)) {
11200 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
11202 lastValue = currValue;
11208 function parseStringifyInterceptor(value) {
11210 value = getValue(value);
11211 return allOrNothing && !isDefined(value) ? value : stringify(value);
11213 $exceptionHandler($interpolateMinErr.interr(text, err));
11221 * @name $interpolate#startSymbol
11223 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
11225 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change
11228 * @returns {string} start symbol.
11230 $interpolate.startSymbol = function() {
11231 return startSymbol;
11237 * @name $interpolate#endSymbol
11239 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
11241 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change
11244 * @returns {string} end symbol.
11246 $interpolate.endSymbol = function() {
11250 return $interpolate;
11254 function $IntervalProvider() {
11255 this.$get = ['$rootScope', '$window', '$q', '$$q',
11256 function($rootScope, $window, $q, $$q) {
11257 var intervals = {};
11265 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay`
11268 * The return value of registering an interval function is a promise. This promise will be
11269 * notified upon each tick of the interval, and will be resolved after `count` iterations, or
11270 * run indefinitely if `count` is not defined. The value of the notification will be the
11271 * number of iterations that have run.
11272 * To cancel an interval, call `$interval.cancel(promise)`.
11274 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to
11275 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that
11278 * <div class="alert alert-warning">
11279 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished
11280 * with them. In particular they are not automatically destroyed when a controller's scope or a
11281 * directive's element are destroyed.
11282 * You should take this into consideration and make sure to always cancel the interval at the
11283 * appropriate moment. See the example below for more details on how and when to do this.
11286 * @param {function()} fn A function that should be called repeatedly.
11287 * @param {number} delay Number of milliseconds between each function call.
11288 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
11290 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
11291 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
11292 * @param {...*=} Pass additional parameters to the executed function.
11293 * @returns {promise} A promise which will be notified on each iteration.
11296 * <example module="intervalExample">
11297 * <file name="index.html">
11299 * angular.module('intervalExample', [])
11300 * .controller('ExampleController', ['$scope', '$interval',
11301 * function($scope, $interval) {
11302 * $scope.format = 'M/d/yy h:mm:ss a';
11303 * $scope.blood_1 = 100;
11304 * $scope.blood_2 = 120;
11307 * $scope.fight = function() {
11308 * // Don't start a new fight if we are already fighting
11309 * if ( angular.isDefined(stop) ) return;
11311 * stop = $interval(function() {
11312 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
11313 * $scope.blood_1 = $scope.blood_1 - 3;
11314 * $scope.blood_2 = $scope.blood_2 - 4;
11316 * $scope.stopFight();
11321 * $scope.stopFight = function() {
11322 * if (angular.isDefined(stop)) {
11323 * $interval.cancel(stop);
11324 * stop = undefined;
11328 * $scope.resetFight = function() {
11329 * $scope.blood_1 = 100;
11330 * $scope.blood_2 = 120;
11333 * $scope.$on('$destroy', function() {
11334 * // Make sure that the interval is destroyed too
11335 * $scope.stopFight();
11338 * // Register the 'myCurrentTime' directive factory method.
11339 * // We inject $interval and dateFilter service since the factory method is DI.
11340 * .directive('myCurrentTime', ['$interval', 'dateFilter',
11341 * function($interval, dateFilter) {
11342 * // return the directive link function. (compile function not needed)
11343 * return function(scope, element, attrs) {
11344 * var format, // date format
11345 * stopTime; // so that we can cancel the time updates
11347 * // used to update the UI
11348 * function updateTime() {
11349 * element.text(dateFilter(new Date(), format));
11352 * // watch the expression, and update the UI on change.
11353 * scope.$watch(attrs.myCurrentTime, function(value) {
11358 * stopTime = $interval(updateTime, 1000);
11360 * // listen on DOM destroy (removal) event, and cancel the next UI update
11361 * // to prevent updating time after the DOM element was removed.
11362 * element.on('$destroy', function() {
11363 * $interval.cancel(stopTime);
11370 * <div ng-controller="ExampleController">
11371 * <label>Date format: <input ng-model="format"></label> <hr/>
11372 * Current time is: <span my-current-time="format"></span>
11374 * Blood 1 : <font color='red'>{{blood_1}}</font>
11375 * Blood 2 : <font color='red'>{{blood_2}}</font>
11376 * <button type="button" data-ng-click="fight()">Fight</button>
11377 * <button type="button" data-ng-click="stopFight()">StopFight</button>
11378 * <button type="button" data-ng-click="resetFight()">resetFight</button>
11385 function interval(fn, delay, count, invokeApply) {
11386 var hasParams = arguments.length > 4,
11387 args = hasParams ? sliceArgs(arguments, 4) : [],
11388 setInterval = $window.setInterval,
11389 clearInterval = $window.clearInterval,
11391 skipApply = (isDefined(invokeApply) && !invokeApply),
11392 deferred = (skipApply ? $$q : $q).defer(),
11393 promise = deferred.promise;
11395 count = isDefined(count) ? count : 0;
11397 promise.then(null, null, (!hasParams) ? fn : function() {
11398 fn.apply(null, args);
11401 promise.$$intervalId = setInterval(function tick() {
11402 deferred.notify(iteration++);
11404 if (count > 0 && iteration >= count) {
11405 deferred.resolve(iteration);
11406 clearInterval(promise.$$intervalId);
11407 delete intervals[promise.$$intervalId];
11410 if (!skipApply) $rootScope.$apply();
11414 intervals[promise.$$intervalId] = deferred;
11422 * @name $interval#cancel
11425 * Cancels a task associated with the `promise`.
11427 * @param {Promise=} promise returned by the `$interval` function.
11428 * @returns {boolean} Returns `true` if the task was successfully canceled.
11430 interval.cancel = function(promise) {
11431 if (promise && promise.$$intervalId in intervals) {
11432 intervals[promise.$$intervalId].reject('canceled');
11433 $window.clearInterval(promise.$$intervalId);
11434 delete intervals[promise.$$intervalId];
11449 * $locale service provides localization rules for various Angular components. As of right now the
11450 * only public api is:
11452 * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
11455 var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/,
11456 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
11457 var $locationMinErr = minErr('$location');
11461 * Encode path using encodeUriSegment, ignoring forward slashes
11463 * @param {string} path Path to encode
11464 * @returns {string}
11466 function encodePath(path) {
11467 var segments = path.split('/'),
11468 i = segments.length;
11471 segments[i] = encodeUriSegment(segments[i]);
11474 return segments.join('/');
11477 function parseAbsoluteUrl(absoluteUrl, locationObj) {
11478 var parsedUrl = urlResolve(absoluteUrl);
11480 locationObj.$$protocol = parsedUrl.protocol;
11481 locationObj.$$host = parsedUrl.hostname;
11482 locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
11486 function parseAppUrl(relativeUrl, locationObj) {
11487 var prefixed = (relativeUrl.charAt(0) !== '/');
11489 relativeUrl = '/' + relativeUrl;
11491 var match = urlResolve(relativeUrl);
11492 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ?
11493 match.pathname.substring(1) : match.pathname);
11494 locationObj.$$search = parseKeyValue(match.search);
11495 locationObj.$$hash = decodeURIComponent(match.hash);
11497 // make sure path starts with '/';
11498 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') {
11499 locationObj.$$path = '/' + locationObj.$$path;
11506 * @param {string} begin
11507 * @param {string} whole
11508 * @returns {string} returns text from whole after begin or undefined if it does not begin with
11511 function beginsWith(begin, whole) {
11512 if (whole.indexOf(begin) === 0) {
11513 return whole.substr(begin.length);
11518 function stripHash(url) {
11519 var index = url.indexOf('#');
11520 return index == -1 ? url : url.substr(0, index);
11523 function trimEmptyHash(url) {
11524 return url.replace(/(#.+)|#$/, '$1');
11528 function stripFile(url) {
11529 return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
11532 /* return the server only (scheme://host:port) */
11533 function serverBase(url) {
11534 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2));
11539 * LocationHtml5Url represents an url
11540 * This object is exposed as $location service when HTML5 mode is enabled and supported
11543 * @param {string} appBase application base URL
11544 * @param {string} appBaseNoFile application base URL stripped of any filename
11545 * @param {string} basePrefix url path prefix
11547 function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
11548 this.$$html5 = true;
11549 basePrefix = basePrefix || '';
11550 parseAbsoluteUrl(appBase, this);
11554 * Parse given html5 (regular) url string into properties
11555 * @param {string} url HTML5 url
11558 this.$$parse = function(url) {
11559 var pathUrl = beginsWith(appBaseNoFile, url);
11560 if (!isString(pathUrl)) {
11561 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url,
11565 parseAppUrl(pathUrl, this);
11567 if (!this.$$path) {
11575 * Compose url and update `absUrl` property
11578 this.$$compose = function() {
11579 var search = toKeyValue(this.$$search),
11580 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11582 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11583 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/'
11586 this.$$parseLinkUrl = function(url, relHref) {
11587 if (relHref && relHref[0] === '#') {
11588 // special case for links to hash fragments:
11589 // keep the old url and only replace the hash fragment
11590 this.hash(relHref.slice(1));
11593 var appUrl, prevAppUrl;
11596 if (isDefined(appUrl = beginsWith(appBase, url))) {
11597 prevAppUrl = appUrl;
11598 if (isDefined(appUrl = beginsWith(basePrefix, appUrl))) {
11599 rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
11601 rewrittenUrl = appBase + prevAppUrl;
11603 } else if (isDefined(appUrl = beginsWith(appBaseNoFile, url))) {
11604 rewrittenUrl = appBaseNoFile + appUrl;
11605 } else if (appBaseNoFile == url + '/') {
11606 rewrittenUrl = appBaseNoFile;
11608 if (rewrittenUrl) {
11609 this.$$parse(rewrittenUrl);
11611 return !!rewrittenUrl;
11617 * LocationHashbangUrl represents url
11618 * This object is exposed as $location service when developer doesn't opt into html5 mode.
11619 * It also serves as the base class for html5 mode fallback on legacy browsers.
11622 * @param {string} appBase application base URL
11623 * @param {string} appBaseNoFile application base URL stripped of any filename
11624 * @param {string} hashPrefix hashbang prefix
11626 function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
11628 parseAbsoluteUrl(appBase, this);
11632 * Parse given hashbang url into properties
11633 * @param {string} url Hashbang url
11636 this.$$parse = function(url) {
11637 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
11638 var withoutHashUrl;
11640 if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') {
11642 // The rest of the url starts with a hash so we have
11643 // got either a hashbang path or a plain hash fragment
11644 withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl);
11645 if (isUndefined(withoutHashUrl)) {
11646 // There was no hashbang prefix so we just have a hash fragment
11647 withoutHashUrl = withoutBaseUrl;
11651 // There was no hashbang path nor hash fragment:
11652 // If we are in HTML5 mode we use what is left as the path;
11653 // Otherwise we ignore what is left
11654 if (this.$$html5) {
11655 withoutHashUrl = withoutBaseUrl;
11657 withoutHashUrl = '';
11658 if (isUndefined(withoutBaseUrl)) {
11665 parseAppUrl(withoutHashUrl, this);
11667 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
11672 * In Windows, on an anchor node on documents loaded from
11673 * the filesystem, the browser will return a pathname
11674 * prefixed with the drive name ('/C:/path') when a
11675 * pathname without a drive is set:
11676 * * a.setAttribute('href', '/foo')
11677 * * a.pathname === '/C:/foo' //true
11679 * Inside of Angular, we're always using pathnames that
11680 * do not include drive names for routing.
11682 function removeWindowsDriveName(path, url, base) {
11684 Matches paths for file protocol on windows,
11685 such as /C:/foo/bar, and captures only /foo/bar.
11687 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/;
11689 var firstPathSegmentMatch;
11691 //Get the relative path from the input URL.
11692 if (url.indexOf(base) === 0) {
11693 url = url.replace(base, '');
11696 // The input URL intentionally contains a first path segment that ends with a colon.
11697 if (windowsFilePathExp.exec(url)) {
11701 firstPathSegmentMatch = windowsFilePathExp.exec(path);
11702 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
11707 * Compose hashbang url and update `absUrl` property
11710 this.$$compose = function() {
11711 var search = toKeyValue(this.$$search),
11712 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11714 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11715 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : '');
11718 this.$$parseLinkUrl = function(url, relHref) {
11719 if (stripHash(appBase) == stripHash(url)) {
11729 * LocationHashbangUrl represents url
11730 * This object is exposed as $location service when html5 history api is enabled but the browser
11731 * does not support it.
11734 * @param {string} appBase application base URL
11735 * @param {string} appBaseNoFile application base URL stripped of any filename
11736 * @param {string} hashPrefix hashbang prefix
11738 function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
11739 this.$$html5 = true;
11740 LocationHashbangUrl.apply(this, arguments);
11742 this.$$parseLinkUrl = function(url, relHref) {
11743 if (relHref && relHref[0] === '#') {
11744 // special case for links to hash fragments:
11745 // keep the old url and only replace the hash fragment
11746 this.hash(relHref.slice(1));
11753 if (appBase == stripHash(url)) {
11754 rewrittenUrl = url;
11755 } else if ((appUrl = beginsWith(appBaseNoFile, url))) {
11756 rewrittenUrl = appBase + hashPrefix + appUrl;
11757 } else if (appBaseNoFile === url + '/') {
11758 rewrittenUrl = appBaseNoFile;
11760 if (rewrittenUrl) {
11761 this.$$parse(rewrittenUrl);
11763 return !!rewrittenUrl;
11766 this.$$compose = function() {
11767 var search = toKeyValue(this.$$search),
11768 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11770 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11771 // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#'
11772 this.$$absUrl = appBase + hashPrefix + this.$$url;
11778 var locationPrototype = {
11781 * Are we in html5 mode?
11787 * Has any change been replacing?
11794 * @name $location#absUrl
11797 * This method is getter only.
11799 * Return full url representation with all segments encoded according to rules specified in
11800 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
11804 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11805 * var absUrl = $location.absUrl();
11806 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
11809 * @return {string} full url
11811 absUrl: locationGetter('$$absUrl'),
11815 * @name $location#url
11818 * This method is getter / setter.
11820 * Return url (e.g. `/path?a=b#hash`) when called without any parameter.
11822 * Change path, search and hash, when called with parameter and return `$location`.
11826 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11827 * var url = $location.url();
11828 * // => "/some/path?foo=bar&baz=xoxo"
11831 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
11832 * @return {string} url
11834 url: function(url) {
11835 if (isUndefined(url)) {
11839 var match = PATH_MATCH.exec(url);
11840 if (match[1] || url === '') this.path(decodeURIComponent(match[1]));
11841 if (match[2] || match[1] || url === '') this.search(match[3] || '');
11842 this.hash(match[5] || '');
11849 * @name $location#protocol
11852 * This method is getter only.
11854 * Return protocol of current url.
11858 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11859 * var protocol = $location.protocol();
11863 * @return {string} protocol of current url
11865 protocol: locationGetter('$$protocol'),
11869 * @name $location#host
11872 * This method is getter only.
11874 * Return host of current url.
11876 * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.
11880 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11881 * var host = $location.host();
11882 * // => "example.com"
11884 * // given url http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
11885 * host = $location.host();
11886 * // => "example.com"
11887 * host = location.host;
11888 * // => "example.com:8080"
11891 * @return {string} host of current url.
11893 host: locationGetter('$$host'),
11897 * @name $location#port
11900 * This method is getter only.
11902 * Return port of current url.
11906 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11907 * var port = $location.port();
11911 * @return {Number} port
11913 port: locationGetter('$$port'),
11917 * @name $location#path
11920 * This method is getter / setter.
11922 * Return path of current url when called without any parameter.
11924 * Change path when called with parameter and return `$location`.
11926 * Note: Path should always begin with forward slash (/), this method will add the forward slash
11927 * if it is missing.
11931 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11932 * var path = $location.path();
11933 * // => "/some/path"
11936 * @param {(string|number)=} path New path
11937 * @return {string} path
11939 path: locationGetterSetter('$$path', function(path) {
11940 path = path !== null ? path.toString() : '';
11941 return path.charAt(0) == '/' ? path : '/' + path;
11946 * @name $location#search
11949 * This method is getter / setter.
11951 * Return search part (as object) of current url when called without any parameter.
11953 * Change search part when called with parameter and return `$location`.
11957 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11958 * var searchObject = $location.search();
11959 * // => {foo: 'bar', baz: 'xoxo'}
11961 * // set foo to 'yipee'
11962 * $location.search('foo', 'yipee');
11963 * // $location.search() => {foo: 'yipee', baz: 'xoxo'}
11966 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or
11969 * When called with a single argument the method acts as a setter, setting the `search` component
11970 * of `$location` to the specified value.
11972 * If the argument is a hash object containing an array of values, these values will be encoded
11973 * as duplicate search parameters in the url.
11975 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue`
11976 * will override only a single search property.
11978 * If `paramValue` is an array, it will override the property of the `search` component of
11979 * `$location` specified via the first argument.
11981 * If `paramValue` is `null`, the property specified via the first argument will be deleted.
11983 * If `paramValue` is `true`, the property specified via the first argument will be added with no
11984 * value nor trailing equal sign.
11986 * @return {Object} If called with no arguments returns the parsed `search` object. If called with
11987 * one or more arguments returns `$location` object itself.
11989 search: function(search, paramValue) {
11990 switch (arguments.length) {
11992 return this.$$search;
11994 if (isString(search) || isNumber(search)) {
11995 search = search.toString();
11996 this.$$search = parseKeyValue(search);
11997 } else if (isObject(search)) {
11998 search = copy(search, {});
11999 // remove object undefined or null properties
12000 forEach(search, function(value, key) {
12001 if (value == null) delete search[key];
12004 this.$$search = search;
12006 throw $locationMinErr('isrcharg',
12007 'The first argument of the `$location#search()` call must be a string or an object.');
12011 if (isUndefined(paramValue) || paramValue === null) {
12012 delete this.$$search[search];
12014 this.$$search[search] = paramValue;
12024 * @name $location#hash
12027 * This method is getter / setter.
12029 * Returns the hash fragment when called without any parameters.
12031 * Changes the hash fragment when called with a parameter and returns `$location`.
12035 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
12036 * var hash = $location.hash();
12037 * // => "hashValue"
12040 * @param {(string|number)=} hash New hash fragment
12041 * @return {string} hash
12043 hash: locationGetterSetter('$$hash', function(hash) {
12044 return hash !== null ? hash.toString() : '';
12049 * @name $location#replace
12052 * If called, all changes to $location during the current `$digest` will replace the current history
12053 * record, instead of adding a new one.
12055 replace: function() {
12056 this.$$replace = true;
12061 forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) {
12062 Location.prototype = Object.create(locationPrototype);
12066 * @name $location#state
12069 * This method is getter / setter.
12071 * Return the history state object when called without any parameter.
12073 * Change the history state object when called with one parameter and return `$location`.
12074 * The state object is later passed to `pushState` or `replaceState`.
12076 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting
12077 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
12078 * older browsers (like IE9 or Android < 4.0), don't use this method.
12080 * @param {object=} state State object for pushState or replaceState
12081 * @return {object} state
12083 Location.prototype.state = function(state) {
12084 if (!arguments.length) {
12085 return this.$$state;
12088 if (Location !== LocationHtml5Url || !this.$$html5) {
12089 throw $locationMinErr('nostate', 'History API state support is available only ' +
12090 'in HTML5 mode and only in browsers supporting HTML5 History API');
12092 // The user might modify `stateObject` after invoking `$location.state(stateObject)`
12093 // but we're changing the $$state reference to $browser.state() during the $digest
12094 // so the modification window is narrow.
12095 this.$$state = isUndefined(state) ? null : state;
12102 function locationGetter(property) {
12103 return function() {
12104 return this[property];
12109 function locationGetterSetter(property, preprocess) {
12110 return function(value) {
12111 if (isUndefined(value)) {
12112 return this[property];
12115 this[property] = preprocess(value);
12127 * @requires $rootElement
12130 * The $location service parses the URL in the browser address bar (based on the
12131 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL
12132 * available to your application. Changes to the URL in the address bar are reflected into
12133 * $location service and changes to $location are reflected into the browser address bar.
12135 * **The $location service:**
12137 * - Exposes the current URL in the browser address bar, so you can
12138 * - Watch and observe the URL.
12139 * - Change the URL.
12140 * - Synchronizes the URL with the browser when the user
12141 * - Changes the address bar.
12142 * - Clicks the back or forward button (or clicks a History link).
12143 * - Clicks on a link.
12144 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
12146 * For more information see {@link guide/$location Developer Guide: Using $location}
12151 * @name $locationProvider
12153 * Use the `$locationProvider` to configure how the application deep linking paths are stored.
12155 function $LocationProvider() {
12156 var hashPrefix = '',
12165 * @name $locationProvider#hashPrefix
12167 * @param {string=} prefix Prefix for hash part (containing path and search)
12168 * @returns {*} current value if used as getter or itself (chaining) if used as setter
12170 this.hashPrefix = function(prefix) {
12171 if (isDefined(prefix)) {
12172 hashPrefix = prefix;
12181 * @name $locationProvider#html5Mode
12183 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value.
12184 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported
12186 * - **enabled** – `{boolean}` – (default: false) If true, will rely on `history.pushState` to
12187 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not
12188 * support `pushState`.
12189 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies
12190 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are
12191 * true, and a base tag is not present, an error will be thrown when `$location` is injected.
12192 * See the {@link guide/$location $location guide for more information}
12193 * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled,
12194 * enables/disables url rewriting for relative links.
12196 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
12198 this.html5Mode = function(mode) {
12199 if (isBoolean(mode)) {
12200 html5Mode.enabled = mode;
12202 } else if (isObject(mode)) {
12204 if (isBoolean(mode.enabled)) {
12205 html5Mode.enabled = mode.enabled;
12208 if (isBoolean(mode.requireBase)) {
12209 html5Mode.requireBase = mode.requireBase;
12212 if (isBoolean(mode.rewriteLinks)) {
12213 html5Mode.rewriteLinks = mode.rewriteLinks;
12224 * @name $location#$locationChangeStart
12225 * @eventType broadcast on root scope
12227 * Broadcasted before a URL will change.
12229 * This change can be prevented by calling
12230 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
12231 * details about event object. Upon successful change
12232 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired.
12234 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
12235 * the browser supports the HTML5 History API.
12237 * @param {Object} angularEvent Synthetic event object.
12238 * @param {string} newUrl New URL
12239 * @param {string=} oldUrl URL that was before it was changed.
12240 * @param {string=} newState New history state object
12241 * @param {string=} oldState History state object that was before it was changed.
12246 * @name $location#$locationChangeSuccess
12247 * @eventType broadcast on root scope
12249 * Broadcasted after a URL was changed.
12251 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
12252 * the browser supports the HTML5 History API.
12254 * @param {Object} angularEvent Synthetic event object.
12255 * @param {string} newUrl New URL
12256 * @param {string=} oldUrl URL that was before it was changed.
12257 * @param {string=} newState New history state object
12258 * @param {string=} oldState History state object that was before it was changed.
12261 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window',
12262 function($rootScope, $browser, $sniffer, $rootElement, $window) {
12265 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to ''
12266 initialUrl = $browser.url(),
12269 if (html5Mode.enabled) {
12270 if (!baseHref && html5Mode.requireBase) {
12271 throw $locationMinErr('nobase',
12272 "$location in HTML5 mode requires a <base> tag to be present!");
12274 appBase = serverBase(initialUrl) + (baseHref || '/');
12275 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url;
12277 appBase = stripHash(initialUrl);
12278 LocationMode = LocationHashbangUrl;
12280 var appBaseNoFile = stripFile(appBase);
12282 $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix);
12283 $location.$$parseLinkUrl(initialUrl, initialUrl);
12285 $location.$$state = $browser.state();
12287 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
12289 function setBrowserUrlWithFallback(url, replace, state) {
12290 var oldUrl = $location.url();
12291 var oldState = $location.$$state;
12293 $browser.url(url, replace, state);
12295 // Make sure $location.state() returns referentially identical (not just deeply equal)
12296 // state object; this makes possible quick checking if the state changed in the digest
12297 // loop. Checking deep equality would be too expensive.
12298 $location.$$state = $browser.state();
12300 // Restore old values if pushState fails
12301 $location.url(oldUrl);
12302 $location.$$state = oldState;
12308 $rootElement.on('click', function(event) {
12309 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
12310 // currently we open nice url link and redirect then
12312 if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return;
12314 var elm = jqLite(event.target);
12316 // traverse the DOM up to find first A tag
12317 while (nodeName_(elm[0]) !== 'a') {
12318 // ignore rewriting if no A tag (reached root element, or no parent - removed from document)
12319 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return;
12322 var absHref = elm.prop('href');
12323 // get the actual href attribute - see
12324 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
12325 var relHref = elm.attr('href') || elm.attr('xlink:href');
12327 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') {
12328 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during
12330 absHref = urlResolve(absHref.animVal).href;
12333 // Ignore when url is started with javascript: or mailto:
12334 if (IGNORE_URI_REGEXP.test(absHref)) return;
12336 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) {
12337 if ($location.$$parseLinkUrl(absHref, relHref)) {
12338 // We do a preventDefault for all urls that are part of the angular application,
12339 // in html5mode and also without, so that we are able to abort navigation without
12340 // getting double entries in the location history.
12341 event.preventDefault();
12342 // update location manually
12343 if ($location.absUrl() != $browser.url()) {
12344 $rootScope.$apply();
12345 // hack to work around FF6 bug 684208 when scenario runner clicks on links
12346 $window.angular['ff-684208-preventDefault'] = true;
12353 // rewrite hashbang url <> html5 url
12354 if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) {
12355 $browser.url($location.absUrl(), true);
12358 var initializing = true;
12360 // update $location when $browser url changes
12361 $browser.onUrlChange(function(newUrl, newState) {
12363 if (isUndefined(beginsWith(appBaseNoFile, newUrl))) {
12364 // If we are navigating outside of the app then force a reload
12365 $window.location.href = newUrl;
12369 $rootScope.$evalAsync(function() {
12370 var oldUrl = $location.absUrl();
12371 var oldState = $location.$$state;
12372 var defaultPrevented;
12373 newUrl = trimEmptyHash(newUrl);
12374 $location.$$parse(newUrl);
12375 $location.$$state = newState;
12377 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
12378 newState, oldState).defaultPrevented;
12380 // if the location was changed by a `$locationChangeStart` handler then stop
12381 // processing this location change
12382 if ($location.absUrl() !== newUrl) return;
12384 if (defaultPrevented) {
12385 $location.$$parse(oldUrl);
12386 $location.$$state = oldState;
12387 setBrowserUrlWithFallback(oldUrl, false, oldState);
12389 initializing = false;
12390 afterLocationChange(oldUrl, oldState);
12393 if (!$rootScope.$$phase) $rootScope.$digest();
12397 $rootScope.$watch(function $locationWatch() {
12398 var oldUrl = trimEmptyHash($browser.url());
12399 var newUrl = trimEmptyHash($location.absUrl());
12400 var oldState = $browser.state();
12401 var currentReplace = $location.$$replace;
12402 var urlOrStateChanged = oldUrl !== newUrl ||
12403 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
12405 if (initializing || urlOrStateChanged) {
12406 initializing = false;
12408 $rootScope.$evalAsync(function() {
12409 var newUrl = $location.absUrl();
12410 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
12411 $location.$$state, oldState).defaultPrevented;
12413 // if the location was changed by a `$locationChangeStart` handler then stop
12414 // processing this location change
12415 if ($location.absUrl() !== newUrl) return;
12417 if (defaultPrevented) {
12418 $location.$$parse(oldUrl);
12419 $location.$$state = oldState;
12421 if (urlOrStateChanged) {
12422 setBrowserUrlWithFallback(newUrl, currentReplace,
12423 oldState === $location.$$state ? null : $location.$$state);
12425 afterLocationChange(oldUrl, oldState);
12430 $location.$$replace = false;
12432 // we don't need to return anything because $evalAsync will make the digest loop dirty when
12433 // there is a change
12438 function afterLocationChange(oldUrl, oldState) {
12439 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl,
12440 $location.$$state, oldState);
12448 * @requires $window
12451 * Simple service for logging. Default implementation safely writes the message
12452 * into the browser's console (if present).
12454 * The main purpose of this service is to simplify debugging and troubleshooting.
12456 * The default is to log `debug` messages. You can use
12457 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this.
12460 <example module="logExample">
12461 <file name="script.js">
12462 angular.module('logExample', [])
12463 .controller('LogController', ['$scope', '$log', function($scope, $log) {
12464 $scope.$log = $log;
12465 $scope.message = 'Hello World!';
12468 <file name="index.html">
12469 <div ng-controller="LogController">
12470 <p>Reload this page with open console, enter text and hit the log button...</p>
12472 <input type="text" ng-model="message" /></label>
12473 <button ng-click="$log.log(message)">log</button>
12474 <button ng-click="$log.warn(message)">warn</button>
12475 <button ng-click="$log.info(message)">info</button>
12476 <button ng-click="$log.error(message)">error</button>
12477 <button ng-click="$log.debug(message)">debug</button>
12485 * @name $logProvider
12487 * Use the `$logProvider` to configure how the application logs messages
12489 function $LogProvider() {
12495 * @name $logProvider#debugEnabled
12497 * @param {boolean=} flag enable or disable debug level messages
12498 * @returns {*} current value if used as getter or itself (chaining) if used as setter
12500 this.debugEnabled = function(flag) {
12501 if (isDefined(flag)) {
12509 this.$get = ['$window', function($window) {
12516 * Write a log message
12518 log: consoleLog('log'),
12525 * Write an information message
12527 info: consoleLog('info'),
12534 * Write a warning message
12536 warn: consoleLog('warn'),
12543 * Write an error message
12545 error: consoleLog('error'),
12552 * Write a debug message
12554 debug: (function() {
12555 var fn = consoleLog('debug');
12557 return function() {
12559 fn.apply(self, arguments);
12565 function formatError(arg) {
12566 if (arg instanceof Error) {
12568 arg = (arg.message && arg.stack.indexOf(arg.message) === -1)
12569 ? 'Error: ' + arg.message + '\n' + arg.stack
12571 } else if (arg.sourceURL) {
12572 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
12578 function consoleLog(type) {
12579 var console = $window.console || {},
12580 logFn = console[type] || console.log || noop,
12583 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode.
12584 // The reason behind this is that console.log has type "object" in IE8...
12586 hasApply = !!logFn.apply;
12590 return function() {
12592 forEach(arguments, function(arg) {
12593 args.push(formatError(arg));
12595 return logFn.apply(console, args);
12599 // we are IE which either doesn't have window.console => this is noop and we do nothing,
12600 // or we are IE where console.log doesn't have apply so we log at least first 2 args
12601 return function(arg1, arg2) {
12602 logFn(arg1, arg2 == null ? '' : arg2);
12608 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
12609 * Any commits to this file should be reviewed with security in mind. *
12610 * Changes to this file can potentially create security vulnerabilities. *
12611 * An approval from 2 Core members with history of modifying *
12612 * this file is required. *
12614 * Does the change somehow allow for arbitrary javascript to be executed? *
12615 * Or allows for someone to change the prototype of built-in objects? *
12616 * Or gives undesired access to variables likes document or window? *
12617 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
12619 var $parseMinErr = minErr('$parse');
12621 // Sandboxing Angular Expressions
12622 // ------------------------------
12623 // Angular expressions are generally considered safe because these expressions only have direct
12624 // access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by
12625 // obtaining a reference to native JS functions such as the Function constructor.
12627 // As an example, consider the following Angular expression:
12629 // {}.toString.constructor('alert("evil JS code")')
12631 // This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits
12632 // against the expression language, but not to prevent exploits that were enabled by exposing
12633 // sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good
12634 // practice and therefore we are not even trying to protect against interaction with an object
12635 // explicitly exposed in this way.
12637 // In general, it is not possible to access a Window object from an angular expression unless a
12638 // window or some DOM object that has a reference to window is published onto a Scope.
12639 // Similarly we prevent invocations of function known to be dangerous, as well as assignments to
12642 // See https://docs.angularjs.org/guide/security
12645 function ensureSafeMemberName(name, fullExpression) {
12646 if (name === "__defineGetter__" || name === "__defineSetter__"
12647 || name === "__lookupGetter__" || name === "__lookupSetter__"
12648 || name === "__proto__") {
12649 throw $parseMinErr('isecfld',
12650 'Attempting to access a disallowed field in Angular expressions! '
12651 + 'Expression: {0}', fullExpression);
12656 function getStringValue(name, fullExpression) {
12657 // From the JavaScript docs:
12658 // Property names must be strings. This means that non-string objects cannot be used
12659 // as keys in an object. Any non-string object, including a number, is typecasted
12660 // into a string via the toString method.
12662 // So, to ensure that we are checking the same `name` that JavaScript would use,
12663 // we cast it to a string, if possible.
12664 // Doing `name + ''` can cause a repl error if the result to `toString` is not a string,
12665 // this is, this will handle objects that misbehave.
12667 if (!isString(name)) {
12668 throw $parseMinErr('iseccst',
12669 'Cannot convert object to primitive value! '
12670 + 'Expression: {0}', fullExpression);
12675 function ensureSafeObject(obj, fullExpression) {
12676 // nifty check if obj is Function that is fast and works across iframes and other contexts
12678 if (obj.constructor === obj) {
12679 throw $parseMinErr('isecfn',
12680 'Referencing Function in Angular expressions is disallowed! Expression: {0}',
12682 } else if (// isWindow(obj)
12683 obj.window === obj) {
12684 throw $parseMinErr('isecwindow',
12685 'Referencing the Window in Angular expressions is disallowed! Expression: {0}',
12687 } else if (// isElement(obj)
12688 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) {
12689 throw $parseMinErr('isecdom',
12690 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}',
12692 } else if (// block Object so that we can't get hold of dangerous Object.* methods
12694 throw $parseMinErr('isecobj',
12695 'Referencing Object in Angular expressions is disallowed! Expression: {0}',
12702 var CALL = Function.prototype.call;
12703 var APPLY = Function.prototype.apply;
12704 var BIND = Function.prototype.bind;
12706 function ensureSafeFunction(obj, fullExpression) {
12708 if (obj.constructor === obj) {
12709 throw $parseMinErr('isecfn',
12710 'Referencing Function in Angular expressions is disallowed! Expression: {0}',
12712 } else if (obj === CALL || obj === APPLY || obj === BIND) {
12713 throw $parseMinErr('isecff',
12714 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}',
12720 function ensureSafeAssignContext(obj, fullExpression) {
12722 if (obj === (0).constructor || obj === (false).constructor || obj === ''.constructor ||
12723 obj === {}.constructor || obj === [].constructor || obj === Function.constructor) {
12724 throw $parseMinErr('isecaf',
12725 'Assigning to a constructor is disallowed! Expression: {0}', fullExpression);
12730 var OPERATORS = createMap();
12731 forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; });
12732 var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
12735 /////////////////////////////////////////
12741 var Lexer = function(options) {
12742 this.options = options;
12745 Lexer.prototype = {
12746 constructor: Lexer,
12748 lex: function(text) {
12753 while (this.index < this.text.length) {
12754 var ch = this.text.charAt(this.index);
12755 if (ch === '"' || ch === "'") {
12756 this.readString(ch);
12757 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {
12759 } else if (this.isIdent(ch)) {
12761 } else if (this.is(ch, '(){}[].,;:?')) {
12762 this.tokens.push({index: this.index, text: ch});
12764 } else if (this.isWhitespace(ch)) {
12767 var ch2 = ch + this.peek();
12768 var ch3 = ch2 + this.peek(2);
12769 var op1 = OPERATORS[ch];
12770 var op2 = OPERATORS[ch2];
12771 var op3 = OPERATORS[ch3];
12772 if (op1 || op2 || op3) {
12773 var token = op3 ? ch3 : (op2 ? ch2 : ch);
12774 this.tokens.push({index: this.index, text: token, operator: true});
12775 this.index += token.length;
12777 this.throwError('Unexpected next character ', this.index, this.index + 1);
12781 return this.tokens;
12784 is: function(ch, chars) {
12785 return chars.indexOf(ch) !== -1;
12788 peek: function(i) {
12790 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false;
12793 isNumber: function(ch) {
12794 return ('0' <= ch && ch <= '9') && typeof ch === "string";
12797 isWhitespace: function(ch) {
12798 // IE treats non-breaking space as \u00A0
12799 return (ch === ' ' || ch === '\r' || ch === '\t' ||
12800 ch === '\n' || ch === '\v' || ch === '\u00A0');
12803 isIdent: function(ch) {
12804 return ('a' <= ch && ch <= 'z' ||
12805 'A' <= ch && ch <= 'Z' ||
12806 '_' === ch || ch === '$');
12809 isExpOperator: function(ch) {
12810 return (ch === '-' || ch === '+' || this.isNumber(ch));
12813 throwError: function(error, start, end) {
12814 end = end || this.index;
12815 var colStr = (isDefined(start)
12816 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']'
12818 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].',
12819 error, colStr, this.text);
12822 readNumber: function() {
12824 var start = this.index;
12825 while (this.index < this.text.length) {
12826 var ch = lowercase(this.text.charAt(this.index));
12827 if (ch == '.' || this.isNumber(ch)) {
12830 var peekCh = this.peek();
12831 if (ch == 'e' && this.isExpOperator(peekCh)) {
12833 } else if (this.isExpOperator(ch) &&
12834 peekCh && this.isNumber(peekCh) &&
12835 number.charAt(number.length - 1) == 'e') {
12837 } else if (this.isExpOperator(ch) &&
12838 (!peekCh || !this.isNumber(peekCh)) &&
12839 number.charAt(number.length - 1) == 'e') {
12840 this.throwError('Invalid exponent');
12851 value: Number(number)
12855 readIdent: function() {
12856 var start = this.index;
12857 while (this.index < this.text.length) {
12858 var ch = this.text.charAt(this.index);
12859 if (!(this.isIdent(ch) || this.isNumber(ch))) {
12866 text: this.text.slice(start, this.index),
12871 readString: function(quote) {
12872 var start = this.index;
12875 var rawString = quote;
12876 var escape = false;
12877 while (this.index < this.text.length) {
12878 var ch = this.text.charAt(this.index);
12882 var hex = this.text.substring(this.index + 1, this.index + 5);
12883 if (!hex.match(/[\da-f]{4}/i)) {
12884 this.throwError('Invalid unicode escape [\\u' + hex + ']');
12887 string += String.fromCharCode(parseInt(hex, 16));
12889 var rep = ESCAPE[ch];
12890 string = string + (rep || ch);
12893 } else if (ch === '\\') {
12895 } else if (ch === quote) {
12909 this.throwError('Unterminated quote', start);
12913 var AST = function(lexer, options) {
12914 this.lexer = lexer;
12915 this.options = options;
12918 AST.Program = 'Program';
12919 AST.ExpressionStatement = 'ExpressionStatement';
12920 AST.AssignmentExpression = 'AssignmentExpression';
12921 AST.ConditionalExpression = 'ConditionalExpression';
12922 AST.LogicalExpression = 'LogicalExpression';
12923 AST.BinaryExpression = 'BinaryExpression';
12924 AST.UnaryExpression = 'UnaryExpression';
12925 AST.CallExpression = 'CallExpression';
12926 AST.MemberExpression = 'MemberExpression';
12927 AST.Identifier = 'Identifier';
12928 AST.Literal = 'Literal';
12929 AST.ArrayExpression = 'ArrayExpression';
12930 AST.Property = 'Property';
12931 AST.ObjectExpression = 'ObjectExpression';
12932 AST.ThisExpression = 'ThisExpression';
12934 // Internal use only
12935 AST.NGValueParameter = 'NGValueParameter';
12938 ast: function(text) {
12940 this.tokens = this.lexer.lex(text);
12942 var value = this.program();
12944 if (this.tokens.length !== 0) {
12945 this.throwError('is an unexpected token', this.tokens[0]);
12951 program: function() {
12954 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']'))
12955 body.push(this.expressionStatement());
12956 if (!this.expect(';')) {
12957 return { type: AST.Program, body: body};
12962 expressionStatement: function() {
12963 return { type: AST.ExpressionStatement, expression: this.filterChain() };
12966 filterChain: function() {
12967 var left = this.expression();
12969 while ((token = this.expect('|'))) {
12970 left = this.filter(left);
12975 expression: function() {
12976 return this.assignment();
12979 assignment: function() {
12980 var result = this.ternary();
12981 if (this.expect('=')) {
12982 result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='};
12987 ternary: function() {
12988 var test = this.logicalOR();
12991 if (this.expect('?')) {
12992 alternate = this.expression();
12993 if (this.consume(':')) {
12994 consequent = this.expression();
12995 return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent};
13001 logicalOR: function() {
13002 var left = this.logicalAND();
13003 while (this.expect('||')) {
13004 left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() };
13009 logicalAND: function() {
13010 var left = this.equality();
13011 while (this.expect('&&')) {
13012 left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()};
13017 equality: function() {
13018 var left = this.relational();
13020 while ((token = this.expect('==','!=','===','!=='))) {
13021 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() };
13026 relational: function() {
13027 var left = this.additive();
13029 while ((token = this.expect('<', '>', '<=', '>='))) {
13030 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() };
13035 additive: function() {
13036 var left = this.multiplicative();
13038 while ((token = this.expect('+','-'))) {
13039 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() };
13044 multiplicative: function() {
13045 var left = this.unary();
13047 while ((token = this.expect('*','/','%'))) {
13048 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() };
13053 unary: function() {
13055 if ((token = this.expect('+', '-', '!'))) {
13056 return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() };
13058 return this.primary();
13062 primary: function() {
13064 if (this.expect('(')) {
13065 primary = this.filterChain();
13067 } else if (this.expect('[')) {
13068 primary = this.arrayDeclaration();
13069 } else if (this.expect('{')) {
13070 primary = this.object();
13071 } else if (this.constants.hasOwnProperty(this.peek().text)) {
13072 primary = copy(this.constants[this.consume().text]);
13073 } else if (this.peek().identifier) {
13074 primary = this.identifier();
13075 } else if (this.peek().constant) {
13076 primary = this.constant();
13078 this.throwError('not a primary expression', this.peek());
13082 while ((next = this.expect('(', '[', '.'))) {
13083 if (next.text === '(') {
13084 primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() };
13086 } else if (next.text === '[') {
13087 primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true };
13089 } else if (next.text === '.') {
13090 primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false };
13092 this.throwError('IMPOSSIBLE');
13098 filter: function(baseExpression) {
13099 var args = [baseExpression];
13100 var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true};
13102 while (this.expect(':')) {
13103 args.push(this.expression());
13109 parseArguments: function() {
13111 if (this.peekToken().text !== ')') {
13113 args.push(this.expression());
13114 } while (this.expect(','));
13119 identifier: function() {
13120 var token = this.consume();
13121 if (!token.identifier) {
13122 this.throwError('is not a valid identifier', token);
13124 return { type: AST.Identifier, name: token.text };
13127 constant: function() {
13128 // TODO check that it is a constant
13129 return { type: AST.Literal, value: this.consume().value };
13132 arrayDeclaration: function() {
13134 if (this.peekToken().text !== ']') {
13136 if (this.peek(']')) {
13137 // Support trailing commas per ES5.1.
13140 elements.push(this.expression());
13141 } while (this.expect(','));
13145 return { type: AST.ArrayExpression, elements: elements };
13148 object: function() {
13149 var properties = [], property;
13150 if (this.peekToken().text !== '}') {
13152 if (this.peek('}')) {
13153 // Support trailing commas per ES5.1.
13156 property = {type: AST.Property, kind: 'init'};
13157 if (this.peek().constant) {
13158 property.key = this.constant();
13159 } else if (this.peek().identifier) {
13160 property.key = this.identifier();
13162 this.throwError("invalid key", this.peek());
13165 property.value = this.expression();
13166 properties.push(property);
13167 } while (this.expect(','));
13171 return {type: AST.ObjectExpression, properties: properties };
13174 throwError: function(msg, token) {
13175 throw $parseMinErr('syntax',
13176 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].',
13177 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index));
13180 consume: function(e1) {
13181 if (this.tokens.length === 0) {
13182 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
13185 var token = this.expect(e1);
13187 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek());
13192 peekToken: function() {
13193 if (this.tokens.length === 0) {
13194 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
13196 return this.tokens[0];
13199 peek: function(e1, e2, e3, e4) {
13200 return this.peekAhead(0, e1, e2, e3, e4);
13203 peekAhead: function(i, e1, e2, e3, e4) {
13204 if (this.tokens.length > i) {
13205 var token = this.tokens[i];
13206 var t = token.text;
13207 if (t === e1 || t === e2 || t === e3 || t === e4 ||
13208 (!e1 && !e2 && !e3 && !e4)) {
13215 expect: function(e1, e2, e3, e4) {
13216 var token = this.peek(e1, e2, e3, e4);
13218 this.tokens.shift();
13225 /* `undefined` is not a constant, it is an identifier,
13226 * but using it as an identifier is not supported
13229 'true': { type: AST.Literal, value: true },
13230 'false': { type: AST.Literal, value: false },
13231 'null': { type: AST.Literal, value: null },
13232 'undefined': {type: AST.Literal, value: undefined },
13233 'this': {type: AST.ThisExpression }
13237 function ifDefined(v, d) {
13238 return typeof v !== 'undefined' ? v : d;
13241 function plusFn(l, r) {
13242 if (typeof l === 'undefined') return r;
13243 if (typeof r === 'undefined') return l;
13247 function isStateless($filter, filterName) {
13248 var fn = $filter(filterName);
13249 return !fn.$stateful;
13252 function findConstantAndWatchExpressions(ast, $filter) {
13255 switch (ast.type) {
13257 allConstants = true;
13258 forEach(ast.body, function(expr) {
13259 findConstantAndWatchExpressions(expr.expression, $filter);
13260 allConstants = allConstants && expr.expression.constant;
13262 ast.constant = allConstants;
13265 ast.constant = true;
13268 case AST.UnaryExpression:
13269 findConstantAndWatchExpressions(ast.argument, $filter);
13270 ast.constant = ast.argument.constant;
13271 ast.toWatch = ast.argument.toWatch;
13273 case AST.BinaryExpression:
13274 findConstantAndWatchExpressions(ast.left, $filter);
13275 findConstantAndWatchExpressions(ast.right, $filter);
13276 ast.constant = ast.left.constant && ast.right.constant;
13277 ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch);
13279 case AST.LogicalExpression:
13280 findConstantAndWatchExpressions(ast.left, $filter);
13281 findConstantAndWatchExpressions(ast.right, $filter);
13282 ast.constant = ast.left.constant && ast.right.constant;
13283 ast.toWatch = ast.constant ? [] : [ast];
13285 case AST.ConditionalExpression:
13286 findConstantAndWatchExpressions(ast.test, $filter);
13287 findConstantAndWatchExpressions(ast.alternate, $filter);
13288 findConstantAndWatchExpressions(ast.consequent, $filter);
13289 ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant;
13290 ast.toWatch = ast.constant ? [] : [ast];
13292 case AST.Identifier:
13293 ast.constant = false;
13294 ast.toWatch = [ast];
13296 case AST.MemberExpression:
13297 findConstantAndWatchExpressions(ast.object, $filter);
13298 if (ast.computed) {
13299 findConstantAndWatchExpressions(ast.property, $filter);
13301 ast.constant = ast.object.constant && (!ast.computed || ast.property.constant);
13302 ast.toWatch = [ast];
13304 case AST.CallExpression:
13305 allConstants = ast.filter ? isStateless($filter, ast.callee.name) : false;
13307 forEach(ast.arguments, function(expr) {
13308 findConstantAndWatchExpressions(expr, $filter);
13309 allConstants = allConstants && expr.constant;
13310 if (!expr.constant) {
13311 argsToWatch.push.apply(argsToWatch, expr.toWatch);
13314 ast.constant = allConstants;
13315 ast.toWatch = ast.filter && isStateless($filter, ast.callee.name) ? argsToWatch : [ast];
13317 case AST.AssignmentExpression:
13318 findConstantAndWatchExpressions(ast.left, $filter);
13319 findConstantAndWatchExpressions(ast.right, $filter);
13320 ast.constant = ast.left.constant && ast.right.constant;
13321 ast.toWatch = [ast];
13323 case AST.ArrayExpression:
13324 allConstants = true;
13326 forEach(ast.elements, function(expr) {
13327 findConstantAndWatchExpressions(expr, $filter);
13328 allConstants = allConstants && expr.constant;
13329 if (!expr.constant) {
13330 argsToWatch.push.apply(argsToWatch, expr.toWatch);
13333 ast.constant = allConstants;
13334 ast.toWatch = argsToWatch;
13336 case AST.ObjectExpression:
13337 allConstants = true;
13339 forEach(ast.properties, function(property) {
13340 findConstantAndWatchExpressions(property.value, $filter);
13341 allConstants = allConstants && property.value.constant;
13342 if (!property.value.constant) {
13343 argsToWatch.push.apply(argsToWatch, property.value.toWatch);
13346 ast.constant = allConstants;
13347 ast.toWatch = argsToWatch;
13349 case AST.ThisExpression:
13350 ast.constant = false;
13356 function getInputs(body) {
13357 if (body.length != 1) return;
13358 var lastExpression = body[0].expression;
13359 var candidate = lastExpression.toWatch;
13360 if (candidate.length !== 1) return candidate;
13361 return candidate[0] !== lastExpression ? candidate : undefined;
13364 function isAssignable(ast) {
13365 return ast.type === AST.Identifier || ast.type === AST.MemberExpression;
13368 function assignableAST(ast) {
13369 if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) {
13370 return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='};
13374 function isLiteral(ast) {
13375 return ast.body.length === 0 ||
13376 ast.body.length === 1 && (
13377 ast.body[0].expression.type === AST.Literal ||
13378 ast.body[0].expression.type === AST.ArrayExpression ||
13379 ast.body[0].expression.type === AST.ObjectExpression);
13382 function isConstant(ast) {
13383 return ast.constant;
13386 function ASTCompiler(astBuilder, $filter) {
13387 this.astBuilder = astBuilder;
13388 this.$filter = $filter;
13391 ASTCompiler.prototype = {
13392 compile: function(expression, expensiveChecks) {
13394 var ast = this.astBuilder.ast(expression);
13398 expensiveChecks: expensiveChecks,
13399 fn: {vars: [], body: [], own: {}},
13400 assign: {vars: [], body: [], own: {}},
13403 findConstantAndWatchExpressions(ast, self.$filter);
13406 this.stage = 'assign';
13407 if ((assignable = assignableAST(ast))) {
13408 this.state.computing = 'assign';
13409 var result = this.nextId();
13410 this.recurse(assignable, result);
13411 this.return_(result);
13412 extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l');
13414 var toWatch = getInputs(ast.body);
13415 self.stage = 'inputs';
13416 forEach(toWatch, function(watch, key) {
13417 var fnKey = 'fn' + key;
13418 self.state[fnKey] = {vars: [], body: [], own: {}};
13419 self.state.computing = fnKey;
13420 var intoId = self.nextId();
13421 self.recurse(watch, intoId);
13422 self.return_(intoId);
13423 self.state.inputs.push(fnKey);
13424 watch.watchId = key;
13426 this.state.computing = 'fn';
13427 this.stage = 'main';
13430 // The build and minification steps remove the string "use strict" from the code, but this is done using a regex.
13431 // This is a workaround for this until we do a better job at only removing the prefix only when we should.
13432 '"' + this.USE + ' ' + this.STRICT + '";\n' +
13433 this.filterPrefix() +
13434 'var fn=' + this.generateFunction('fn', 's,l,a,i') +
13440 var fn = (new Function('$filter',
13441 'ensureSafeMemberName',
13442 'ensureSafeObject',
13443 'ensureSafeFunction',
13445 'ensureSafeAssignContext',
13451 ensureSafeMemberName,
13453 ensureSafeFunction,
13455 ensureSafeAssignContext,
13460 this.state = this.stage = undefined;
13461 fn.literal = isLiteral(ast);
13462 fn.constant = isConstant(ast);
13470 watchFns: function() {
13472 var fns = this.state.inputs;
13474 forEach(fns, function(name) {
13475 result.push('var ' + name + '=' + self.generateFunction(name, 's'));
13478 result.push('fn.inputs=[' + fns.join(',') + '];');
13480 return result.join('');
13483 generateFunction: function(name, params) {
13484 return 'function(' + params + '){' +
13485 this.varsPrefix(name) +
13490 filterPrefix: function() {
13493 forEach(this.state.filters, function(id, filter) {
13494 parts.push(id + '=$filter(' + self.escape(filter) + ')');
13496 if (parts.length) return 'var ' + parts.join(',') + ';';
13500 varsPrefix: function(section) {
13501 return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : '';
13504 body: function(section) {
13505 return this.state[section].body.join('');
13508 recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
13509 var left, right, self = this, args, expression;
13510 recursionFn = recursionFn || noop;
13511 if (!skipWatchIdCheck && isDefined(ast.watchId)) {
13512 intoId = intoId || this.nextId();
13514 this.lazyAssign(intoId, this.computedMember('i', ast.watchId)),
13515 this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true)
13519 switch (ast.type) {
13521 forEach(ast.body, function(expression, pos) {
13522 self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; });
13523 if (pos !== ast.body.length - 1) {
13524 self.current().body.push(right, ';');
13526 self.return_(right);
13531 expression = this.escape(ast.value);
13532 this.assign(intoId, expression);
13533 recursionFn(expression);
13535 case AST.UnaryExpression:
13536 this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; });
13537 expression = ast.operator + '(' + this.ifDefined(right, 0) + ')';
13538 this.assign(intoId, expression);
13539 recursionFn(expression);
13541 case AST.BinaryExpression:
13542 this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; });
13543 this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; });
13544 if (ast.operator === '+') {
13545 expression = this.plus(left, right);
13546 } else if (ast.operator === '-') {
13547 expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0);
13549 expression = '(' + left + ')' + ast.operator + '(' + right + ')';
13551 this.assign(intoId, expression);
13552 recursionFn(expression);
13554 case AST.LogicalExpression:
13555 intoId = intoId || this.nextId();
13556 self.recurse(ast.left, intoId);
13557 self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId));
13558 recursionFn(intoId);
13560 case AST.ConditionalExpression:
13561 intoId = intoId || this.nextId();
13562 self.recurse(ast.test, intoId);
13563 self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId));
13564 recursionFn(intoId);
13566 case AST.Identifier:
13567 intoId = intoId || this.nextId();
13569 nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s');
13570 nameId.computed = false;
13571 nameId.name = ast.name;
13573 ensureSafeMemberName(ast.name);
13574 self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)),
13576 self.if_(self.stage === 'inputs' || 's', function() {
13577 if (create && create !== 1) {
13579 self.not(self.nonComputedMember('s', ast.name)),
13580 self.lazyAssign(self.nonComputedMember('s', ast.name), '{}'));
13582 self.assign(intoId, self.nonComputedMember('s', ast.name));
13584 }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name))
13586 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) {
13587 self.addEnsureSafeObject(intoId);
13589 recursionFn(intoId);
13591 case AST.MemberExpression:
13592 left = nameId && (nameId.context = this.nextId()) || this.nextId();
13593 intoId = intoId || this.nextId();
13594 self.recurse(ast.object, left, undefined, function() {
13595 self.if_(self.notNull(left), function() {
13596 if (ast.computed) {
13597 right = self.nextId();
13598 self.recurse(ast.property, right);
13599 self.getStringValue(right);
13600 self.addEnsureSafeMemberName(right);
13601 if (create && create !== 1) {
13602 self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}'));
13604 expression = self.ensureSafeObject(self.computedMember(left, right));
13605 self.assign(intoId, expression);
13607 nameId.computed = true;
13608 nameId.name = right;
13611 ensureSafeMemberName(ast.property.name);
13612 if (create && create !== 1) {
13613 self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}'));
13615 expression = self.nonComputedMember(left, ast.property.name);
13616 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) {
13617 expression = self.ensureSafeObject(expression);
13619 self.assign(intoId, expression);
13621 nameId.computed = false;
13622 nameId.name = ast.property.name;
13626 self.assign(intoId, 'undefined');
13628 recursionFn(intoId);
13631 case AST.CallExpression:
13632 intoId = intoId || this.nextId();
13634 right = self.filter(ast.callee.name);
13636 forEach(ast.arguments, function(expr) {
13637 var argument = self.nextId();
13638 self.recurse(expr, argument);
13639 args.push(argument);
13641 expression = right + '(' + args.join(',') + ')';
13642 self.assign(intoId, expression);
13643 recursionFn(intoId);
13645 right = self.nextId();
13648 self.recurse(ast.callee, right, left, function() {
13649 self.if_(self.notNull(right), function() {
13650 self.addEnsureSafeFunction(right);
13651 forEach(ast.arguments, function(expr) {
13652 self.recurse(expr, self.nextId(), undefined, function(argument) {
13653 args.push(self.ensureSafeObject(argument));
13657 if (!self.state.expensiveChecks) {
13658 self.addEnsureSafeObject(left.context);
13660 expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')';
13662 expression = right + '(' + args.join(',') + ')';
13664 expression = self.ensureSafeObject(expression);
13665 self.assign(intoId, expression);
13667 self.assign(intoId, 'undefined');
13669 recursionFn(intoId);
13673 case AST.AssignmentExpression:
13674 right = this.nextId();
13676 if (!isAssignable(ast.left)) {
13677 throw $parseMinErr('lval', 'Trying to assing a value to a non l-value');
13679 this.recurse(ast.left, undefined, left, function() {
13680 self.if_(self.notNull(left.context), function() {
13681 self.recurse(ast.right, right);
13682 self.addEnsureSafeObject(self.member(left.context, left.name, left.computed));
13683 self.addEnsureSafeAssignContext(left.context);
13684 expression = self.member(left.context, left.name, left.computed) + ast.operator + right;
13685 self.assign(intoId, expression);
13686 recursionFn(intoId || expression);
13690 case AST.ArrayExpression:
13692 forEach(ast.elements, function(expr) {
13693 self.recurse(expr, self.nextId(), undefined, function(argument) {
13694 args.push(argument);
13697 expression = '[' + args.join(',') + ']';
13698 this.assign(intoId, expression);
13699 recursionFn(expression);
13701 case AST.ObjectExpression:
13703 forEach(ast.properties, function(property) {
13704 self.recurse(property.value, self.nextId(), undefined, function(expr) {
13705 args.push(self.escape(
13706 property.key.type === AST.Identifier ? property.key.name :
13707 ('' + property.key.value)) +
13711 expression = '{' + args.join(',') + '}';
13712 this.assign(intoId, expression);
13713 recursionFn(expression);
13715 case AST.ThisExpression:
13716 this.assign(intoId, 's');
13719 case AST.NGValueParameter:
13720 this.assign(intoId, 'v');
13726 getHasOwnProperty: function(element, property) {
13727 var key = element + '.' + property;
13728 var own = this.current().own;
13729 if (!own.hasOwnProperty(key)) {
13730 own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')');
13735 assign: function(id, value) {
13737 this.current().body.push(id, '=', value, ';');
13741 filter: function(filterName) {
13742 if (!this.state.filters.hasOwnProperty(filterName)) {
13743 this.state.filters[filterName] = this.nextId(true);
13745 return this.state.filters[filterName];
13748 ifDefined: function(id, defaultValue) {
13749 return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')';
13752 plus: function(left, right) {
13753 return 'plus(' + left + ',' + right + ')';
13756 return_: function(id) {
13757 this.current().body.push('return ', id, ';');
13760 if_: function(test, alternate, consequent) {
13761 if (test === true) {
13764 var body = this.current().body;
13765 body.push('if(', test, '){');
13769 body.push('else{');
13776 not: function(expression) {
13777 return '!(' + expression + ')';
13780 notNull: function(expression) {
13781 return expression + '!=null';
13784 nonComputedMember: function(left, right) {
13785 return left + '.' + right;
13788 computedMember: function(left, right) {
13789 return left + '[' + right + ']';
13792 member: function(left, right, computed) {
13793 if (computed) return this.computedMember(left, right);
13794 return this.nonComputedMember(left, right);
13797 addEnsureSafeObject: function(item) {
13798 this.current().body.push(this.ensureSafeObject(item), ';');
13801 addEnsureSafeMemberName: function(item) {
13802 this.current().body.push(this.ensureSafeMemberName(item), ';');
13805 addEnsureSafeFunction: function(item) {
13806 this.current().body.push(this.ensureSafeFunction(item), ';');
13809 addEnsureSafeAssignContext: function(item) {
13810 this.current().body.push(this.ensureSafeAssignContext(item), ';');
13813 ensureSafeObject: function(item) {
13814 return 'ensureSafeObject(' + item + ',text)';
13817 ensureSafeMemberName: function(item) {
13818 return 'ensureSafeMemberName(' + item + ',text)';
13821 ensureSafeFunction: function(item) {
13822 return 'ensureSafeFunction(' + item + ',text)';
13825 getStringValue: function(item) {
13826 this.assign(item, 'getStringValue(' + item + ',text)');
13829 ensureSafeAssignContext: function(item) {
13830 return 'ensureSafeAssignContext(' + item + ',text)';
13833 lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
13835 return function() {
13836 self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck);
13840 lazyAssign: function(id, value) {
13842 return function() {
13843 self.assign(id, value);
13847 stringEscapeRegex: /[^ a-zA-Z0-9]/g,
13849 stringEscapeFn: function(c) {
13850 return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4);
13853 escape: function(value) {
13854 if (isString(value)) return "'" + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + "'";
13855 if (isNumber(value)) return value.toString();
13856 if (value === true) return 'true';
13857 if (value === false) return 'false';
13858 if (value === null) return 'null';
13859 if (typeof value === 'undefined') return 'undefined';
13861 throw $parseMinErr('esc', 'IMPOSSIBLE');
13864 nextId: function(skip, init) {
13865 var id = 'v' + (this.state.nextId++);
13867 this.current().vars.push(id + (init ? '=' + init : ''));
13872 current: function() {
13873 return this.state[this.state.computing];
13878 function ASTInterpreter(astBuilder, $filter) {
13879 this.astBuilder = astBuilder;
13880 this.$filter = $filter;
13883 ASTInterpreter.prototype = {
13884 compile: function(expression, expensiveChecks) {
13886 var ast = this.astBuilder.ast(expression);
13887 this.expression = expression;
13888 this.expensiveChecks = expensiveChecks;
13889 findConstantAndWatchExpressions(ast, self.$filter);
13892 if ((assignable = assignableAST(ast))) {
13893 assign = this.recurse(assignable);
13895 var toWatch = getInputs(ast.body);
13899 forEach(toWatch, function(watch, key) {
13900 var input = self.recurse(watch);
13901 watch.input = input;
13902 inputs.push(input);
13903 watch.watchId = key;
13906 var expressions = [];
13907 forEach(ast.body, function(expression) {
13908 expressions.push(self.recurse(expression.expression));
13910 var fn = ast.body.length === 0 ? function() {} :
13911 ast.body.length === 1 ? expressions[0] :
13912 function(scope, locals) {
13914 forEach(expressions, function(exp) {
13915 lastValue = exp(scope, locals);
13920 fn.assign = function(scope, value, locals) {
13921 return assign(scope, locals, value);
13925 fn.inputs = inputs;
13927 fn.literal = isLiteral(ast);
13928 fn.constant = isConstant(ast);
13932 recurse: function(ast, context, create) {
13933 var left, right, self = this, args, expression;
13935 return this.inputs(ast.input, ast.watchId);
13937 switch (ast.type) {
13939 return this.value(ast.value, context);
13940 case AST.UnaryExpression:
13941 right = this.recurse(ast.argument);
13942 return this['unary' + ast.operator](right, context);
13943 case AST.BinaryExpression:
13944 left = this.recurse(ast.left);
13945 right = this.recurse(ast.right);
13946 return this['binary' + ast.operator](left, right, context);
13947 case AST.LogicalExpression:
13948 left = this.recurse(ast.left);
13949 right = this.recurse(ast.right);
13950 return this['binary' + ast.operator](left, right, context);
13951 case AST.ConditionalExpression:
13952 return this['ternary?:'](
13953 this.recurse(ast.test),
13954 this.recurse(ast.alternate),
13955 this.recurse(ast.consequent),
13958 case AST.Identifier:
13959 ensureSafeMemberName(ast.name, self.expression);
13960 return self.identifier(ast.name,
13961 self.expensiveChecks || isPossiblyDangerousMemberName(ast.name),
13962 context, create, self.expression);
13963 case AST.MemberExpression:
13964 left = this.recurse(ast.object, false, !!create);
13965 if (!ast.computed) {
13966 ensureSafeMemberName(ast.property.name, self.expression);
13967 right = ast.property.name;
13969 if (ast.computed) right = this.recurse(ast.property);
13970 return ast.computed ?
13971 this.computedMember(left, right, context, create, self.expression) :
13972 this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression);
13973 case AST.CallExpression:
13975 forEach(ast.arguments, function(expr) {
13976 args.push(self.recurse(expr));
13978 if (ast.filter) right = this.$filter(ast.callee.name);
13979 if (!ast.filter) right = this.recurse(ast.callee, true);
13980 return ast.filter ?
13981 function(scope, locals, assign, inputs) {
13983 for (var i = 0; i < args.length; ++i) {
13984 values.push(args[i](scope, locals, assign, inputs));
13986 var value = right.apply(undefined, values, inputs);
13987 return context ? {context: undefined, name: undefined, value: value} : value;
13989 function(scope, locals, assign, inputs) {
13990 var rhs = right(scope, locals, assign, inputs);
13992 if (rhs.value != null) {
13993 ensureSafeObject(rhs.context, self.expression);
13994 ensureSafeFunction(rhs.value, self.expression);
13996 for (var i = 0; i < args.length; ++i) {
13997 values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression));
13999 value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression);
14001 return context ? {value: value} : value;
14003 case AST.AssignmentExpression:
14004 left = this.recurse(ast.left, true, 1);
14005 right = this.recurse(ast.right);
14006 return function(scope, locals, assign, inputs) {
14007 var lhs = left(scope, locals, assign, inputs);
14008 var rhs = right(scope, locals, assign, inputs);
14009 ensureSafeObject(lhs.value, self.expression);
14010 ensureSafeAssignContext(lhs.context);
14011 lhs.context[lhs.name] = rhs;
14012 return context ? {value: rhs} : rhs;
14014 case AST.ArrayExpression:
14016 forEach(ast.elements, function(expr) {
14017 args.push(self.recurse(expr));
14019 return function(scope, locals, assign, inputs) {
14021 for (var i = 0; i < args.length; ++i) {
14022 value.push(args[i](scope, locals, assign, inputs));
14024 return context ? {value: value} : value;
14026 case AST.ObjectExpression:
14028 forEach(ast.properties, function(property) {
14029 args.push({key: property.key.type === AST.Identifier ?
14030 property.key.name :
14031 ('' + property.key.value),
14032 value: self.recurse(property.value)
14035 return function(scope, locals, assign, inputs) {
14037 for (var i = 0; i < args.length; ++i) {
14038 value[args[i].key] = args[i].value(scope, locals, assign, inputs);
14040 return context ? {value: value} : value;
14042 case AST.ThisExpression:
14043 return function(scope) {
14044 return context ? {value: scope} : scope;
14046 case AST.NGValueParameter:
14047 return function(scope, locals, assign, inputs) {
14048 return context ? {value: assign} : assign;
14053 'unary+': function(argument, context) {
14054 return function(scope, locals, assign, inputs) {
14055 var arg = argument(scope, locals, assign, inputs);
14056 if (isDefined(arg)) {
14061 return context ? {value: arg} : arg;
14064 'unary-': function(argument, context) {
14065 return function(scope, locals, assign, inputs) {
14066 var arg = argument(scope, locals, assign, inputs);
14067 if (isDefined(arg)) {
14072 return context ? {value: arg} : arg;
14075 'unary!': function(argument, context) {
14076 return function(scope, locals, assign, inputs) {
14077 var arg = !argument(scope, locals, assign, inputs);
14078 return context ? {value: arg} : arg;
14081 'binary+': function(left, right, context) {
14082 return function(scope, locals, assign, inputs) {
14083 var lhs = left(scope, locals, assign, inputs);
14084 var rhs = right(scope, locals, assign, inputs);
14085 var arg = plusFn(lhs, rhs);
14086 return context ? {value: arg} : arg;
14089 'binary-': function(left, right, context) {
14090 return function(scope, locals, assign, inputs) {
14091 var lhs = left(scope, locals, assign, inputs);
14092 var rhs = right(scope, locals, assign, inputs);
14093 var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0);
14094 return context ? {value: arg} : arg;
14097 'binary*': function(left, right, context) {
14098 return function(scope, locals, assign, inputs) {
14099 var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs);
14100 return context ? {value: arg} : arg;
14103 'binary/': function(left, right, context) {
14104 return function(scope, locals, assign, inputs) {
14105 var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs);
14106 return context ? {value: arg} : arg;
14109 'binary%': function(left, right, context) {
14110 return function(scope, locals, assign, inputs) {
14111 var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs);
14112 return context ? {value: arg} : arg;
14115 'binary===': function(left, right, context) {
14116 return function(scope, locals, assign, inputs) {
14117 var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs);
14118 return context ? {value: arg} : arg;
14121 'binary!==': function(left, right, context) {
14122 return function(scope, locals, assign, inputs) {
14123 var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs);
14124 return context ? {value: arg} : arg;
14127 'binary==': function(left, right, context) {
14128 return function(scope, locals, assign, inputs) {
14129 var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs);
14130 return context ? {value: arg} : arg;
14133 'binary!=': function(left, right, context) {
14134 return function(scope, locals, assign, inputs) {
14135 var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs);
14136 return context ? {value: arg} : arg;
14139 'binary<': function(left, right, context) {
14140 return function(scope, locals, assign, inputs) {
14141 var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs);
14142 return context ? {value: arg} : arg;
14145 'binary>': function(left, right, context) {
14146 return function(scope, locals, assign, inputs) {
14147 var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs);
14148 return context ? {value: arg} : arg;
14151 'binary<=': function(left, right, context) {
14152 return function(scope, locals, assign, inputs) {
14153 var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs);
14154 return context ? {value: arg} : arg;
14157 'binary>=': function(left, right, context) {
14158 return function(scope, locals, assign, inputs) {
14159 var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs);
14160 return context ? {value: arg} : arg;
14163 'binary&&': function(left, right, context) {
14164 return function(scope, locals, assign, inputs) {
14165 var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs);
14166 return context ? {value: arg} : arg;
14169 'binary||': function(left, right, context) {
14170 return function(scope, locals, assign, inputs) {
14171 var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs);
14172 return context ? {value: arg} : arg;
14175 'ternary?:': function(test, alternate, consequent, context) {
14176 return function(scope, locals, assign, inputs) {
14177 var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs);
14178 return context ? {value: arg} : arg;
14181 value: function(value, context) {
14182 return function() { return context ? {context: undefined, name: undefined, value: value} : value; };
14184 identifier: function(name, expensiveChecks, context, create, expression) {
14185 return function(scope, locals, assign, inputs) {
14186 var base = locals && (name in locals) ? locals : scope;
14187 if (create && create !== 1 && base && !(base[name])) {
14190 var value = base ? base[name] : undefined;
14191 if (expensiveChecks) {
14192 ensureSafeObject(value, expression);
14195 return {context: base, name: name, value: value};
14201 computedMember: function(left, right, context, create, expression) {
14202 return function(scope, locals, assign, inputs) {
14203 var lhs = left(scope, locals, assign, inputs);
14207 rhs = right(scope, locals, assign, inputs);
14208 rhs = getStringValue(rhs);
14209 ensureSafeMemberName(rhs, expression);
14210 if (create && create !== 1 && lhs && !(lhs[rhs])) {
14214 ensureSafeObject(value, expression);
14217 return {context: lhs, name: rhs, value: value};
14223 nonComputedMember: function(left, right, expensiveChecks, context, create, expression) {
14224 return function(scope, locals, assign, inputs) {
14225 var lhs = left(scope, locals, assign, inputs);
14226 if (create && create !== 1 && lhs && !(lhs[right])) {
14229 var value = lhs != null ? lhs[right] : undefined;
14230 if (expensiveChecks || isPossiblyDangerousMemberName(right)) {
14231 ensureSafeObject(value, expression);
14234 return {context: lhs, name: right, value: value};
14240 inputs: function(input, watchId) {
14241 return function(scope, value, locals, inputs) {
14242 if (inputs) return inputs[watchId];
14243 return input(scope, value, locals);
14251 var Parser = function(lexer, $filter, options) {
14252 this.lexer = lexer;
14253 this.$filter = $filter;
14254 this.options = options;
14255 this.ast = new AST(this.lexer);
14256 this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) :
14257 new ASTCompiler(this.ast, $filter);
14260 Parser.prototype = {
14261 constructor: Parser,
14263 parse: function(text) {
14264 return this.astCompiler.compile(text, this.options.expensiveChecks);
14268 var getterFnCacheDefault = createMap();
14269 var getterFnCacheExpensive = createMap();
14271 function isPossiblyDangerousMemberName(name) {
14272 return name == 'constructor';
14275 var objectValueOf = Object.prototype.valueOf;
14277 function getValueOf(value) {
14278 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value);
14281 ///////////////////////////////////
14290 * Converts Angular {@link guide/expression expression} into a function.
14293 * var getter = $parse('user.name');
14294 * var setter = getter.assign;
14295 * var context = {user:{name:'angular'}};
14296 * var locals = {user:{name:'local'}};
14298 * expect(getter(context)).toEqual('angular');
14299 * setter(context, 'newValue');
14300 * expect(context.user.name).toEqual('newValue');
14301 * expect(getter(context, locals)).toEqual('local');
14305 * @param {string} expression String expression to compile.
14306 * @returns {function(context, locals)} a function which represents the compiled expression:
14308 * * `context` – `{object}` – an object against which any expressions embedded in the strings
14309 * are evaluated against (typically a scope object).
14310 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
14313 * The returned function also has the following properties:
14314 * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript
14316 * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript
14317 * constant literals.
14318 * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be
14319 * set to a function to change its value on the given context.
14326 * @name $parseProvider
14329 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse}
14332 function $ParseProvider() {
14333 var cacheDefault = createMap();
14334 var cacheExpensive = createMap();
14336 this.$get = ['$filter', function($filter) {
14337 var noUnsafeEval = csp().noUnsafeEval;
14338 var $parseOptions = {
14340 expensiveChecks: false
14342 $parseOptionsExpensive = {
14344 expensiveChecks: true
14347 return function $parse(exp, interceptorFn, expensiveChecks) {
14348 var parsedExpression, oneTime, cacheKey;
14350 switch (typeof exp) {
14355 var cache = (expensiveChecks ? cacheExpensive : cacheDefault);
14356 parsedExpression = cache[cacheKey];
14358 if (!parsedExpression) {
14359 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {
14361 exp = exp.substring(2);
14363 var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions;
14364 var lexer = new Lexer(parseOptions);
14365 var parser = new Parser(lexer, $filter, parseOptions);
14366 parsedExpression = parser.parse(exp);
14367 if (parsedExpression.constant) {
14368 parsedExpression.$$watchDelegate = constantWatchDelegate;
14369 } else if (oneTime) {
14370 parsedExpression.$$watchDelegate = parsedExpression.literal ?
14371 oneTimeLiteralWatchDelegate : oneTimeWatchDelegate;
14372 } else if (parsedExpression.inputs) {
14373 parsedExpression.$$watchDelegate = inputsWatchDelegate;
14375 cache[cacheKey] = parsedExpression;
14377 return addInterceptor(parsedExpression, interceptorFn);
14380 return addInterceptor(exp, interceptorFn);
14387 function expressionInputDirtyCheck(newValue, oldValueOfValue) {
14389 if (newValue == null || oldValueOfValue == null) { // null/undefined
14390 return newValue === oldValueOfValue;
14393 if (typeof newValue === 'object') {
14395 // attempt to convert the value to a primitive type
14396 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can
14397 // be cheaply dirty-checked
14398 newValue = getValueOf(newValue);
14400 if (typeof newValue === 'object') {
14401 // objects/arrays are not supported - deep-watching them would be too expensive
14405 // fall-through to the primitive equality check
14409 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue);
14412 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) {
14413 var inputExpressions = parsedExpression.inputs;
14416 if (inputExpressions.length === 1) {
14417 var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails
14418 inputExpressions = inputExpressions[0];
14419 return scope.$watch(function expressionInputWatch(scope) {
14420 var newInputValue = inputExpressions(scope);
14421 if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) {
14422 lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]);
14423 oldInputValueOf = newInputValue && getValueOf(newInputValue);
14426 }, listener, objectEquality, prettyPrintExpression);
14429 var oldInputValueOfValues = [];
14430 var oldInputValues = [];
14431 for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
14432 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails
14433 oldInputValues[i] = null;
14436 return scope.$watch(function expressionInputsWatch(scope) {
14437 var changed = false;
14439 for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
14440 var newInputValue = inputExpressions[i](scope);
14441 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) {
14442 oldInputValues[i] = newInputValue;
14443 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue);
14448 lastResult = parsedExpression(scope, undefined, undefined, oldInputValues);
14452 }, listener, objectEquality, prettyPrintExpression);
14455 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14456 var unwatch, lastValue;
14457 return unwatch = scope.$watch(function oneTimeWatch(scope) {
14458 return parsedExpression(scope);
14459 }, function oneTimeListener(value, old, scope) {
14461 if (isFunction(listener)) {
14462 listener.apply(this, arguments);
14464 if (isDefined(value)) {
14465 scope.$$postDigest(function() {
14466 if (isDefined(lastValue)) {
14471 }, objectEquality);
14474 function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14475 var unwatch, lastValue;
14476 return unwatch = scope.$watch(function oneTimeWatch(scope) {
14477 return parsedExpression(scope);
14478 }, function oneTimeListener(value, old, scope) {
14480 if (isFunction(listener)) {
14481 listener.call(this, value, old, scope);
14483 if (isAllDefined(value)) {
14484 scope.$$postDigest(function() {
14485 if (isAllDefined(lastValue)) unwatch();
14488 }, objectEquality);
14490 function isAllDefined(value) {
14491 var allDefined = true;
14492 forEach(value, function(val) {
14493 if (!isDefined(val)) allDefined = false;
14499 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14501 return unwatch = scope.$watch(function constantWatch(scope) {
14502 return parsedExpression(scope);
14503 }, function constantListener(value, old, scope) {
14504 if (isFunction(listener)) {
14505 listener.apply(this, arguments);
14508 }, objectEquality);
14511 function addInterceptor(parsedExpression, interceptorFn) {
14512 if (!interceptorFn) return parsedExpression;
14513 var watchDelegate = parsedExpression.$$watchDelegate;
14514 var useInputs = false;
14517 watchDelegate !== oneTimeLiteralWatchDelegate &&
14518 watchDelegate !== oneTimeWatchDelegate;
14520 var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) {
14521 var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs);
14522 return interceptorFn(value, scope, locals);
14523 } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) {
14524 var value = parsedExpression(scope, locals, assign, inputs);
14525 var result = interceptorFn(value, scope, locals);
14526 // we only return the interceptor's result if the
14527 // initial value is defined (for bind-once)
14528 return isDefined(value) ? result : value;
14531 // Propagate $$watchDelegates other then inputsWatchDelegate
14532 if (parsedExpression.$$watchDelegate &&
14533 parsedExpression.$$watchDelegate !== inputsWatchDelegate) {
14534 fn.$$watchDelegate = parsedExpression.$$watchDelegate;
14535 } else if (!interceptorFn.$stateful) {
14536 // If there is an interceptor, but no watchDelegate then treat the interceptor like
14537 // we treat filters - it is assumed to be a pure function unless flagged with $stateful
14538 fn.$$watchDelegate = inputsWatchDelegate;
14539 useInputs = !parsedExpression.inputs;
14540 fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression];
14551 * @requires $rootScope
14554 * A service that helps you run functions asynchronously, and use their return values (or exceptions)
14555 * when they are done processing.
14557 * This is an implementation of promises/deferred objects inspired by
14558 * [Kris Kowal's Q](https://github.com/kriskowal/q).
14560 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred
14561 * implementations, and the other which resembles ES6 promises to some degree.
14565 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver`
14566 * function as the first argument. This is similar to the native Promise implementation from ES6 Harmony,
14567 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
14569 * While the constructor-style use is supported, not all of the supporting methods from ES6 Harmony promises are
14572 * It can be used like so:
14575 * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
14576 * // are available in the current lexical scope (they could have been injected or passed in).
14578 * function asyncGreet(name) {
14579 * // perform some asynchronous operation, resolve or reject the promise when appropriate.
14580 * return $q(function(resolve, reject) {
14581 * setTimeout(function() {
14582 * if (okToGreet(name)) {
14583 * resolve('Hello, ' + name + '!');
14585 * reject('Greeting ' + name + ' is not allowed.');
14591 * var promise = asyncGreet('Robin Hood');
14592 * promise.then(function(greeting) {
14593 * alert('Success: ' + greeting);
14594 * }, function(reason) {
14595 * alert('Failed: ' + reason);
14599 * Note: progress/notify callbacks are not currently supported via the ES6-style interface.
14601 * Note: unlike ES6 behaviour, an exception thrown in the constructor function will NOT implicitly reject the promise.
14603 * However, the more traditional CommonJS-style usage is still available, and documented below.
14605 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
14606 * interface for interacting with an object that represents the result of an action that is
14607 * performed asynchronously, and may or may not be finished at any given point in time.
14609 * From the perspective of dealing with error handling, deferred and promise APIs are to
14610 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
14613 * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
14614 * // are available in the current lexical scope (they could have been injected or passed in).
14616 * function asyncGreet(name) {
14617 * var deferred = $q.defer();
14619 * setTimeout(function() {
14620 * deferred.notify('About to greet ' + name + '.');
14622 * if (okToGreet(name)) {
14623 * deferred.resolve('Hello, ' + name + '!');
14625 * deferred.reject('Greeting ' + name + ' is not allowed.');
14629 * return deferred.promise;
14632 * var promise = asyncGreet('Robin Hood');
14633 * promise.then(function(greeting) {
14634 * alert('Success: ' + greeting);
14635 * }, function(reason) {
14636 * alert('Failed: ' + reason);
14637 * }, function(update) {
14638 * alert('Got notification: ' + update);
14642 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff
14643 * comes in the way of guarantees that promise and deferred APIs make, see
14644 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md.
14646 * Additionally the promise api allows for composition that is very hard to do with the
14647 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
14648 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the
14649 * section on serial or parallel joining of promises.
14651 * # The Deferred API
14653 * A new instance of deferred is constructed by calling `$q.defer()`.
14655 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs
14656 * that can be used for signaling the successful or unsuccessful completion, as well as the status
14661 * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection
14662 * constructed via `$q.reject`, the promise will be rejected instead.
14663 * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
14664 * resolving it with a rejection constructed via `$q.reject`.
14665 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called
14666 * multiple times before the promise is either resolved or rejected.
14670 * - promise – `{Promise}` – promise object associated with this deferred.
14673 * # The Promise API
14675 * A new promise instance is created when a deferred instance is created and can be retrieved by
14676 * calling `deferred.promise`.
14678 * The purpose of the promise object is to allow for interested parties to get access to the result
14679 * of the deferred task when it completes.
14683 * - `then(successCallback, errorCallback, notifyCallback)` – regardless of when the promise was or
14684 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously
14685 * as soon as the result is available. The callbacks are called with a single argument: the result
14686 * or rejection reason. Additionally, the notify callback may be called zero or more times to
14687 * provide a progress indication, before the promise is resolved or rejected.
14689 * This method *returns a new promise* which is resolved or rejected via the return value of the
14690 * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
14691 * with the value which is resolved in that promise using
14692 * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)).
14693 * It also notifies via the return value of the `notifyCallback` method. The promise cannot be
14694 * resolved or rejected from the notifyCallback method.
14696 * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)`
14698 * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise,
14699 * but to do so without modifying the final value. This is useful to release resources or do some
14700 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full
14701 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for
14702 * more information.
14704 * # Chaining promises
14706 * Because calling the `then` method of a promise returns a new derived promise, it is easily
14707 * possible to create a chain of promises:
14710 * promiseB = promiseA.then(function(result) {
14711 * return result + 1;
14714 * // promiseB will be resolved immediately after promiseA is resolved and its value
14715 * // will be the result of promiseA incremented by 1
14718 * It is possible to create chains of any length and since a promise can be resolved with another
14719 * promise (which will defer its resolution further), it is possible to pause/defer resolution of
14720 * the promises at any point in the chain. This makes it possible to implement powerful APIs like
14721 * $http's response interceptors.
14724 * # Differences between Kris Kowal's Q and $q
14726 * There are two main differences:
14728 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
14729 * mechanism in angular, which means faster propagation of resolution or rejection into your
14730 * models and avoiding unnecessary browser repaints, which would result in flickering UI.
14731 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
14732 * all the important functionality needed for common async tasks.
14737 * it('should simulate promise', inject(function($q, $rootScope) {
14738 * var deferred = $q.defer();
14739 * var promise = deferred.promise;
14740 * var resolvedValue;
14742 * promise.then(function(value) { resolvedValue = value; });
14743 * expect(resolvedValue).toBeUndefined();
14745 * // Simulate resolving of promise
14746 * deferred.resolve(123);
14747 * // Note that the 'then' function does not get called synchronously.
14748 * // This is because we want the promise API to always be async, whether or not
14749 * // it got called synchronously or asynchronously.
14750 * expect(resolvedValue).toBeUndefined();
14752 * // Propagate promise resolution to 'then' functions using $apply().
14753 * $rootScope.$apply();
14754 * expect(resolvedValue).toEqual(123);
14758 * @param {function(function, function)} resolver Function which is responsible for resolving or
14759 * rejecting the newly created promise. The first parameter is a function which resolves the
14760 * promise, the second parameter is a function which rejects the promise.
14762 * @returns {Promise} The newly created promise.
14764 function $QProvider() {
14766 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
14767 return qFactory(function(callback) {
14768 $rootScope.$evalAsync(callback);
14769 }, $exceptionHandler);
14773 function $$QProvider() {
14774 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) {
14775 return qFactory(function(callback) {
14776 $browser.defer(callback);
14777 }, $exceptionHandler);
14782 * Constructs a promise manager.
14784 * @param {function(function)} nextTick Function for executing functions in the next turn.
14785 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for
14786 * debugging purposes.
14787 * @returns {object} Promise manager.
14789 function qFactory(nextTick, exceptionHandler) {
14790 var $qMinErr = minErr('$q', TypeError);
14791 function callOnce(self, resolveFn, rejectFn) {
14792 var called = false;
14793 function wrap(fn) {
14794 return function(value) {
14795 if (called) return;
14797 fn.call(self, value);
14801 return [wrap(resolveFn), wrap(rejectFn)];
14806 * @name ng.$q#defer
14810 * Creates a `Deferred` object which represents a task which will finish in the future.
14812 * @returns {Deferred} Returns a new instance of deferred.
14814 var defer = function() {
14815 return new Deferred();
14818 function Promise() {
14819 this.$$state = { status: 0 };
14822 extend(Promise.prototype, {
14823 then: function(onFulfilled, onRejected, progressBack) {
14824 if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) {
14827 var result = new Deferred();
14829 this.$$state.pending = this.$$state.pending || [];
14830 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);
14831 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state);
14833 return result.promise;
14836 "catch": function(callback) {
14837 return this.then(null, callback);
14840 "finally": function(callback, progressBack) {
14841 return this.then(function(value) {
14842 return handleCallback(value, true, callback);
14843 }, function(error) {
14844 return handleCallback(error, false, callback);
14849 //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native
14850 function simpleBind(context, fn) {
14851 return function(value) {
14852 fn.call(context, value);
14856 function processQueue(state) {
14857 var fn, deferred, pending;
14859 pending = state.pending;
14860 state.processScheduled = false;
14861 state.pending = undefined;
14862 for (var i = 0, ii = pending.length; i < ii; ++i) {
14863 deferred = pending[i][0];
14864 fn = pending[i][state.status];
14866 if (isFunction(fn)) {
14867 deferred.resolve(fn(state.value));
14868 } else if (state.status === 1) {
14869 deferred.resolve(state.value);
14871 deferred.reject(state.value);
14874 deferred.reject(e);
14875 exceptionHandler(e);
14880 function scheduleProcessQueue(state) {
14881 if (state.processScheduled || !state.pending) return;
14882 state.processScheduled = true;
14883 nextTick(function() { processQueue(state); });
14886 function Deferred() {
14887 this.promise = new Promise();
14888 //Necessary to support unbound execution :/
14889 this.resolve = simpleBind(this, this.resolve);
14890 this.reject = simpleBind(this, this.reject);
14891 this.notify = simpleBind(this, this.notify);
14894 extend(Deferred.prototype, {
14895 resolve: function(val) {
14896 if (this.promise.$$state.status) return;
14897 if (val === this.promise) {
14898 this.$$reject($qMinErr(
14900 "Expected promise to be resolved with value other than itself '{0}'",
14903 this.$$resolve(val);
14908 $$resolve: function(val) {
14911 fns = callOnce(this, this.$$resolve, this.$$reject);
14913 if ((isObject(val) || isFunction(val))) then = val && val.then;
14914 if (isFunction(then)) {
14915 this.promise.$$state.status = -1;
14916 then.call(val, fns[0], fns[1], this.notify);
14918 this.promise.$$state.value = val;
14919 this.promise.$$state.status = 1;
14920 scheduleProcessQueue(this.promise.$$state);
14924 exceptionHandler(e);
14928 reject: function(reason) {
14929 if (this.promise.$$state.status) return;
14930 this.$$reject(reason);
14933 $$reject: function(reason) {
14934 this.promise.$$state.value = reason;
14935 this.promise.$$state.status = 2;
14936 scheduleProcessQueue(this.promise.$$state);
14939 notify: function(progress) {
14940 var callbacks = this.promise.$$state.pending;
14942 if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) {
14943 nextTick(function() {
14944 var callback, result;
14945 for (var i = 0, ii = callbacks.length; i < ii; i++) {
14946 result = callbacks[i][0];
14947 callback = callbacks[i][3];
14949 result.notify(isFunction(callback) ? callback(progress) : progress);
14951 exceptionHandler(e);
14965 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be
14966 * used to forward rejection in a chain of promises. If you are dealing with the last promise in
14967 * a promise chain, you don't need to worry about it.
14969 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of
14970 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via
14971 * a promise error callback and you want to forward the error to the promise derived from the
14972 * current promise, you have to "rethrow" the error by returning a rejection constructed via
14976 * promiseB = promiseA.then(function(result) {
14977 * // success: do something and resolve promiseB
14978 * // with the old or a new result
14980 * }, function(reason) {
14981 * // error: handle the error if possible and
14982 * // resolve promiseB with newPromiseOrValue,
14983 * // otherwise forward the rejection to promiseB
14984 * if (canHandle(reason)) {
14985 * // handle the error and recover
14986 * return newPromiseOrValue;
14988 * return $q.reject(reason);
14992 * @param {*} reason Constant, message, exception or an object representing the rejection reason.
14993 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
14995 var reject = function(reason) {
14996 var result = new Deferred();
14997 result.reject(reason);
14998 return result.promise;
15001 var makePromise = function makePromise(value, resolved) {
15002 var result = new Deferred();
15004 result.resolve(value);
15006 result.reject(value);
15008 return result.promise;
15011 var handleCallback = function handleCallback(value, isResolved, callback) {
15012 var callbackOutput = null;
15014 if (isFunction(callback)) callbackOutput = callback();
15016 return makePromise(e, false);
15018 if (isPromiseLike(callbackOutput)) {
15019 return callbackOutput.then(function() {
15020 return makePromise(value, isResolved);
15021 }, function(error) {
15022 return makePromise(error, false);
15025 return makePromise(value, isResolved);
15035 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
15036 * This is useful when you are dealing with an object that might or might not be a promise, or if
15037 * the promise comes from a source that can't be trusted.
15039 * @param {*} value Value or a promise
15040 * @param {Function=} successCallback
15041 * @param {Function=} errorCallback
15042 * @param {Function=} progressCallback
15043 * @returns {Promise} Returns a promise of the passed value or promise
15047 var when = function(value, callback, errback, progressBack) {
15048 var result = new Deferred();
15049 result.resolve(value);
15050 return result.promise.then(callback, errback, progressBack);
15059 * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6.
15061 * @param {*} value Value or a promise
15062 * @param {Function=} successCallback
15063 * @param {Function=} errorCallback
15064 * @param {Function=} progressCallback
15065 * @returns {Promise} Returns a promise of the passed value or promise
15067 var resolve = when;
15075 * Combines multiple promises into a single promise that is resolved when all of the input
15076 * promises are resolved.
15078 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
15079 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values,
15080 * each value corresponding to the promise at the same index/key in the `promises` array/hash.
15081 * If any of the promises is resolved with a rejection, this resulting promise will be rejected
15082 * with the same rejection value.
15085 function all(promises) {
15086 var deferred = new Deferred(),
15088 results = isArray(promises) ? [] : {};
15090 forEach(promises, function(promise, key) {
15092 when(promise).then(function(value) {
15093 if (results.hasOwnProperty(key)) return;
15094 results[key] = value;
15095 if (!(--counter)) deferred.resolve(results);
15096 }, function(reason) {
15097 if (results.hasOwnProperty(key)) return;
15098 deferred.reject(reason);
15102 if (counter === 0) {
15103 deferred.resolve(results);
15106 return deferred.promise;
15109 var $Q = function Q(resolver) {
15110 if (!isFunction(resolver)) {
15111 throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver);
15114 if (!(this instanceof Q)) {
15115 // More useful when $Q is the Promise itself.
15116 return new Q(resolver);
15119 var deferred = new Deferred();
15121 function resolveFn(value) {
15122 deferred.resolve(value);
15125 function rejectFn(reason) {
15126 deferred.reject(reason);
15129 resolver(resolveFn, rejectFn);
15131 return deferred.promise;
15135 $Q.reject = reject;
15137 $Q.resolve = resolve;
15143 function $$RAFProvider() { //rAF
15144 this.$get = ['$window', '$timeout', function($window, $timeout) {
15145 var requestAnimationFrame = $window.requestAnimationFrame ||
15146 $window.webkitRequestAnimationFrame;
15148 var cancelAnimationFrame = $window.cancelAnimationFrame ||
15149 $window.webkitCancelAnimationFrame ||
15150 $window.webkitCancelRequestAnimationFrame;
15152 var rafSupported = !!requestAnimationFrame;
15153 var raf = rafSupported
15155 var id = requestAnimationFrame(fn);
15156 return function() {
15157 cancelAnimationFrame(id);
15161 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666
15162 return function() {
15163 $timeout.cancel(timer);
15167 raf.supported = rafSupported;
15176 * The design decisions behind the scope are heavily favored for speed and memory consumption.
15178 * The typical use of scope is to watch the expressions, which most of the time return the same
15179 * value as last time so we optimize the operation.
15181 * Closures construction is expensive in terms of speed as well as memory:
15182 * - No closures, instead use prototypical inheritance for API
15183 * - Internal state needs to be stored on scope directly, which means that private state is
15184 * exposed as $$____ properties
15186 * Loop operations are optimized by using while(count--) { ... }
15187 * - This means that in order to keep the same order of execution as addition we have to add
15188 * items to the array at the beginning (unshift) instead of at the end (push)
15190 * Child scopes are created and removed often
15191 * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists
15193 * There are fewer watches than observers. This is why you don't want the observer to be implemented
15194 * in the same way as watch. Watch requires return of the initialization function which is expensive
15201 * @name $rootScopeProvider
15204 * Provider for the $rootScope service.
15209 * @name $rootScopeProvider#digestTtl
15212 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and
15213 * assuming that the model is unstable.
15215 * The current default is 10 iterations.
15217 * In complex applications it's possible that the dependencies between `$watch`s will result in
15218 * several digest iterations. However if an application needs more than the default 10 digest
15219 * iterations for its model to stabilize then you should investigate what is causing the model to
15220 * continuously change during the digest.
15222 * Increasing the TTL could have performance implications, so you should not change it without
15223 * proper justification.
15225 * @param {number} limit The number of digest iterations.
15234 * Every application has a single root {@link ng.$rootScope.Scope scope}.
15235 * All other scopes are descendant scopes of the root scope. Scopes provide separation
15236 * between the model and the view, via a mechanism for watching the model for changes.
15237 * They also provide event emission/broadcast and subscription facility. See the
15238 * {@link guide/scope developer guide on scopes}.
15240 function $RootScopeProvider() {
15242 var $rootScopeMinErr = minErr('$rootScope');
15243 var lastDirtyWatch = null;
15244 var applyAsyncId = null;
15246 this.digestTtl = function(value) {
15247 if (arguments.length) {
15253 function createChildScopeClass(parent) {
15254 function ChildScope() {
15255 this.$$watchers = this.$$nextSibling =
15256 this.$$childHead = this.$$childTail = null;
15257 this.$$listeners = {};
15258 this.$$listenerCount = {};
15259 this.$$watchersCount = 0;
15260 this.$id = nextUid();
15261 this.$$ChildScope = null;
15263 ChildScope.prototype = parent;
15267 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser',
15268 function($injector, $exceptionHandler, $parse, $browser) {
15270 function destroyChildScope($event) {
15271 $event.currentScope.$$destroyed = true;
15274 function cleanUpScope($scope) {
15277 // There is a memory leak in IE9 if all child scopes are not disconnected
15278 // completely when a scope is destroyed. So this code will recurse up through
15279 // all this scopes children
15281 // See issue https://github.com/angular/angular.js/issues/10706
15282 $scope.$$childHead && cleanUpScope($scope.$$childHead);
15283 $scope.$$nextSibling && cleanUpScope($scope.$$nextSibling);
15286 // The code below works around IE9 and V8's memory leaks
15289 // - https://code.google.com/p/v8/issues/detail?id=2073#c26
15290 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909
15291 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
15293 $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead =
15294 $scope.$$childTail = $scope.$root = $scope.$$watchers = null;
15299 * @name $rootScope.Scope
15302 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
15303 * {@link auto.$injector $injector}. Child scopes are created using the
15304 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
15305 * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for
15306 * an in-depth introduction and usage examples.
15310 * A scope can inherit from a parent scope, as in this example:
15312 var parent = $rootScope;
15313 var child = parent.$new();
15315 parent.salutation = "Hello";
15316 expect(child.salutation).toEqual('Hello');
15318 child.salutation = "Welcome";
15319 expect(child.salutation).toEqual('Welcome');
15320 expect(parent.salutation).toEqual('Hello');
15323 * When interacting with `Scope` in tests, additional helper methods are available on the
15324 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional
15328 * @param {Object.<string, function()>=} providers Map of service factory which need to be
15329 * provided for the current scope. Defaults to {@link ng}.
15330 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should
15331 * append/override services provided by `providers`. This is handy
15332 * when unit-testing and having the need to override a default
15334 * @returns {Object} Newly created scope.
15338 this.$id = nextUid();
15339 this.$$phase = this.$parent = this.$$watchers =
15340 this.$$nextSibling = this.$$prevSibling =
15341 this.$$childHead = this.$$childTail = null;
15343 this.$$destroyed = false;
15344 this.$$listeners = {};
15345 this.$$listenerCount = {};
15346 this.$$watchersCount = 0;
15347 this.$$isolateBindings = null;
15352 * @name $rootScope.Scope#$id
15355 * Unique scope ID (monotonically increasing) useful for debugging.
15360 * @name $rootScope.Scope#$parent
15363 * Reference to the parent scope.
15368 * @name $rootScope.Scope#$root
15371 * Reference to the root scope.
15374 Scope.prototype = {
15375 constructor: Scope,
15378 * @name $rootScope.Scope#$new
15382 * Creates a new child {@link ng.$rootScope.Scope scope}.
15384 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event.
15385 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
15387 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is
15388 * desired for the scope and its child scopes to be permanently detached from the parent and
15389 * thus stop participating in model change detection and listener notification by invoking.
15391 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the
15392 * parent scope. The scope is isolated, as it can not see parent scope properties.
15393 * When creating widgets, it is useful for the widget to not accidentally read parent
15396 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent`
15397 * of the newly created scope. Defaults to `this` scope if not provided.
15398 * This is used when creating a transclude scope to correctly place it
15399 * in the scope hierarchy while maintaining the correct prototypical
15402 * @returns {Object} The newly created child scope.
15405 $new: function(isolate, parent) {
15408 parent = parent || this;
15411 child = new Scope();
15412 child.$root = this.$root;
15414 // Only create a child scope class if somebody asks for one,
15415 // but cache it to allow the VM to optimize lookups.
15416 if (!this.$$ChildScope) {
15417 this.$$ChildScope = createChildScopeClass(this);
15419 child = new this.$$ChildScope();
15421 child.$parent = parent;
15422 child.$$prevSibling = parent.$$childTail;
15423 if (parent.$$childHead) {
15424 parent.$$childTail.$$nextSibling = child;
15425 parent.$$childTail = child;
15427 parent.$$childHead = parent.$$childTail = child;
15430 // When the new scope is not isolated or we inherit from `this`, and
15431 // the parent scope is destroyed, the property `$$destroyed` is inherited
15432 // prototypically. In all other cases, this property needs to be set
15433 // when the parent scope is destroyed.
15434 // The listener needs to be added after the parent is set
15435 if (isolate || parent != this) child.$on('$destroy', destroyChildScope);
15442 * @name $rootScope.Scope#$watch
15446 * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
15448 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest
15449 * $digest()} and should return the value that will be watched. (`watchExpression` should not change
15450 * its value when executed multiple times with the same input because it may be executed multiple
15451 * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be
15452 * [idempotent](http://en.wikipedia.org/wiki/Idempotence).
15453 * - The `listener` is called only when the value from the current `watchExpression` and the
15454 * previous call to `watchExpression` are not equal (with the exception of the initial run,
15455 * see below). Inequality is determined according to reference inequality,
15456 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators)
15457 * via the `!==` Javascript operator, unless `objectEquality == true`
15459 * - When `objectEquality == true`, inequality of the `watchExpression` is determined
15460 * according to the {@link angular.equals} function. To save the value of the object for
15461 * later comparison, the {@link angular.copy} function is used. This therefore means that
15462 * watching complex objects will have adverse memory and performance implications.
15463 * - The watch `listener` may change the model, which may trigger other `listener`s to fire.
15464 * This is achieved by rerunning the watchers until no changes are detected. The rerun
15465 * iteration limit is 10 to prevent an infinite loop deadlock.
15468 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
15469 * you can register a `watchExpression` function with no `listener`. (Be prepared for
15470 * multiple calls to your `watchExpression` because it will execute multiple times in a
15471 * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.)
15473 * After a watcher is registered with the scope, the `listener` fn is called asynchronously
15474 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
15475 * watcher. In rare cases, this is undesirable because the listener is called when the result
15476 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
15477 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the
15478 * listener was called due to initialization.
15484 // let's assume that scope was dependency injected as the $rootScope
15485 var scope = $rootScope;
15486 scope.name = 'misko';
15489 expect(scope.counter).toEqual(0);
15490 scope.$watch('name', function(newValue, oldValue) {
15491 scope.counter = scope.counter + 1;
15493 expect(scope.counter).toEqual(0);
15496 // the listener is always called during the first $digest loop after it was registered
15497 expect(scope.counter).toEqual(1);
15500 // but now it will not be called unless the value changes
15501 expect(scope.counter).toEqual(1);
15503 scope.name = 'adam';
15505 expect(scope.counter).toEqual(2);
15509 // Using a function as a watchExpression
15511 scope.foodCounter = 0;
15512 expect(scope.foodCounter).toEqual(0);
15514 // This function returns the value being watched. It is called for each turn of the $digest loop
15515 function() { return food; },
15516 // This is the change listener, called when the value returned from the above function changes
15517 function(newValue, oldValue) {
15518 if ( newValue !== oldValue ) {
15519 // Only increment the counter if the value changed
15520 scope.foodCounter = scope.foodCounter + 1;
15524 // No digest has been run so the counter will be zero
15525 expect(scope.foodCounter).toEqual(0);
15527 // Run the digest but since food has not changed count will still be zero
15529 expect(scope.foodCounter).toEqual(0);
15531 // Update food and run digest. Now the counter will increment
15532 food = 'cheeseburger';
15534 expect(scope.foodCounter).toEqual(1);
15540 * @param {(function()|string)} watchExpression Expression that is evaluated on each
15541 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers
15542 * a call to the `listener`.
15544 * - `string`: Evaluated as {@link guide/expression expression}
15545 * - `function(scope)`: called with current `scope` as a parameter.
15546 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value
15547 * of `watchExpression` changes.
15549 * - `newVal` contains the current value of the `watchExpression`
15550 * - `oldVal` contains the previous value of the `watchExpression`
15551 * - `scope` refers to the current scope
15552 * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of
15553 * comparing for reference equality.
15554 * @returns {function()} Returns a deregistration function for this listener.
15556 $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) {
15557 var get = $parse(watchExp);
15559 if (get.$$watchDelegate) {
15560 return get.$$watchDelegate(this, listener, objectEquality, get, watchExp);
15563 array = scope.$$watchers,
15566 last: initWatchVal,
15568 exp: prettyPrintExpression || watchExp,
15569 eq: !!objectEquality
15572 lastDirtyWatch = null;
15574 if (!isFunction(listener)) {
15579 array = scope.$$watchers = [];
15581 // we use unshift since we use a while loop in $digest for speed.
15582 // the while loop reads in reverse order.
15583 array.unshift(watcher);
15584 incrementWatchersCount(this, 1);
15586 return function deregisterWatch() {
15587 if (arrayRemove(array, watcher) >= 0) {
15588 incrementWatchersCount(scope, -1);
15590 lastDirtyWatch = null;
15596 * @name $rootScope.Scope#$watchGroup
15600 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`.
15601 * If any one expression in the collection changes the `listener` is executed.
15603 * - The items in the `watchExpressions` array are observed via standard $watch operation and are examined on every
15604 * call to $digest() to see if any items changes.
15605 * - The `listener` is called whenever any expression in the `watchExpressions` array changes.
15607 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually
15608 * watched using {@link ng.$rootScope.Scope#$watch $watch()}
15610 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any
15611 * expression in `watchExpressions` changes
15612 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching
15613 * those of `watchExpression`
15614 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching
15615 * those of `watchExpression`
15616 * The `scope` refers to the current scope.
15617 * @returns {function()} Returns a de-registration function for all listeners.
15619 $watchGroup: function(watchExpressions, listener) {
15620 var oldValues = new Array(watchExpressions.length);
15621 var newValues = new Array(watchExpressions.length);
15622 var deregisterFns = [];
15624 var changeReactionScheduled = false;
15625 var firstRun = true;
15627 if (!watchExpressions.length) {
15628 // No expressions means we call the listener ASAP
15629 var shouldCall = true;
15630 self.$evalAsync(function() {
15631 if (shouldCall) listener(newValues, newValues, self);
15633 return function deregisterWatchGroup() {
15634 shouldCall = false;
15638 if (watchExpressions.length === 1) {
15639 // Special case size of one
15640 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) {
15641 newValues[0] = value;
15642 oldValues[0] = oldValue;
15643 listener(newValues, (value === oldValue) ? newValues : oldValues, scope);
15647 forEach(watchExpressions, function(expr, i) {
15648 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) {
15649 newValues[i] = value;
15650 oldValues[i] = oldValue;
15651 if (!changeReactionScheduled) {
15652 changeReactionScheduled = true;
15653 self.$evalAsync(watchGroupAction);
15656 deregisterFns.push(unwatchFn);
15659 function watchGroupAction() {
15660 changeReactionScheduled = false;
15664 listener(newValues, newValues, self);
15666 listener(newValues, oldValues, self);
15670 return function deregisterWatchGroup() {
15671 while (deregisterFns.length) {
15672 deregisterFns.shift()();
15680 * @name $rootScope.Scope#$watchCollection
15684 * Shallow watches the properties of an object and fires whenever any of the properties change
15685 * (for arrays, this implies watching the array items; for object maps, this implies watching
15686 * the properties). If a change is detected, the `listener` callback is fired.
15688 * - The `obj` collection is observed via standard $watch operation and is examined on every
15689 * call to $digest() to see if any items have been added, removed, or moved.
15690 * - The `listener` is called whenever anything within the `obj` has changed. Examples include
15691 * adding, removing, and moving items belonging to an object or array.
15696 $scope.names = ['igor', 'matias', 'misko', 'james'];
15697 $scope.dataCount = 4;
15699 $scope.$watchCollection('names', function(newNames, oldNames) {
15700 $scope.dataCount = newNames.length;
15703 expect($scope.dataCount).toEqual(4);
15706 //still at 4 ... no changes
15707 expect($scope.dataCount).toEqual(4);
15709 $scope.names.pop();
15712 //now there's been a change
15713 expect($scope.dataCount).toEqual(3);
15717 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The
15718 * expression value should evaluate to an object or an array which is observed on each
15719 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the
15720 * collection will trigger a call to the `listener`.
15722 * @param {function(newCollection, oldCollection, scope)} listener a callback function called
15723 * when a change is detected.
15724 * - The `newCollection` object is the newly modified data obtained from the `obj` expression
15725 * - The `oldCollection` object is a copy of the former collection data.
15726 * Due to performance considerations, the`oldCollection` value is computed only if the
15727 * `listener` function declares two or more arguments.
15728 * - The `scope` argument refers to the current scope.
15730 * @returns {function()} Returns a de-registration function for this listener. When the
15731 * de-registration function is executed, the internal watch operation is terminated.
15733 $watchCollection: function(obj, listener) {
15734 $watchCollectionInterceptor.$stateful = true;
15737 // the current value, updated on each dirty-check run
15739 // a shallow copy of the newValue from the last dirty-check run,
15740 // updated to match newValue during dirty-check run
15742 // a shallow copy of the newValue from when the last change happened
15744 // only track veryOldValue if the listener is asking for it
15745 var trackVeryOldValue = (listener.length > 1);
15746 var changeDetected = 0;
15747 var changeDetector = $parse(obj, $watchCollectionInterceptor);
15748 var internalArray = [];
15749 var internalObject = {};
15750 var initRun = true;
15753 function $watchCollectionInterceptor(_value) {
15755 var newLength, key, bothNaN, newItem, oldItem;
15757 // If the new value is undefined, then return undefined as the watch may be a one-time watch
15758 if (isUndefined(newValue)) return;
15760 if (!isObject(newValue)) { // if primitive
15761 if (oldValue !== newValue) {
15762 oldValue = newValue;
15765 } else if (isArrayLike(newValue)) {
15766 if (oldValue !== internalArray) {
15767 // we are transitioning from something which was not an array into array.
15768 oldValue = internalArray;
15769 oldLength = oldValue.length = 0;
15773 newLength = newValue.length;
15775 if (oldLength !== newLength) {
15776 // if lengths do not match we need to trigger change notification
15778 oldValue.length = oldLength = newLength;
15780 // copy the items to oldValue and look for changes.
15781 for (var i = 0; i < newLength; i++) {
15782 oldItem = oldValue[i];
15783 newItem = newValue[i];
15785 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
15786 if (!bothNaN && (oldItem !== newItem)) {
15788 oldValue[i] = newItem;
15792 if (oldValue !== internalObject) {
15793 // we are transitioning from something which was not an object into object.
15794 oldValue = internalObject = {};
15798 // copy the items to oldValue and look for changes.
15800 for (key in newValue) {
15801 if (hasOwnProperty.call(newValue, key)) {
15803 newItem = newValue[key];
15804 oldItem = oldValue[key];
15806 if (key in oldValue) {
15807 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
15808 if (!bothNaN && (oldItem !== newItem)) {
15810 oldValue[key] = newItem;
15814 oldValue[key] = newItem;
15819 if (oldLength > newLength) {
15820 // we used to have more keys, need to find them and destroy them.
15822 for (key in oldValue) {
15823 if (!hasOwnProperty.call(newValue, key)) {
15825 delete oldValue[key];
15830 return changeDetected;
15833 function $watchCollectionAction() {
15836 listener(newValue, newValue, self);
15838 listener(newValue, veryOldValue, self);
15841 // make a copy for the next time a collection is changed
15842 if (trackVeryOldValue) {
15843 if (!isObject(newValue)) {
15845 veryOldValue = newValue;
15846 } else if (isArrayLike(newValue)) {
15847 veryOldValue = new Array(newValue.length);
15848 for (var i = 0; i < newValue.length; i++) {
15849 veryOldValue[i] = newValue[i];
15851 } else { // if object
15853 for (var key in newValue) {
15854 if (hasOwnProperty.call(newValue, key)) {
15855 veryOldValue[key] = newValue[key];
15862 return this.$watch(changeDetector, $watchCollectionAction);
15867 * @name $rootScope.Scope#$digest
15871 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and
15872 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change
15873 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers}
15874 * until no more listeners are firing. This means that it is possible to get into an infinite
15875 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of
15876 * iterations exceeds 10.
15878 * Usually, you don't call `$digest()` directly in
15879 * {@link ng.directive:ngController controllers} or in
15880 * {@link ng.$compileProvider#directive directives}.
15881 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within
15882 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`.
15884 * If you want to be notified whenever `$digest()` is called,
15885 * you can register a `watchExpression` function with
15886 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`.
15888 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle.
15893 scope.name = 'misko';
15896 expect(scope.counter).toEqual(0);
15897 scope.$watch('name', function(newValue, oldValue) {
15898 scope.counter = scope.counter + 1;
15900 expect(scope.counter).toEqual(0);
15903 // the listener is always called during the first $digest loop after it was registered
15904 expect(scope.counter).toEqual(1);
15907 // but now it will not be called unless the value changes
15908 expect(scope.counter).toEqual(1);
15910 scope.name = 'adam';
15912 expect(scope.counter).toEqual(2);
15916 $digest: function() {
15917 var watch, value, last,
15921 next, current, target = this,
15923 logIdx, logMsg, asyncTask;
15925 beginPhase('$digest');
15926 // Check for changes to browser url that happened in sync before the call to $digest
15927 $browser.$$checkUrlChange();
15929 if (this === $rootScope && applyAsyncId !== null) {
15930 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then
15931 // cancel the scheduled $apply and flush the queue of expressions to be evaluated.
15932 $browser.defer.cancel(applyAsyncId);
15936 lastDirtyWatch = null;
15938 do { // "while dirty" loop
15942 while (asyncQueue.length) {
15944 asyncTask = asyncQueue.shift();
15945 asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals);
15947 $exceptionHandler(e);
15949 lastDirtyWatch = null;
15952 traverseScopesLoop:
15953 do { // "traverse the scopes" loop
15954 if ((watchers = current.$$watchers)) {
15955 // process our watches
15956 length = watchers.length;
15959 watch = watchers[length];
15960 // Most common watches are on primitives, in which case we can short
15961 // circuit it with === operator, only when === fails do we use .equals
15963 if ((value = watch.get(current)) !== (last = watch.last) &&
15965 ? equals(value, last)
15966 : (typeof value === 'number' && typeof last === 'number'
15967 && isNaN(value) && isNaN(last)))) {
15969 lastDirtyWatch = watch;
15970 watch.last = watch.eq ? copy(value, null) : value;
15971 watch.fn(value, ((last === initWatchVal) ? value : last), current);
15974 if (!watchLog[logIdx]) watchLog[logIdx] = [];
15975 watchLog[logIdx].push({
15976 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
15981 } else if (watch === lastDirtyWatch) {
15982 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers
15983 // have already been tested.
15985 break traverseScopesLoop;
15989 $exceptionHandler(e);
15994 // Insanity Warning: scope depth-first traversal
15995 // yes, this code is a bit crazy, but it works and we have tests to prove it!
15996 // this piece should be kept in sync with the traversal in $broadcast
15997 if (!(next = ((current.$$watchersCount && current.$$childHead) ||
15998 (current !== target && current.$$nextSibling)))) {
15999 while (current !== target && !(next = current.$$nextSibling)) {
16000 current = current.$parent;
16003 } while ((current = next));
16005 // `break traverseScopesLoop;` takes us to here
16007 if ((dirty || asyncQueue.length) && !(ttl--)) {
16009 throw $rootScopeMinErr('infdig',
16010 '{0} $digest() iterations reached. Aborting!\n' +
16011 'Watchers fired in the last 5 iterations: {1}',
16015 } while (dirty || asyncQueue.length);
16019 while (postDigestQueue.length) {
16021 postDigestQueue.shift()();
16023 $exceptionHandler(e);
16031 * @name $rootScope.Scope#$destroy
16032 * @eventType broadcast on scope being destroyed
16035 * Broadcasted when a scope and its children are being destroyed.
16037 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
16038 * clean up DOM bindings before an element is removed from the DOM.
16043 * @name $rootScope.Scope#$destroy
16047 * Removes the current scope (and all of its children) from the parent scope. Removal implies
16048 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
16049 * propagate to the current scope and its children. Removal also implies that the current
16050 * scope is eligible for garbage collection.
16052 * The `$destroy()` is usually used by directives such as
16053 * {@link ng.directive:ngRepeat ngRepeat} for managing the
16054 * unrolling of the loop.
16056 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope.
16057 * Application code can register a `$destroy` event handler that will give it a chance to
16058 * perform any necessary cleanup.
16060 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
16061 * clean up DOM bindings before an element is removed from the DOM.
16063 $destroy: function() {
16064 // We can't destroy a scope that has been already destroyed.
16065 if (this.$$destroyed) return;
16066 var parent = this.$parent;
16068 this.$broadcast('$destroy');
16069 this.$$destroyed = true;
16071 if (this === $rootScope) {
16072 //Remove handlers attached to window when $rootScope is removed
16073 $browser.$$applicationDestroyed();
16076 incrementWatchersCount(this, -this.$$watchersCount);
16077 for (var eventName in this.$$listenerCount) {
16078 decrementListenerCount(this, this.$$listenerCount[eventName], eventName);
16081 // sever all the references to parent scopes (after this cleanup, the current scope should
16082 // not be retained by any of our references and should be eligible for garbage collection)
16083 if (parent && parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
16084 if (parent && parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
16085 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
16086 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
16088 // Disable listeners, watchers and apply/digest methods
16089 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop;
16090 this.$on = this.$watch = this.$watchGroup = function() { return noop; };
16091 this.$$listeners = {};
16093 // Disconnect the next sibling to prevent `cleanUpScope` destroying those too
16094 this.$$nextSibling = null;
16095 cleanUpScope(this);
16100 * @name $rootScope.Scope#$eval
16104 * Executes the `expression` on the current scope and returns the result. Any exceptions in
16105 * the expression are propagated (uncaught). This is useful when evaluating Angular
16110 var scope = ng.$rootScope.Scope();
16114 expect(scope.$eval('a+b')).toEqual(3);
16115 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
16118 * @param {(string|function())=} expression An angular expression to be executed.
16120 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16121 * - `function(scope)`: execute the function with the current `scope` parameter.
16123 * @param {(object)=} locals Local variables object, useful for overriding values in scope.
16124 * @returns {*} The result of evaluating the expression.
16126 $eval: function(expr, locals) {
16127 return $parse(expr)(this, locals);
16132 * @name $rootScope.Scope#$evalAsync
16136 * Executes the expression on the current scope at a later point in time.
16138 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only
16141 * - it will execute after the function that scheduled the evaluation (preferably before DOM
16143 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after
16144 * `expression` execution.
16146 * Any exceptions from the execution of the expression are forwarded to the
16147 * {@link ng.$exceptionHandler $exceptionHandler} service.
16149 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle
16150 * will be scheduled. However, it is encouraged to always call code that changes the model
16151 * from within an `$apply` call. That includes code evaluated via `$evalAsync`.
16153 * @param {(string|function())=} expression An angular expression to be executed.
16155 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16156 * - `function(scope)`: execute the function with the current `scope` parameter.
16158 * @param {(object)=} locals Local variables object, useful for overriding values in scope.
16160 $evalAsync: function(expr, locals) {
16161 // if we are outside of an $digest loop and this is the first time we are scheduling async
16162 // task also schedule async auto-flush
16163 if (!$rootScope.$$phase && !asyncQueue.length) {
16164 $browser.defer(function() {
16165 if (asyncQueue.length) {
16166 $rootScope.$digest();
16171 asyncQueue.push({scope: this, expression: expr, locals: locals});
16174 $$postDigest: function(fn) {
16175 postDigestQueue.push(fn);
16180 * @name $rootScope.Scope#$apply
16184 * `$apply()` is used to execute an expression in angular from outside of the angular
16185 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries).
16186 * Because we are calling into the angular framework we need to perform proper scope life
16187 * cycle of {@link ng.$exceptionHandler exception handling},
16188 * {@link ng.$rootScope.Scope#$digest executing watches}.
16192 * # Pseudo-Code of `$apply()`
16194 function $apply(expr) {
16196 return $eval(expr);
16198 $exceptionHandler(e);
16206 * Scope's `$apply()` method transitions through the following stages:
16208 * 1. The {@link guide/expression expression} is executed using the
16209 * {@link ng.$rootScope.Scope#$eval $eval()} method.
16210 * 2. Any exceptions from the execution of the expression are forwarded to the
16211 * {@link ng.$exceptionHandler $exceptionHandler} service.
16212 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the
16213 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
16216 * @param {(string|function())=} exp An angular expression to be executed.
16218 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16219 * - `function(scope)`: execute the function with current `scope` parameter.
16221 * @returns {*} The result of evaluating the expression.
16223 $apply: function(expr) {
16225 beginPhase('$apply');
16227 return this.$eval(expr);
16232 $exceptionHandler(e);
16235 $rootScope.$digest();
16237 $exceptionHandler(e);
16245 * @name $rootScope.Scope#$applyAsync
16249 * Schedule the invocation of $apply to occur at a later time. The actual time difference
16250 * varies across browsers, but is typically around ~10 milliseconds.
16252 * This can be used to queue up multiple expressions which need to be evaluated in the same
16255 * @param {(string|function())=} exp An angular expression to be executed.
16257 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16258 * - `function(scope)`: execute the function with current `scope` parameter.
16260 $applyAsync: function(expr) {
16262 expr && applyAsyncQueue.push($applyAsyncExpression);
16263 scheduleApplyAsync();
16265 function $applyAsyncExpression() {
16272 * @name $rootScope.Scope#$on
16276 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for
16277 * discussion of event life cycle.
16279 * The event listener function format is: `function(event, args...)`. The `event` object
16280 * passed into the listener has the following attributes:
16282 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or
16284 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the
16285 * event propagates through the scope hierarchy, this property is set to null.
16286 * - `name` - `{string}`: name of the event.
16287 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel
16288 * further event propagation (available only for events that were `$emit`-ed).
16289 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag
16291 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
16293 * @param {string} name Event name to listen on.
16294 * @param {function(event, ...args)} listener Function to call when the event is emitted.
16295 * @returns {function()} Returns a deregistration function for this listener.
16297 $on: function(name, listener) {
16298 var namedListeners = this.$$listeners[name];
16299 if (!namedListeners) {
16300 this.$$listeners[name] = namedListeners = [];
16302 namedListeners.push(listener);
16304 var current = this;
16306 if (!current.$$listenerCount[name]) {
16307 current.$$listenerCount[name] = 0;
16309 current.$$listenerCount[name]++;
16310 } while ((current = current.$parent));
16313 return function() {
16314 var indexOfListener = namedListeners.indexOf(listener);
16315 if (indexOfListener !== -1) {
16316 namedListeners[indexOfListener] = null;
16317 decrementListenerCount(self, 1, name);
16325 * @name $rootScope.Scope#$emit
16329 * Dispatches an event `name` upwards through the scope hierarchy notifying the
16330 * registered {@link ng.$rootScope.Scope#$on} listeners.
16332 * The event life cycle starts at the scope on which `$emit` was called. All
16333 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
16334 * notified. Afterwards, the event traverses upwards toward the root scope and calls all
16335 * registered listeners along the way. The event will stop propagating if one of the listeners
16338 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
16339 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
16341 * @param {string} name Event name to emit.
16342 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
16343 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}).
16345 $emit: function(name, args) {
16349 stopPropagation = false,
16352 targetScope: scope,
16353 stopPropagation: function() {stopPropagation = true;},
16354 preventDefault: function() {
16355 event.defaultPrevented = true;
16357 defaultPrevented: false
16359 listenerArgs = concat([event], arguments, 1),
16363 namedListeners = scope.$$listeners[name] || empty;
16364 event.currentScope = scope;
16365 for (i = 0, length = namedListeners.length; i < length; i++) {
16367 // if listeners were deregistered, defragment the array
16368 if (!namedListeners[i]) {
16369 namedListeners.splice(i, 1);
16375 //allow all listeners attached to the current scope to run
16376 namedListeners[i].apply(null, listenerArgs);
16378 $exceptionHandler(e);
16381 //if any listener on the current scope stops propagation, prevent bubbling
16382 if (stopPropagation) {
16383 event.currentScope = null;
16387 scope = scope.$parent;
16390 event.currentScope = null;
16398 * @name $rootScope.Scope#$broadcast
16402 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the
16403 * registered {@link ng.$rootScope.Scope#$on} listeners.
16405 * The event life cycle starts at the scope on which `$broadcast` was called. All
16406 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
16407 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current
16408 * scope and calls all registered listeners along the way. The event cannot be canceled.
16410 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
16411 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
16413 * @param {string} name Event name to broadcast.
16414 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
16415 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
16417 $broadcast: function(name, args) {
16423 targetScope: target,
16424 preventDefault: function() {
16425 event.defaultPrevented = true;
16427 defaultPrevented: false
16430 if (!target.$$listenerCount[name]) return event;
16432 var listenerArgs = concat([event], arguments, 1),
16433 listeners, i, length;
16435 //down while you can, then up and next sibling or up and next sibling until back at root
16436 while ((current = next)) {
16437 event.currentScope = current;
16438 listeners = current.$$listeners[name] || [];
16439 for (i = 0, length = listeners.length; i < length; i++) {
16440 // if listeners were deregistered, defragment the array
16441 if (!listeners[i]) {
16442 listeners.splice(i, 1);
16449 listeners[i].apply(null, listenerArgs);
16451 $exceptionHandler(e);
16455 // Insanity Warning: scope depth-first traversal
16456 // yes, this code is a bit crazy, but it works and we have tests to prove it!
16457 // this piece should be kept in sync with the traversal in $digest
16458 // (though it differs due to having the extra check for $$listenerCount)
16459 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
16460 (current !== target && current.$$nextSibling)))) {
16461 while (current !== target && !(next = current.$$nextSibling)) {
16462 current = current.$parent;
16467 event.currentScope = null;
16472 var $rootScope = new Scope();
16474 //The internal queues. Expose them on the $rootScope for debugging/testing purposes.
16475 var asyncQueue = $rootScope.$$asyncQueue = [];
16476 var postDigestQueue = $rootScope.$$postDigestQueue = [];
16477 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = [];
16482 function beginPhase(phase) {
16483 if ($rootScope.$$phase) {
16484 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase);
16487 $rootScope.$$phase = phase;
16490 function clearPhase() {
16491 $rootScope.$$phase = null;
16494 function incrementWatchersCount(current, count) {
16496 current.$$watchersCount += count;
16497 } while ((current = current.$parent));
16500 function decrementListenerCount(current, count, name) {
16502 current.$$listenerCount[name] -= count;
16504 if (current.$$listenerCount[name] === 0) {
16505 delete current.$$listenerCount[name];
16507 } while ((current = current.$parent));
16511 * function used as an initial value for watchers.
16512 * because it's unique we can easily tell it apart from other values
16514 function initWatchVal() {}
16516 function flushApplyAsync() {
16517 while (applyAsyncQueue.length) {
16519 applyAsyncQueue.shift()();
16521 $exceptionHandler(e);
16524 applyAsyncId = null;
16527 function scheduleApplyAsync() {
16528 if (applyAsyncId === null) {
16529 applyAsyncId = $browser.defer(function() {
16530 $rootScope.$apply(flushApplyAsync);
16539 * Private service to sanitize uris for links and images. Used by $compile and $sanitize.
16541 function $$SanitizeUriProvider() {
16542 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/,
16543 imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/;
16547 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
16548 * urls during a[href] sanitization.
16550 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
16552 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
16553 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
16554 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
16555 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
16557 * @param {RegExp=} regexp New regexp to whitelist urls with.
16558 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
16559 * chaining otherwise.
16561 this.aHrefSanitizationWhitelist = function(regexp) {
16562 if (isDefined(regexp)) {
16563 aHrefSanitizationWhitelist = regexp;
16566 return aHrefSanitizationWhitelist;
16572 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
16573 * urls during img[src] sanitization.
16575 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
16577 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
16578 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
16579 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
16580 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
16582 * @param {RegExp=} regexp New regexp to whitelist urls with.
16583 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
16584 * chaining otherwise.
16586 this.imgSrcSanitizationWhitelist = function(regexp) {
16587 if (isDefined(regexp)) {
16588 imgSrcSanitizationWhitelist = regexp;
16591 return imgSrcSanitizationWhitelist;
16594 this.$get = function() {
16595 return function sanitizeUri(uri, isImage) {
16596 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
16598 normalizedVal = urlResolve(uri).href;
16599 if (normalizedVal !== '' && !normalizedVal.match(regex)) {
16600 return 'unsafe:' + normalizedVal;
16607 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
16608 * Any commits to this file should be reviewed with security in mind. *
16609 * Changes to this file can potentially create security vulnerabilities. *
16610 * An approval from 2 Core members with history of modifying *
16611 * this file is required. *
16613 * Does the change somehow allow for arbitrary javascript to be executed? *
16614 * Or allows for someone to change the prototype of built-in objects? *
16615 * Or gives undesired access to variables likes document or window? *
16616 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
16618 var $sceMinErr = minErr('$sce');
16620 var SCE_CONTEXTS = {
16624 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a
16625 // url. (e.g. ng-include, script src, templateUrl)
16626 RESOURCE_URL: 'resourceUrl',
16630 // Helper functions follow.
16632 function adjustMatcher(matcher) {
16633 if (matcher === 'self') {
16635 } else if (isString(matcher)) {
16636 // Strings match exactly except for 2 wildcards - '*' and '**'.
16637 // '*' matches any character except those from the set ':/.?&'.
16638 // '**' matches any character (like .* in a RegExp).
16639 // More than 2 *'s raises an error as it's ill defined.
16640 if (matcher.indexOf('***') > -1) {
16641 throw $sceMinErr('iwcard',
16642 'Illegal sequence *** in string matcher. String: {0}', matcher);
16644 matcher = escapeForRegexp(matcher).
16645 replace('\\*\\*', '.*').
16646 replace('\\*', '[^:/.?&;]*');
16647 return new RegExp('^' + matcher + '$');
16648 } else if (isRegExp(matcher)) {
16649 // The only other type of matcher allowed is a Regexp.
16650 // Match entire URL / disallow partial matches.
16651 // Flags are reset (i.e. no global, ignoreCase or multiline)
16652 return new RegExp('^' + matcher.source + '$');
16654 throw $sceMinErr('imatcher',
16655 'Matchers may only be "self", string patterns or RegExp objects');
16660 function adjustMatchers(matchers) {
16661 var adjustedMatchers = [];
16662 if (isDefined(matchers)) {
16663 forEach(matchers, function(matcher) {
16664 adjustedMatchers.push(adjustMatcher(matcher));
16667 return adjustedMatchers;
16673 * @name $sceDelegate
16678 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict
16679 * Contextual Escaping (SCE)} services to AngularJS.
16681 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of
16682 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is
16683 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to
16684 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things
16685 * work because `$sce` delegates to `$sceDelegate` for these operations.
16687 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service.
16689 * The default instance of `$sceDelegate` should work out of the box with little pain. While you
16690 * can override it completely to change the behavior of `$sce`, the common case would
16691 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting
16692 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as
16693 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist
16694 * $sceDelegateProvider.resourceUrlWhitelist} and {@link
16695 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
16700 * @name $sceDelegateProvider
16703 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate
16704 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure
16705 * that the URLs used for sourcing Angular templates are safe. Refer {@link
16706 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and
16707 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
16709 * For the general details about this service in Angular, read the main page for {@link ng.$sce
16710 * Strict Contextual Escaping (SCE)}.
16712 * **Example**: Consider the following case. <a name="example"></a>
16714 * - your app is hosted at url `http://myapp.example.com/`
16715 * - but some of your templates are hosted on other domains you control such as
16716 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc.
16717 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`.
16719 * Here is what a secure configuration for this scenario might look like:
16722 * angular.module('myApp', []).config(function($sceDelegateProvider) {
16723 * $sceDelegateProvider.resourceUrlWhitelist([
16724 * // Allow same origin resource loads.
16726 * // Allow loading from our assets domain. Notice the difference between * and **.
16727 * 'http://srv*.assets.example.com/**'
16730 * // The blacklist overrides the whitelist so the open redirect here is blocked.
16731 * $sceDelegateProvider.resourceUrlBlacklist([
16732 * 'http://myapp.example.com/clickThru**'
16738 function $SceDelegateProvider() {
16739 this.SCE_CONTEXTS = SCE_CONTEXTS;
16741 // Resource URLs can also be trusted by policy.
16742 var resourceUrlWhitelist = ['self'],
16743 resourceUrlBlacklist = [];
16747 * @name $sceDelegateProvider#resourceUrlWhitelist
16750 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value
16751 * provided. This must be an array or null. A snapshot of this array is used so further
16752 * changes to the array are ignored.
16754 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
16755 * allowed in this array.
16757 * Note: **an empty whitelist array will block all URLs**!
16759 * @return {Array} the currently set whitelist array.
16761 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only
16762 * same origin resource requests.
16765 * Sets/Gets the whitelist of trusted resource URLs.
16767 this.resourceUrlWhitelist = function(value) {
16768 if (arguments.length) {
16769 resourceUrlWhitelist = adjustMatchers(value);
16771 return resourceUrlWhitelist;
16776 * @name $sceDelegateProvider#resourceUrlBlacklist
16779 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value
16780 * provided. This must be an array or null. A snapshot of this array is used so further
16781 * changes to the array are ignored.
16783 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
16784 * allowed in this array.
16786 * The typical usage for the blacklist is to **block
16787 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as
16788 * these would otherwise be trusted but actually return content from the redirected domain.
16790 * Finally, **the blacklist overrides the whitelist** and has the final say.
16792 * @return {Array} the currently set blacklist array.
16794 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there
16795 * is no blacklist.)
16798 * Sets/Gets the blacklist of trusted resource URLs.
16801 this.resourceUrlBlacklist = function(value) {
16802 if (arguments.length) {
16803 resourceUrlBlacklist = adjustMatchers(value);
16805 return resourceUrlBlacklist;
16808 this.$get = ['$injector', function($injector) {
16810 var htmlSanitizer = function htmlSanitizer(html) {
16811 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
16814 if ($injector.has('$sanitize')) {
16815 htmlSanitizer = $injector.get('$sanitize');
16819 function matchUrl(matcher, parsedUrl) {
16820 if (matcher === 'self') {
16821 return urlIsSameOrigin(parsedUrl);
16823 // definitely a regex. See adjustMatchers()
16824 return !!matcher.exec(parsedUrl.href);
16828 function isResourceUrlAllowedByPolicy(url) {
16829 var parsedUrl = urlResolve(url.toString());
16830 var i, n, allowed = false;
16831 // Ensure that at least one item from the whitelist allows this url.
16832 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) {
16833 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) {
16839 // Ensure that no item from the blacklist blocked this url.
16840 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) {
16841 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) {
16850 function generateHolderType(Base) {
16851 var holderType = function TrustedValueHolderType(trustedValue) {
16852 this.$$unwrapTrustedValue = function() {
16853 return trustedValue;
16857 holderType.prototype = new Base();
16859 holderType.prototype.valueOf = function sceValueOf() {
16860 return this.$$unwrapTrustedValue();
16862 holderType.prototype.toString = function sceToString() {
16863 return this.$$unwrapTrustedValue().toString();
16868 var trustedValueHolderBase = generateHolderType(),
16871 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase);
16872 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase);
16873 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase);
16874 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase);
16875 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]);
16879 * @name $sceDelegate#trustAs
16882 * Returns an object that is trusted by angular for use in specified strict
16883 * contextual escaping contexts (such as ng-bind-html, ng-include, any src
16884 * attribute interpolation, any dom event binding attribute interpolation
16885 * such as for onclick, etc.) that uses the provided value.
16886 * See {@link ng.$sce $sce} for enabling strict contextual escaping.
16888 * @param {string} type The kind of context in which this value is safe for use. e.g. url,
16889 * resourceUrl, html, js and css.
16890 * @param {*} value The value that that should be considered trusted/safe.
16891 * @returns {*} A value that can be used to stand in for the provided `value` in places
16892 * where Angular expects a $sce.trustAs() return value.
16894 function trustAs(type, trustedValue) {
16895 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
16896 if (!Constructor) {
16897 throw $sceMinErr('icontext',
16898 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}',
16899 type, trustedValue);
16901 if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') {
16902 return trustedValue;
16904 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting
16905 // mutable objects, we ensure here that the value passed in is actually a string.
16906 if (typeof trustedValue !== 'string') {
16907 throw $sceMinErr('itype',
16908 'Attempted to trust a non-string value in a content requiring a string: Context: {0}',
16911 return new Constructor(trustedValue);
16916 * @name $sceDelegate#valueOf
16919 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs
16920 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link
16921 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}.
16923 * If the passed parameter is not a value that had been returned by {@link
16924 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is.
16926 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}
16927 * call or anything else.
16928 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs
16929 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns
16930 * `value` unchanged.
16932 function valueOf(maybeTrusted) {
16933 if (maybeTrusted instanceof trustedValueHolderBase) {
16934 return maybeTrusted.$$unwrapTrustedValue();
16936 return maybeTrusted;
16942 * @name $sceDelegate#getTrusted
16945 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and
16946 * returns the originally supplied value if the queried context type is a supertype of the
16947 * created type. If this condition isn't satisfied, throws an exception.
16949 * @param {string} type The kind of context in which this value is to be used.
16950 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs
16951 * `$sceDelegate.trustAs`} call.
16952 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs
16953 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception.
16955 function getTrusted(type, maybeTrusted) {
16956 if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') {
16957 return maybeTrusted;
16959 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
16960 if (constructor && maybeTrusted instanceof constructor) {
16961 return maybeTrusted.$$unwrapTrustedValue();
16963 // If we get here, then we may only take one of two actions.
16964 // 1. sanitize the value for the requested type, or
16965 // 2. throw an exception.
16966 if (type === SCE_CONTEXTS.RESOURCE_URL) {
16967 if (isResourceUrlAllowedByPolicy(maybeTrusted)) {
16968 return maybeTrusted;
16970 throw $sceMinErr('insecurl',
16971 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}',
16972 maybeTrusted.toString());
16974 } else if (type === SCE_CONTEXTS.HTML) {
16975 return htmlSanitizer(maybeTrusted);
16977 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
16980 return { trustAs: trustAs,
16981 getTrusted: getTrusted,
16982 valueOf: valueOf };
16989 * @name $sceProvider
16992 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service.
16993 * - enable/disable Strict Contextual Escaping (SCE) in a module
16994 * - override the default implementation with a custom delegate
16996 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}.
16999 /* jshint maxlen: false*/
17008 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS.
17010 * # Strict Contextual Escaping
17012 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain
17013 * contexts to result in a value that is marked as safe to use for that context. One example of
17014 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer
17015 * to these contexts as privileged or SCE contexts.
17017 * As of version 1.2, Angular ships with SCE enabled by default.
17019 * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow
17020 * one to execute arbitrary javascript by the use of the expression() syntax. Refer
17021 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
17022 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>`
17023 * to the top of your HTML document.
17025 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for
17026 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier.
17028 * Here's an example of a binding in a privileged context:
17031 * <input ng-model="userHtml" aria-label="User input">
17032 * <div ng-bind-html="userHtml"></div>
17035 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE
17036 * disabled, this application allows the user to render arbitrary HTML into the DIV.
17037 * In a more realistic example, one may be rendering user comments, blog articles, etc. via
17038 * bindings. (HTML is just one example of a context where rendering user controlled input creates
17039 * security vulnerabilities.)
17041 * For the case of HTML, you might use a library, either on the client side, or on the server side,
17042 * to sanitize unsafe HTML before binding to the value and rendering it in the document.
17044 * How would you ensure that every place that used these types of bindings was bound to a value that
17045 * was sanitized by your library (or returned as safe for rendering by your server?) How can you
17046 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
17047 * properties/fields and forgot to update the binding to the sanitized value?
17049 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can
17050 * determine that something explicitly says it's safe to use a value for binding in that
17051 * context. You can then audit your code (a simple grep would do) to ensure that this is only done
17052 * for those values that you can easily tell are safe - because they were received from your server,
17053 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps
17054 * allowing only the files in a specific directory to do this. Ensuring that the internal API
17055 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task.
17057 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs}
17058 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to
17059 * obtain values that will be accepted by SCE / privileged contexts.
17062 * ## How does it work?
17064 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted
17065 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link
17066 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the
17067 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals.
17069 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link
17070 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly
17074 * var ngBindHtmlDirective = ['$sce', function($sce) {
17075 * return function(scope, element, attr) {
17076 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
17077 * element.html(value || '');
17083 * ## Impact on loading templates
17085 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as
17086 * `templateUrl`'s specified by {@link guide/directive directives}.
17088 * By default, Angular only loads templates from the same domain and protocol as the application
17089 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl
17090 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or
17091 * protocols, you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist
17092 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value.
17096 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
17097 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
17098 * policy apply in addition to this and may further restrict whether the template is successfully
17099 * loaded. This means that without the right CORS policy, loading templates from a different domain
17100 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some
17103 * ## This feels like too much overhead
17105 * It's important to remember that SCE only applies to interpolation expressions.
17107 * If your expressions are constant literals, they're automatically trusted and you don't need to
17108 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g.
17109 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works.
17111 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them
17112 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here.
17114 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load
17115 * templates in `ng-include` from your application's domain without having to even know about SCE.
17116 * It blocks loading templates from other domains or loading templates over http from an https
17117 * served document. You can change these by setting your own custom {@link
17118 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link
17119 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs.
17121 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an
17122 * application that's secure and can be audited to verify that with much more ease than bolting
17123 * security onto an application later.
17125 * <a name="contexts"></a>
17126 * ## What trusted context types are supported?
17128 * | Context | Notes |
17129 * |---------------------|----------------|
17130 * | `$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. |
17131 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. |
17132 * | `$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. |
17133 * | `$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. |
17134 * | `$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. |
17136 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a>
17138 * Each element in these arrays must be one of the following:
17141 * - The special **string**, `'self'`, can be used to match against all URLs of the **same
17142 * domain** as the application document using the **same protocol**.
17143 * - **String** (except the special value `'self'`)
17144 * - The string is matched against the full *normalized / absolute URL* of the resource
17145 * being tested (substring matches are not good enough.)
17146 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters
17147 * match themselves.
17148 * - `*`: matches zero or more occurrences of any character other than one of the following 6
17149 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use
17151 * - `**`: matches zero or more occurrences of *any* character. As such, it's not
17152 * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g.
17153 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might
17154 * not have been the intention.) Its usage at the very end of the path is ok. (e.g.
17155 * http://foo.example.com/templates/**).
17156 * - **RegExp** (*see caveat below*)
17157 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax
17158 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to
17159 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should
17160 * have good test coverage). For instance, the use of `.` in the regex is correct only in a
17161 * small number of cases. A `.` character in the regex used when matching the scheme or a
17162 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It
17163 * is highly recommended to use the string patterns and only fall back to regular expressions
17164 * as a last resort.
17165 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is
17166 * matched against the **entire** *normalized / absolute URL* of the resource being tested
17167 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags
17168 * present on the RegExp (such as multiline, global, ignoreCase) are ignored.
17169 * - If you are generating your JavaScript from some other templating engine (not
17170 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)),
17171 * remember to escape your regular expression (and be aware that you might need more than
17172 * one level of escaping depending on your templating engine and the way you interpolated
17173 * the value.) Do make use of your platform's escaping mechanism as it might be good
17174 * enough before coding your own. E.g. Ruby has
17175 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape)
17176 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape).
17177 * Javascript lacks a similar built in function for escaping. Take a look at Google
17178 * Closure library's [goog.string.regExpEscape(s)](
17179 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962).
17181 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example.
17183 * ## Show me an example using SCE.
17185 * <example module="mySceApp" deps="angular-sanitize.js">
17186 * <file name="index.html">
17187 * <div ng-controller="AppController as myCtrl">
17188 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br>
17189 * <b>User comments</b><br>
17190 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when
17191 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an
17193 * <div class="well">
17194 * <div ng-repeat="userComment in myCtrl.userComments">
17195 * <b>{{userComment.name}}</b>:
17196 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span>
17203 * <file name="script.js">
17204 * angular.module('mySceApp', ['ngSanitize'])
17205 * .controller('AppController', ['$http', '$templateCache', '$sce',
17206 * function($http, $templateCache, $sce) {
17208 * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) {
17209 * self.userComments = userComments;
17211 * self.explicitlyTrustedHtml = $sce.trustAsHtml(
17212 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' +
17213 * 'sanitization."">Hover over this text.</span>');
17217 * <file name="test_data.json">
17219 * { "name": "Alice",
17221 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>"
17224 * "htmlComment": "<i>Yes!</i> Am I the only other one?"
17229 * <file name="protractor.js" type="protractor">
17230 * describe('SCE doc demo', function() {
17231 * it('should sanitize untrusted values', function() {
17232 * expect(element.all(by.css('.htmlComment')).first().getInnerHtml())
17233 * .toBe('<span>Is <i>anyone</i> reading this?</span>');
17236 * it('should NOT sanitize explicitly trusted values', function() {
17237 * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe(
17238 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' +
17239 * 'sanitization."">Hover over this text.</span>');
17247 * ## Can I disable SCE completely?
17249 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits
17250 * for little coding overhead. It will be much harder to take an SCE disabled application and
17251 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE
17252 * for cases where you have a lot of existing code that was written before SCE was introduced and
17253 * you're migrating them a module at a time.
17255 * That said, here's how you can completely disable SCE:
17258 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
17259 * // Completely disable SCE. For demonstration purposes only!
17260 * // Do not use in new projects.
17261 * $sceProvider.enabled(false);
17266 /* jshint maxlen: 100 */
17268 function $SceProvider() {
17269 var enabled = true;
17273 * @name $sceProvider#enabled
17276 * @param {boolean=} value If provided, then enables/disables SCE.
17277 * @return {boolean} true if SCE is enabled, false otherwise.
17280 * Enables/disables SCE and returns the current value.
17282 this.enabled = function(value) {
17283 if (arguments.length) {
17290 /* Design notes on the default implementation for SCE.
17292 * The API contract for the SCE delegate
17293 * -------------------------------------
17294 * The SCE delegate object must provide the following 3 methods:
17296 * - trustAs(contextEnum, value)
17297 * This method is used to tell the SCE service that the provided value is OK to use in the
17298 * contexts specified by contextEnum. It must return an object that will be accepted by
17299 * getTrusted() for a compatible contextEnum and return this value.
17302 * For values that were not produced by trustAs(), return them as is. For values that were
17303 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if
17304 * trustAs is wrapping the given values into some type, this operation unwraps it when given
17307 * - getTrusted(contextEnum, value)
17308 * This function should return the a value that is safe to use in the context specified by
17309 * contextEnum or throw and exception otherwise.
17311 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be
17312 * opaque or wrapped in some holder object. That happens to be an implementation detail. For
17313 * instance, an implementation could maintain a registry of all trusted objects by context. In
17314 * such a case, trustAs() would return the same object that was passed in. getTrusted() would
17315 * return the same object passed in if it was found in the registry under a compatible context or
17316 * throw an exception otherwise. An implementation might only wrap values some of the time based
17317 * on some criteria. getTrusted() might return a value and not throw an exception for special
17318 * constants or objects even if not wrapped. All such implementations fulfill this contract.
17321 * A note on the inheritance model for SCE contexts
17322 * ------------------------------------------------
17323 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This
17324 * is purely an implementation details.
17326 * The contract is simply this:
17328 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value)
17329 * will also succeed.
17331 * Inheritance happens to capture this in a natural way. In some future, we
17332 * may not use inheritance anymore. That is OK because no code outside of
17333 * sce.js and sceSpecs.js would need to be aware of this detail.
17336 this.$get = ['$parse', '$sceDelegate', function(
17337 $parse, $sceDelegate) {
17338 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow
17339 // the "expression(javascript expression)" syntax which is insecure.
17340 if (enabled && msie < 8) {
17341 throw $sceMinErr('iequirks',
17342 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' +
17343 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
17344 'document. See http://docs.angularjs.org/api/ng.$sce for more information.');
17347 var sce = shallowCopy(SCE_CONTEXTS);
17351 * @name $sce#isEnabled
17354 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you
17355 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}.
17358 * Returns a boolean indicating if SCE is enabled.
17360 sce.isEnabled = function() {
17363 sce.trustAs = $sceDelegate.trustAs;
17364 sce.getTrusted = $sceDelegate.getTrusted;
17365 sce.valueOf = $sceDelegate.valueOf;
17368 sce.trustAs = sce.getTrusted = function(type, value) { return value; };
17369 sce.valueOf = identity;
17374 * @name $sce#parseAs
17377 * Converts Angular {@link guide/expression expression} into a function. This is like {@link
17378 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it
17379 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*,
17382 * @param {string} type The kind of SCE context in which this result will be used.
17383 * @param {string} expression String expression to compile.
17384 * @returns {function(context, locals)} a function which represents the compiled expression:
17386 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17387 * are evaluated against (typically a scope object).
17388 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17391 sce.parseAs = function sceParseAs(type, expr) {
17392 var parsed = $parse(expr);
17393 if (parsed.literal && parsed.constant) {
17396 return $parse(expr, function(value) {
17397 return sce.getTrusted(type, value);
17404 * @name $sce#trustAs
17407 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such,
17408 * returns an object that is trusted by angular for use in specified strict contextual
17409 * escaping contexts (such as ng-bind-html, ng-include, any src attribute
17410 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.)
17411 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual
17414 * @param {string} type The kind of context in which this value is safe for use. e.g. url,
17415 * resourceUrl, html, js and css.
17416 * @param {*} value The value that that should be considered trusted/safe.
17417 * @returns {*} A value that can be used to stand in for the provided `value` in places
17418 * where Angular expects a $sce.trustAs() return value.
17423 * @name $sce#trustAsHtml
17426 * Shorthand method. `$sce.trustAsHtml(value)` →
17427 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`}
17429 * @param {*} value The value to trustAs.
17430 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml
17431 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives
17432 * only accept expressions that are either literal constants or are the
17433 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17438 * @name $sce#trustAsUrl
17441 * Shorthand method. `$sce.trustAsUrl(value)` →
17442 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`}
17444 * @param {*} value The value to trustAs.
17445 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl
17446 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives
17447 * only accept expressions that are either literal constants or are the
17448 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17453 * @name $sce#trustAsResourceUrl
17456 * Shorthand method. `$sce.trustAsResourceUrl(value)` →
17457 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`}
17459 * @param {*} value The value to trustAs.
17460 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl
17461 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives
17462 * only accept expressions that are either literal constants or are the return
17463 * value of {@link ng.$sce#trustAs $sce.trustAs}.)
17468 * @name $sce#trustAsJs
17471 * Shorthand method. `$sce.trustAsJs(value)` →
17472 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`}
17474 * @param {*} value The value to trustAs.
17475 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs
17476 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives
17477 * only accept expressions that are either literal constants or are the
17478 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17483 * @name $sce#getTrusted
17486 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such,
17487 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the
17488 * originally supplied value if the queried context type is a supertype of the created type.
17489 * If this condition isn't satisfied, throws an exception.
17491 * @param {string} type The kind of context in which this value is to be used.
17492 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`}
17494 * @returns {*} The value the was originally provided to
17495 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context.
17496 * Otherwise, throws an exception.
17501 * @name $sce#getTrustedHtml
17504 * Shorthand method. `$sce.getTrustedHtml(value)` →
17505 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`}
17507 * @param {*} value The value to pass to `$sce.getTrusted`.
17508 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)`
17513 * @name $sce#getTrustedCss
17516 * Shorthand method. `$sce.getTrustedCss(value)` →
17517 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`}
17519 * @param {*} value The value to pass to `$sce.getTrusted`.
17520 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)`
17525 * @name $sce#getTrustedUrl
17528 * Shorthand method. `$sce.getTrustedUrl(value)` →
17529 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`}
17531 * @param {*} value The value to pass to `$sce.getTrusted`.
17532 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)`
17537 * @name $sce#getTrustedResourceUrl
17540 * Shorthand method. `$sce.getTrustedResourceUrl(value)` →
17541 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`}
17543 * @param {*} value The value to pass to `$sceDelegate.getTrusted`.
17544 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)`
17549 * @name $sce#getTrustedJs
17552 * Shorthand method. `$sce.getTrustedJs(value)` →
17553 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`}
17555 * @param {*} value The value to pass to `$sce.getTrusted`.
17556 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)`
17561 * @name $sce#parseAsHtml
17564 * Shorthand method. `$sce.parseAsHtml(expression string)` →
17565 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`}
17567 * @param {string} expression String expression to compile.
17568 * @returns {function(context, locals)} a function which represents the compiled expression:
17570 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17571 * are evaluated against (typically a scope object).
17572 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17578 * @name $sce#parseAsCss
17581 * Shorthand method. `$sce.parseAsCss(value)` →
17582 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`}
17584 * @param {string} expression String expression to compile.
17585 * @returns {function(context, locals)} a function which represents the compiled expression:
17587 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17588 * are evaluated against (typically a scope object).
17589 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17595 * @name $sce#parseAsUrl
17598 * Shorthand method. `$sce.parseAsUrl(value)` →
17599 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`}
17601 * @param {string} expression String expression to compile.
17602 * @returns {function(context, locals)} a function which represents the compiled expression:
17604 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17605 * are evaluated against (typically a scope object).
17606 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17612 * @name $sce#parseAsResourceUrl
17615 * Shorthand method. `$sce.parseAsResourceUrl(value)` →
17616 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`}
17618 * @param {string} expression String expression to compile.
17619 * @returns {function(context, locals)} a function which represents the compiled expression:
17621 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17622 * are evaluated against (typically a scope object).
17623 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17629 * @name $sce#parseAsJs
17632 * Shorthand method. `$sce.parseAsJs(value)` →
17633 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`}
17635 * @param {string} expression String expression to compile.
17636 * @returns {function(context, locals)} a function which represents the compiled expression:
17638 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17639 * are evaluated against (typically a scope object).
17640 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17644 // Shorthand delegations.
17645 var parse = sce.parseAs,
17646 getTrusted = sce.getTrusted,
17647 trustAs = sce.trustAs;
17649 forEach(SCE_CONTEXTS, function(enumValue, name) {
17650 var lName = lowercase(name);
17651 sce[camelCase("parse_as_" + lName)] = function(expr) {
17652 return parse(enumValue, expr);
17654 sce[camelCase("get_trusted_" + lName)] = function(value) {
17655 return getTrusted(enumValue, value);
17657 sce[camelCase("trust_as_" + lName)] = function(value) {
17658 return trustAs(enumValue, value);
17667 * !!! This is an undocumented "private" service !!!
17670 * @requires $window
17671 * @requires $document
17673 * @property {boolean} history Does the browser support html5 history api ?
17674 * @property {boolean} transitions Does the browser support CSS transition events ?
17675 * @property {boolean} animations Does the browser support CSS animation events ?
17678 * This is very simple implementation of testing browser's features.
17680 function $SnifferProvider() {
17681 this.$get = ['$window', '$document', function($window, $document) {
17682 var eventSupport = {},
17684 toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
17685 boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
17686 document = $document[0] || {},
17688 vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/,
17689 bodyStyle = document.body && document.body.style,
17690 transitions = false,
17691 animations = false,
17695 for (var prop in bodyStyle) {
17696 if (match = vendorRegex.exec(prop)) {
17697 vendorPrefix = match[0];
17698 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1);
17703 if (!vendorPrefix) {
17704 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit';
17707 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle));
17708 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle));
17710 if (android && (!transitions || !animations)) {
17711 transitions = isString(bodyStyle.webkitTransition);
17712 animations = isString(bodyStyle.webkitAnimation);
17718 // Android has history.pushState, but it does not update location correctly
17719 // so let's not use the history API at all.
17720 // http://code.google.com/p/android/issues/detail?id=17471
17721 // https://github.com/angular/angular.js/issues/904
17723 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has
17724 // so let's not use the history API also
17725 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined
17727 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee),
17729 hasEvent: function(event) {
17730 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
17731 // it. In particular the event is not fired when backspace or delete key are pressed or
17732 // when cut operation is performed.
17733 // IE10+ implements 'input' event but it erroneously fires under various situations,
17734 // e.g. when placeholder changes, or a form is focused.
17735 if (event === 'input' && msie <= 11) return false;
17737 if (isUndefined(eventSupport[event])) {
17738 var divElm = document.createElement('div');
17739 eventSupport[event] = 'on' + event in divElm;
17742 return eventSupport[event];
17745 vendorPrefix: vendorPrefix,
17746 transitions: transitions,
17747 animations: animations,
17753 var $compileMinErr = minErr('$compile');
17757 * @name $templateRequest
17760 * The `$templateRequest` service runs security checks then downloads the provided template using
17761 * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request
17762 * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the
17763 * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the
17764 * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted
17765 * when `tpl` is of type string and `$templateCache` has the matching entry.
17767 * @param {string|TrustedResourceUrl} tpl The HTTP request template URL
17768 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty
17770 * @return {Promise} a promise for the HTTP response data of the given URL.
17772 * @property {number} totalPendingRequests total amount of pending template requests being downloaded.
17774 function $TemplateRequestProvider() {
17775 this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) {
17776 function handleRequestFn(tpl, ignoreRequestError) {
17777 handleRequestFn.totalPendingRequests++;
17779 // We consider the template cache holds only trusted templates, so
17780 // there's no need to go through whitelisting again for keys that already
17781 // are included in there. This also makes Angular accept any script
17782 // directive, no matter its name. However, we still need to unwrap trusted
17784 if (!isString(tpl) || !$templateCache.get(tpl)) {
17785 tpl = $sce.getTrustedResourceUrl(tpl);
17788 var transformResponse = $http.defaults && $http.defaults.transformResponse;
17790 if (isArray(transformResponse)) {
17791 transformResponse = transformResponse.filter(function(transformer) {
17792 return transformer !== defaultHttpResponseTransform;
17794 } else if (transformResponse === defaultHttpResponseTransform) {
17795 transformResponse = null;
17798 var httpOptions = {
17799 cache: $templateCache,
17800 transformResponse: transformResponse
17803 return $http.get(tpl, httpOptions)
17804 ['finally'](function() {
17805 handleRequestFn.totalPendingRequests--;
17807 .then(function(response) {
17808 $templateCache.put(tpl, response.data);
17809 return response.data;
17812 function handleError(resp) {
17813 if (!ignoreRequestError) {
17814 throw $compileMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})',
17815 tpl, resp.status, resp.statusText);
17817 return $q.reject(resp);
17821 handleRequestFn.totalPendingRequests = 0;
17823 return handleRequestFn;
17827 function $$TestabilityProvider() {
17828 this.$get = ['$rootScope', '$browser', '$location',
17829 function($rootScope, $browser, $location) {
17832 * @name $testability
17835 * The private $$testability service provides a collection of methods for use when debugging
17836 * or by automated test and debugging tools.
17838 var testability = {};
17841 * @name $$testability#findBindings
17844 * Returns an array of elements that are bound (via ng-bind or {{}})
17845 * to expressions matching the input.
17847 * @param {Element} element The element root to search from.
17848 * @param {string} expression The binding expression to match.
17849 * @param {boolean} opt_exactMatch If true, only returns exact matches
17850 * for the expression. Filters and whitespace are ignored.
17852 testability.findBindings = function(element, expression, opt_exactMatch) {
17853 var bindings = element.getElementsByClassName('ng-binding');
17855 forEach(bindings, function(binding) {
17856 var dataBinding = angular.element(binding).data('$binding');
17858 forEach(dataBinding, function(bindingName) {
17859 if (opt_exactMatch) {
17860 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)');
17861 if (matcher.test(bindingName)) {
17862 matches.push(binding);
17865 if (bindingName.indexOf(expression) != -1) {
17866 matches.push(binding);
17876 * @name $$testability#findModels
17879 * Returns an array of elements that are two-way found via ng-model to
17880 * expressions matching the input.
17882 * @param {Element} element The element root to search from.
17883 * @param {string} expression The model expression to match.
17884 * @param {boolean} opt_exactMatch If true, only returns exact matches
17885 * for the expression.
17887 testability.findModels = function(element, expression, opt_exactMatch) {
17888 var prefixes = ['ng-', 'data-ng-', 'ng\\:'];
17889 for (var p = 0; p < prefixes.length; ++p) {
17890 var attributeEquals = opt_exactMatch ? '=' : '*=';
17891 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]';
17892 var elements = element.querySelectorAll(selector);
17893 if (elements.length) {
17900 * @name $$testability#getLocation
17903 * Shortcut for getting the location in a browser agnostic way. Returns
17904 * the path, search, and hash. (e.g. /path?a=b#hash)
17906 testability.getLocation = function() {
17907 return $location.url();
17911 * @name $$testability#setLocation
17914 * Shortcut for navigating to a location without doing a full page reload.
17916 * @param {string} url The location url (path, search and hash,
17917 * e.g. /path?a=b#hash) to go to.
17919 testability.setLocation = function(url) {
17920 if (url !== $location.url()) {
17921 $location.url(url);
17922 $rootScope.$digest();
17927 * @name $$testability#whenStable
17930 * Calls the callback when $timeout and $http requests are completed.
17932 * @param {function} callback
17934 testability.whenStable = function(callback) {
17935 $browser.notifyWhenNoOutstandingRequests(callback);
17938 return testability;
17942 function $TimeoutProvider() {
17943 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler',
17944 function($rootScope, $browser, $q, $$q, $exceptionHandler) {
17946 var deferreds = {};
17954 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch
17955 * block and delegates any exceptions to
17956 * {@link ng.$exceptionHandler $exceptionHandler} service.
17958 * The return value of calling `$timeout` is a promise, which will be resolved when
17959 * the delay has passed and the timeout function, if provided, is executed.
17961 * To cancel a timeout request, call `$timeout.cancel(promise)`.
17963 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
17964 * synchronously flush the queue of deferred functions.
17966 * If you only want a promise that will be resolved after some specified delay
17967 * then you can call `$timeout` without the `fn` function.
17969 * @param {function()=} fn A function, whose execution should be delayed.
17970 * @param {number=} [delay=0] Delay in milliseconds.
17971 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
17972 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
17973 * @param {...*=} Pass additional parameters to the executed function.
17974 * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this
17975 * promise will be resolved with is the return value of the `fn` function.
17978 function timeout(fn, delay, invokeApply) {
17979 if (!isFunction(fn)) {
17980 invokeApply = delay;
17985 var args = sliceArgs(arguments, 3),
17986 skipApply = (isDefined(invokeApply) && !invokeApply),
17987 deferred = (skipApply ? $$q : $q).defer(),
17988 promise = deferred.promise,
17991 timeoutId = $browser.defer(function() {
17993 deferred.resolve(fn.apply(null, args));
17995 deferred.reject(e);
17996 $exceptionHandler(e);
17999 delete deferreds[promise.$$timeoutId];
18002 if (!skipApply) $rootScope.$apply();
18005 promise.$$timeoutId = timeoutId;
18006 deferreds[timeoutId] = deferred;
18014 * @name $timeout#cancel
18017 * Cancels a task associated with the `promise`. As a result of this, the promise will be
18018 * resolved with a rejection.
18020 * @param {Promise=} promise Promise returned by the `$timeout` function.
18021 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
18024 timeout.cancel = function(promise) {
18025 if (promise && promise.$$timeoutId in deferreds) {
18026 deferreds[promise.$$timeoutId].reject('canceled');
18027 delete deferreds[promise.$$timeoutId];
18028 return $browser.defer.cancel(promise.$$timeoutId);
18037 // NOTE: The usage of window and document instead of $window and $document here is
18038 // deliberate. This service depends on the specific behavior of anchor nodes created by the
18039 // browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and
18040 // cause us to break tests. In addition, when the browser resolves a URL for XHR, it
18041 // doesn't know about mocked locations and resolves URLs to the real document - which is
18042 // exactly the behavior needed here. There is little value is mocking these out for this
18044 var urlParsingNode = document.createElement("a");
18045 var originUrl = urlResolve(window.location.href);
18050 * Implementation Notes for non-IE browsers
18051 * ----------------------------------------
18052 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM,
18053 * results both in the normalizing and parsing of the URL. Normalizing means that a relative
18054 * URL will be resolved into an absolute URL in the context of the application document.
18055 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related
18056 * properties are all populated to reflect the normalized URL. This approach has wide
18057 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See
18058 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
18060 * Implementation Notes for IE
18061 * ---------------------------
18062 * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other
18063 * browsers. However, the parsed components will not be set if the URL assigned did not specify
18064 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We
18065 * work around that by performing the parsing in a 2nd step by taking a previously normalized
18066 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the
18067 * properties such as protocol, hostname, port, etc.
18070 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement
18071 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
18072 * http://url.spec.whatwg.org/#urlutils
18073 * https://github.com/angular/angular.js/pull/2902
18074 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/
18077 * @param {string} url The URL to be parsed.
18078 * @description Normalizes and parses a URL.
18079 * @returns {object} Returns the normalized URL as a dictionary.
18081 * | member name | Description |
18082 * |---------------|----------------|
18083 * | href | A normalized version of the provided URL if it was not an absolute URL |
18084 * | protocol | The protocol including the trailing colon |
18085 * | host | The host and port (if the port is non-default) of the normalizedUrl |
18086 * | search | The search params, minus the question mark |
18087 * | hash | The hash string, minus the hash symbol
18088 * | hostname | The hostname
18089 * | port | The port, without ":"
18090 * | pathname | The pathname, beginning with "/"
18093 function urlResolve(url) {
18097 // Normalize before parse. Refer Implementation Notes on why this is
18098 // done in two steps on IE.
18099 urlParsingNode.setAttribute("href", href);
18100 href = urlParsingNode.href;
18103 urlParsingNode.setAttribute('href', href);
18105 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
18107 href: urlParsingNode.href,
18108 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
18109 host: urlParsingNode.host,
18110 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '',
18111 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
18112 hostname: urlParsingNode.hostname,
18113 port: urlParsingNode.port,
18114 pathname: (urlParsingNode.pathname.charAt(0) === '/')
18115 ? urlParsingNode.pathname
18116 : '/' + urlParsingNode.pathname
18121 * Parse a request URL and determine whether this is a same-origin request as the application document.
18123 * @param {string|object} requestUrl The url of the request as a string that will be resolved
18124 * or a parsed URL object.
18125 * @returns {boolean} Whether the request is for the same origin as the application document.
18127 function urlIsSameOrigin(requestUrl) {
18128 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl;
18129 return (parsed.protocol === originUrl.protocol &&
18130 parsed.host === originUrl.host);
18138 * A reference to the browser's `window` object. While `window`
18139 * is globally available in JavaScript, it causes testability problems, because
18140 * it is a global variable. In angular we always refer to it through the
18141 * `$window` service, so it may be overridden, removed or mocked for testing.
18143 * Expressions, like the one defined for the `ngClick` directive in the example
18144 * below, are evaluated with respect to the current scope. Therefore, there is
18145 * no risk of inadvertently coding in a dependency on a global value in such an
18149 <example module="windowExample">
18150 <file name="index.html">
18152 angular.module('windowExample', [])
18153 .controller('ExampleController', ['$scope', '$window', function($scope, $window) {
18154 $scope.greeting = 'Hello, World!';
18155 $scope.doGreeting = function(greeting) {
18156 $window.alert(greeting);
18160 <div ng-controller="ExampleController">
18161 <input type="text" ng-model="greeting" aria-label="greeting" />
18162 <button ng-click="doGreeting(greeting)">ALERT</button>
18165 <file name="protractor.js" type="protractor">
18166 it('should display the greeting in the input box', function() {
18167 element(by.model('greeting')).sendKeys('Hello, E2E Tests');
18168 // If we click the button it will block the test runner
18169 // element(':button').click();
18174 function $WindowProvider() {
18175 this.$get = valueFn(window);
18179 * @name $$cookieReader
18180 * @requires $document
18183 * This is a private service for reading cookies used by $http and ngCookies
18185 * @return {Object} a key/value map of the current cookies
18187 function $$CookieReader($document) {
18188 var rawDocument = $document[0] || {};
18189 var lastCookies = {};
18190 var lastCookieString = '';
18192 function safeDecodeURIComponent(str) {
18194 return decodeURIComponent(str);
18200 return function() {
18201 var cookieArray, cookie, i, index, name;
18202 var currentCookieString = rawDocument.cookie || '';
18204 if (currentCookieString !== lastCookieString) {
18205 lastCookieString = currentCookieString;
18206 cookieArray = lastCookieString.split('; ');
18209 for (i = 0; i < cookieArray.length; i++) {
18210 cookie = cookieArray[i];
18211 index = cookie.indexOf('=');
18212 if (index > 0) { //ignore nameless cookies
18213 name = safeDecodeURIComponent(cookie.substring(0, index));
18214 // the first value that is seen for a cookie is the most
18215 // specific one. values for the same cookie name that
18216 // follow are for less specific paths.
18217 if (isUndefined(lastCookies[name])) {
18218 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
18223 return lastCookies;
18227 $$CookieReader.$inject = ['$document'];
18229 function $$CookieReaderProvider() {
18230 this.$get = $$CookieReader;
18233 /* global currencyFilter: true,
18235 filterFilter: true,
18237 limitToFilter: true,
18238 lowercaseFilter: true,
18239 numberFilter: true,
18240 orderByFilter: true,
18241 uppercaseFilter: true,
18246 * @name $filterProvider
18249 * Filters are just functions which transform input to an output. However filters need to be
18250 * Dependency Injected. To achieve this a filter definition consists of a factory function which is
18251 * annotated with dependencies and is responsible for creating a filter function.
18253 * <div class="alert alert-warning">
18254 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
18255 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
18256 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
18257 * (`myapp_subsection_filterx`).
18261 * // Filter registration
18262 * function MyModule($provide, $filterProvider) {
18263 * // create a service to demonstrate injection (not always needed)
18264 * $provide.value('greet', function(name){
18265 * return 'Hello ' + name + '!';
18268 * // register a filter factory which uses the
18269 * // greet service to demonstrate DI.
18270 * $filterProvider.register('greet', function(greet){
18271 * // return the filter function which uses the greet service
18272 * // to generate salutation
18273 * return function(text) {
18274 * // filters need to be forgiving so check input validity
18275 * return text && greet(text) || text;
18281 * The filter function is registered with the `$injector` under the filter name suffix with
18285 * it('should be the same instance', inject(
18286 * function($filterProvider) {
18287 * $filterProvider.register('reverse', function(){
18291 * function($filter, reverseFilter) {
18292 * expect($filter('reverse')).toBe(reverseFilter);
18297 * For more information about how angular filters work, and how to create your own filters, see
18298 * {@link guide/filter Filters} in the Angular Developer Guide.
18306 * Filters are used for formatting data displayed to the user.
18308 * The general syntax in templates is as follows:
18310 * {{ expression [| filter_name[:parameter_value] ... ] }}
18312 * @param {String} name Name of the filter function to retrieve
18313 * @return {Function} the filter function
18315 <example name="$filter" module="filterExample">
18316 <file name="index.html">
18317 <div ng-controller="MainCtrl">
18318 <h3>{{ originalText }}</h3>
18319 <h3>{{ filteredText }}</h3>
18323 <file name="script.js">
18324 angular.module('filterExample', [])
18325 .controller('MainCtrl', function($scope, $filter) {
18326 $scope.originalText = 'hello';
18327 $scope.filteredText = $filter('uppercase')($scope.originalText);
18332 $FilterProvider.$inject = ['$provide'];
18333 function $FilterProvider($provide) {
18334 var suffix = 'Filter';
18338 * @name $filterProvider#register
18339 * @param {string|Object} name Name of the filter function, or an object map of filters where
18340 * the keys are the filter names and the values are the filter factories.
18342 * <div class="alert alert-warning">
18343 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
18344 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
18345 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
18346 * (`myapp_subsection_filterx`).
18348 * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered.
18349 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map
18350 * of the registered filter instances.
18352 function register(name, factory) {
18353 if (isObject(name)) {
18355 forEach(name, function(filter, key) {
18356 filters[key] = register(key, filter);
18360 return $provide.factory(name + suffix, factory);
18363 this.register = register;
18365 this.$get = ['$injector', function($injector) {
18366 return function(name) {
18367 return $injector.get(name + suffix);
18371 ////////////////////////////////////////
18374 currencyFilter: false,
18376 filterFilter: false,
18378 limitToFilter: false,
18379 lowercaseFilter: false,
18380 numberFilter: false,
18381 orderByFilter: false,
18382 uppercaseFilter: false,
18385 register('currency', currencyFilter);
18386 register('date', dateFilter);
18387 register('filter', filterFilter);
18388 register('json', jsonFilter);
18389 register('limitTo', limitToFilter);
18390 register('lowercase', lowercaseFilter);
18391 register('number', numberFilter);
18392 register('orderBy', orderByFilter);
18393 register('uppercase', uppercaseFilter);
18402 * Selects a subset of items from `array` and returns it as a new array.
18404 * @param {Array} array The source array.
18405 * @param {string|Object|function()} expression The predicate to be used for selecting items from
18410 * - `string`: The string is used for matching against the contents of the `array`. All strings or
18411 * objects with string properties in `array` that match this string will be returned. This also
18412 * applies to nested object properties.
18413 * The predicate can be negated by prefixing the string with `!`.
18415 * - `Object`: A pattern object can be used to filter specific properties on objects contained
18416 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
18417 * which have property `name` containing "M" and property `phone` containing "1". A special
18418 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any
18419 * property of the object or its nested object properties. That's equivalent to the simple
18420 * substring match with a `string` as described above. The predicate can be negated by prefixing
18421 * the string with `!`.
18422 * For example `{name: "!M"}` predicate will return an array of items which have property `name`
18423 * not containing "M".
18425 * Note that a named property will match properties on the same level only, while the special
18426 * `$` property will match properties on the same level or deeper. E.g. an array item like
18427 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but
18428 * **will** be matched by `{$: 'John'}`.
18430 * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters.
18431 * The function is called for each element of the array, with the element, its index, and
18432 * the entire array itself as arguments.
18434 * The final result is an array of those elements that the predicate returned true for.
18436 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in
18437 * determining if the expected value (from the filter expression) and actual value (from
18438 * the object in the array) should be considered a match.
18442 * - `function(actual, expected)`:
18443 * The function will be given the object value and the predicate value to compare and
18444 * should return true if both values should be considered equal.
18446 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`.
18447 * This is essentially strict comparison of expected and actual.
18449 * - `false|undefined`: A short hand for a function which will look for a substring match in case
18452 * Primitive values are converted to strings. Objects are not compared against primitives,
18453 * unless they have a custom `toString` method (e.g. `Date` objects).
18457 <file name="index.html">
18458 <div ng-init="friends = [{name:'John', phone:'555-1276'},
18459 {name:'Mary', phone:'800-BIG-MARY'},
18460 {name:'Mike', phone:'555-4321'},
18461 {name:'Adam', phone:'555-5678'},
18462 {name:'Julie', phone:'555-8765'},
18463 {name:'Juliette', phone:'555-5678'}]"></div>
18465 <label>Search: <input ng-model="searchText"></label>
18466 <table id="searchTextResults">
18467 <tr><th>Name</th><th>Phone</th></tr>
18468 <tr ng-repeat="friend in friends | filter:searchText">
18469 <td>{{friend.name}}</td>
18470 <td>{{friend.phone}}</td>
18474 <label>Any: <input ng-model="search.$"></label> <br>
18475 <label>Name only <input ng-model="search.name"></label><br>
18476 <label>Phone only <input ng-model="search.phone"></label><br>
18477 <label>Equality <input type="checkbox" ng-model="strict"></label><br>
18478 <table id="searchObjResults">
18479 <tr><th>Name</th><th>Phone</th></tr>
18480 <tr ng-repeat="friendObj in friends | filter:search:strict">
18481 <td>{{friendObj.name}}</td>
18482 <td>{{friendObj.phone}}</td>
18486 <file name="protractor.js" type="protractor">
18487 var expectFriendNames = function(expectedNames, key) {
18488 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) {
18489 arr.forEach(function(wd, i) {
18490 expect(wd.getText()).toMatch(expectedNames[i]);
18495 it('should search across all fields when filtering with a string', function() {
18496 var searchText = element(by.model('searchText'));
18497 searchText.clear();
18498 searchText.sendKeys('m');
18499 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend');
18501 searchText.clear();
18502 searchText.sendKeys('76');
18503 expectFriendNames(['John', 'Julie'], 'friend');
18506 it('should search in specific fields when filtering with a predicate object', function() {
18507 var searchAny = element(by.model('search.$'));
18509 searchAny.sendKeys('i');
18510 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj');
18512 it('should use a equal comparison when comparator is true', function() {
18513 var searchName = element(by.model('search.name'));
18514 var strict = element(by.model('strict'));
18515 searchName.clear();
18516 searchName.sendKeys('Julie');
18518 expectFriendNames(['Julie'], 'friendObj');
18523 function filterFilter() {
18524 return function(array, expression, comparator) {
18525 if (!isArrayLike(array)) {
18526 if (array == null) {
18529 throw minErr('filter')('notarray', 'Expected array but received: {0}', array);
18533 var expressionType = getTypeForFilter(expression);
18535 var matchAgainstAnyProp;
18537 switch (expressionType) {
18539 predicateFn = expression;
18545 matchAgainstAnyProp = true;
18549 predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp);
18555 return Array.prototype.filter.call(array, predicateFn);
18559 // Helper functions for `filterFilter`
18560 function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
18561 var shouldMatchPrimitives = isObject(expression) && ('$' in expression);
18564 if (comparator === true) {
18565 comparator = equals;
18566 } else if (!isFunction(comparator)) {
18567 comparator = function(actual, expected) {
18568 if (isUndefined(actual)) {
18569 // No substring matching against `undefined`
18572 if ((actual === null) || (expected === null)) {
18573 // No substring matching against `null`; only match against `null`
18574 return actual === expected;
18576 if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) {
18577 // Should not compare primitives against objects, unless they have custom `toString` method
18581 actual = lowercase('' + actual);
18582 expected = lowercase('' + expected);
18583 return actual.indexOf(expected) !== -1;
18587 predicateFn = function(item) {
18588 if (shouldMatchPrimitives && !isObject(item)) {
18589 return deepCompare(item, expression.$, comparator, false);
18591 return deepCompare(item, expression, comparator, matchAgainstAnyProp);
18594 return predicateFn;
18597 function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) {
18598 var actualType = getTypeForFilter(actual);
18599 var expectedType = getTypeForFilter(expected);
18601 if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
18602 return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
18603 } else if (isArray(actual)) {
18604 // In case `actual` is an array, consider it a match
18605 // if ANY of it's items matches `expected`
18606 return actual.some(function(item) {
18607 return deepCompare(item, expected, comparator, matchAgainstAnyProp);
18611 switch (actualType) {
18614 if (matchAgainstAnyProp) {
18615 for (key in actual) {
18616 if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) {
18620 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false);
18621 } else if (expectedType === 'object') {
18622 for (key in expected) {
18623 var expectedVal = expected[key];
18624 if (isFunction(expectedVal) || isUndefined(expectedVal)) {
18628 var matchAnyProperty = key === '$';
18629 var actualVal = matchAnyProperty ? actual : actual[key];
18630 if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) {
18636 return comparator(actual, expected);
18642 return comparator(actual, expected);
18646 // Used for easily differentiating between `null` and actual `object`
18647 function getTypeForFilter(val) {
18648 return (val === null) ? 'null' : typeof val;
18657 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
18658 * symbol for current locale is used.
18660 * @param {number} amount Input to filter.
18661 * @param {string=} symbol Currency symbol or identifier to be displayed.
18662 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale
18663 * @returns {string} Formatted number.
18667 <example module="currencyExample">
18668 <file name="index.html">
18670 angular.module('currencyExample', [])
18671 .controller('ExampleController', ['$scope', function($scope) {
18672 $scope.amount = 1234.56;
18675 <div ng-controller="ExampleController">
18676 <input type="number" ng-model="amount" aria-label="amount"> <br>
18677 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br>
18678 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span>
18679 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span>
18682 <file name="protractor.js" type="protractor">
18683 it('should init with 1234.56', function() {
18684 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56');
18685 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56');
18686 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235');
18688 it('should update', function() {
18689 if (browser.params.browser == 'safari') {
18690 // Safari does not understand the minus key. See
18691 // https://github.com/angular/protractor/issues/481
18694 element(by.model('amount')).clear();
18695 element(by.model('amount')).sendKeys('-1234');
18696 expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00');
18697 expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00');
18698 expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234');
18703 currencyFilter.$inject = ['$locale'];
18704 function currencyFilter($locale) {
18705 var formats = $locale.NUMBER_FORMATS;
18706 return function(amount, currencySymbol, fractionSize) {
18707 if (isUndefined(currencySymbol)) {
18708 currencySymbol = formats.CURRENCY_SYM;
18711 if (isUndefined(fractionSize)) {
18712 fractionSize = formats.PATTERNS[1].maxFrac;
18715 // if null or undefined pass it through
18716 return (amount == null)
18718 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize).
18719 replace(/\u00A4/g, currencySymbol);
18729 * Formats a number as text.
18731 * If the input is null or undefined, it will just be returned.
18732 * If the input is infinite (Infinity/-Infinity) the Infinity symbol '∞' is returned.
18733 * If the input is not a number an empty string is returned.
18736 * @param {number|string} number Number to format.
18737 * @param {(number|string)=} fractionSize Number of decimal places to round the number to.
18738 * If this is not provided then the fraction size is computed from the current locale's number
18739 * formatting pattern. In the case of the default locale, it will be 3.
18740 * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
18743 <example module="numberFilterExample">
18744 <file name="index.html">
18746 angular.module('numberFilterExample', [])
18747 .controller('ExampleController', ['$scope', function($scope) {
18748 $scope.val = 1234.56789;
18751 <div ng-controller="ExampleController">
18752 <label>Enter number: <input ng-model='val'></label><br>
18753 Default formatting: <span id='number-default'>{{val | number}}</span><br>
18754 No fractions: <span>{{val | number:0}}</span><br>
18755 Negative number: <span>{{-val | number:4}}</span>
18758 <file name="protractor.js" type="protractor">
18759 it('should format numbers', function() {
18760 expect(element(by.id('number-default')).getText()).toBe('1,234.568');
18761 expect(element(by.binding('val | number:0')).getText()).toBe('1,235');
18762 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679');
18765 it('should update', function() {
18766 element(by.model('val')).clear();
18767 element(by.model('val')).sendKeys('3374.333');
18768 expect(element(by.id('number-default')).getText()).toBe('3,374.333');
18769 expect(element(by.binding('val | number:0')).getText()).toBe('3,374');
18770 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330');
18777 numberFilter.$inject = ['$locale'];
18778 function numberFilter($locale) {
18779 var formats = $locale.NUMBER_FORMATS;
18780 return function(number, fractionSize) {
18782 // if null or undefined pass it through
18783 return (number == null)
18785 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP,
18790 var DECIMAL_SEP = '.';
18791 function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
18792 if (isObject(number)) return '';
18794 var isNegative = number < 0;
18795 number = Math.abs(number);
18797 var isInfinity = number === Infinity;
18798 if (!isInfinity && !isFinite(number)) return '';
18800 var numStr = number + '',
18802 hasExponent = false,
18805 if (isInfinity) formatedText = '\u221e';
18807 if (!isInfinity && numStr.indexOf('e') !== -1) {
18808 var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
18809 if (match && match[2] == '-' && match[3] > fractionSize + 1) {
18812 formatedText = numStr;
18813 hasExponent = true;
18817 if (!isInfinity && !hasExponent) {
18818 var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
18820 // determine fractionSize if it is not specified
18821 if (isUndefined(fractionSize)) {
18822 fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac);
18825 // safely round numbers in JS without hitting imprecisions of floating-point arithmetics
18827 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
18828 number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
18830 var fraction = ('' + number).split(DECIMAL_SEP);
18831 var whole = fraction[0];
18832 fraction = fraction[1] || '';
18835 lgroup = pattern.lgSize,
18836 group = pattern.gSize;
18838 if (whole.length >= (lgroup + group)) {
18839 pos = whole.length - lgroup;
18840 for (i = 0; i < pos; i++) {
18841 if ((pos - i) % group === 0 && i !== 0) {
18842 formatedText += groupSep;
18844 formatedText += whole.charAt(i);
18848 for (i = pos; i < whole.length; i++) {
18849 if ((whole.length - i) % lgroup === 0 && i !== 0) {
18850 formatedText += groupSep;
18852 formatedText += whole.charAt(i);
18855 // format fraction part.
18856 while (fraction.length < fractionSize) {
18860 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize);
18862 if (fractionSize > 0 && number < 1) {
18863 formatedText = number.toFixed(fractionSize);
18864 number = parseFloat(formatedText);
18865 formatedText = formatedText.replace(DECIMAL_SEP, decimalSep);
18869 if (number === 0) {
18870 isNegative = false;
18873 parts.push(isNegative ? pattern.negPre : pattern.posPre,
18875 isNegative ? pattern.negSuf : pattern.posSuf);
18876 return parts.join('');
18879 function padNumber(num, digits, trim) {
18886 while (num.length < digits) num = '0' + num;
18888 num = num.substr(num.length - digits);
18894 function dateGetter(name, size, offset, trim) {
18895 offset = offset || 0;
18896 return function(date) {
18897 var value = date['get' + name]();
18898 if (offset > 0 || value > -offset) {
18901 if (value === 0 && offset == -12) value = 12;
18902 return padNumber(value, size, trim);
18906 function dateStrGetter(name, shortForm) {
18907 return function(date, formats) {
18908 var value = date['get' + name]();
18909 var get = uppercase(shortForm ? ('SHORT' + name) : name);
18911 return formats[get][value];
18915 function timeZoneGetter(date, formats, offset) {
18916 var zone = -1 * offset;
18917 var paddedZone = (zone >= 0) ? "+" : "";
18919 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) +
18920 padNumber(Math.abs(zone % 60), 2);
18925 function getFirstThursdayOfYear(year) {
18926 // 0 = index of January
18927 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay();
18928 // 4 = index of Thursday (+1 to account for 1st = 5)
18929 // 11 = index of *next* Thursday (+1 account for 1st = 12)
18930 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst);
18933 function getThursdayThisWeek(datetime) {
18934 return new Date(datetime.getFullYear(), datetime.getMonth(),
18935 // 4 = index of Thursday
18936 datetime.getDate() + (4 - datetime.getDay()));
18939 function weekGetter(size) {
18940 return function(date) {
18941 var firstThurs = getFirstThursdayOfYear(date.getFullYear()),
18942 thisThurs = getThursdayThisWeek(date);
18944 var diff = +thisThurs - +firstThurs,
18945 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week
18947 return padNumber(result, size);
18951 function ampmGetter(date, formats) {
18952 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
18955 function eraGetter(date, formats) {
18956 return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1];
18959 function longEraGetter(date, formats) {
18960 return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1];
18963 var DATE_FORMATS = {
18964 yyyy: dateGetter('FullYear', 4),
18965 yy: dateGetter('FullYear', 2, 0, true),
18966 y: dateGetter('FullYear', 1),
18967 MMMM: dateStrGetter('Month'),
18968 MMM: dateStrGetter('Month', true),
18969 MM: dateGetter('Month', 2, 1),
18970 M: dateGetter('Month', 1, 1),
18971 dd: dateGetter('Date', 2),
18972 d: dateGetter('Date', 1),
18973 HH: dateGetter('Hours', 2),
18974 H: dateGetter('Hours', 1),
18975 hh: dateGetter('Hours', 2, -12),
18976 h: dateGetter('Hours', 1, -12),
18977 mm: dateGetter('Minutes', 2),
18978 m: dateGetter('Minutes', 1),
18979 ss: dateGetter('Seconds', 2),
18980 s: dateGetter('Seconds', 1),
18981 // while ISO 8601 requires fractions to be prefixed with `.` or `,`
18982 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions
18983 sss: dateGetter('Milliseconds', 3),
18984 EEEE: dateStrGetter('Day'),
18985 EEE: dateStrGetter('Day', true),
18993 GGGG: longEraGetter
18996 var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,
18997 NUMBER_STRING = /^\-?\d+$/;
19005 * Formats `date` to a string based on the requested `format`.
19007 * `format` string can be composed of the following elements:
19009 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
19010 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
19011 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
19012 * * `'MMMM'`: Month in year (January-December)
19013 * * `'MMM'`: Month in year (Jan-Dec)
19014 * * `'MM'`: Month in year, padded (01-12)
19015 * * `'M'`: Month in year (1-12)
19016 * * `'dd'`: Day in month, padded (01-31)
19017 * * `'d'`: Day in month (1-31)
19018 * * `'EEEE'`: Day in Week,(Sunday-Saturday)
19019 * * `'EEE'`: Day in Week, (Sun-Sat)
19020 * * `'HH'`: Hour in day, padded (00-23)
19021 * * `'H'`: Hour in day (0-23)
19022 * * `'hh'`: Hour in AM/PM, padded (01-12)
19023 * * `'h'`: Hour in AM/PM, (1-12)
19024 * * `'mm'`: Minute in hour, padded (00-59)
19025 * * `'m'`: Minute in hour (0-59)
19026 * * `'ss'`: Second in minute, padded (00-59)
19027 * * `'s'`: Second in minute (0-59)
19028 * * `'sss'`: Millisecond in second, padded (000-999)
19029 * * `'a'`: AM/PM marker
19030 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
19031 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year
19032 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year
19033 * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD')
19034 * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini')
19036 * `format` string can also be one of the following predefined
19037 * {@link guide/i18n localizable formats}:
19039 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
19040 * (e.g. Sep 3, 2010 12:05:08 PM)
19041 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM)
19042 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale
19043 * (e.g. Friday, September 3, 2010)
19044 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010)
19045 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010)
19046 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
19047 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM)
19048 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM)
19050 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g.
19051 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence
19052 * (e.g. `"h 'o''clock'"`).
19054 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
19055 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its
19056 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is
19057 * specified in the string input, the time is considered to be in the local timezone.
19058 * @param {string=} format Formatting rules (see Description). If not specified,
19059 * `mediumDate` is used.
19060 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the
19061 * continental US time zone abbreviations, but for general use, use a time zone offset, for
19062 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
19063 * If not specified, the timezone of the browser will be used.
19064 * @returns {string} Formatted string or the input if input is not recognized as date/millis.
19068 <file name="index.html">
19069 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>:
19070 <span>{{1288323623006 | date:'medium'}}</span><br>
19071 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
19072 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br>
19073 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
19074 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br>
19075 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>:
19076 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br>
19078 <file name="protractor.js" type="protractor">
19079 it('should format date', function() {
19080 expect(element(by.binding("1288323623006 | date:'medium'")).getText()).
19081 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
19082 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()).
19083 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/);
19084 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()).
19085 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
19086 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()).
19087 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/);
19092 dateFilter.$inject = ['$locale'];
19093 function dateFilter($locale) {
19096 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
19097 // 1 2 3 4 5 6 7 8 9 10 11
19098 function jsonStringToDate(string) {
19100 if (match = string.match(R_ISO8601_STR)) {
19101 var date = new Date(0),
19104 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear,
19105 timeSetter = match[8] ? date.setUTCHours : date.setHours;
19108 tzHour = toInt(match[9] + match[10]);
19109 tzMin = toInt(match[9] + match[11]);
19111 dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
19112 var h = toInt(match[4] || 0) - tzHour;
19113 var m = toInt(match[5] || 0) - tzMin;
19114 var s = toInt(match[6] || 0);
19115 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
19116 timeSetter.call(date, h, m, s, ms);
19123 return function(date, format, timezone) {
19128 format = format || 'mediumDate';
19129 format = $locale.DATETIME_FORMATS[format] || format;
19130 if (isString(date)) {
19131 date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date);
19134 if (isNumber(date)) {
19135 date = new Date(date);
19138 if (!isDate(date) || !isFinite(date.getTime())) {
19143 match = DATE_FORMATS_SPLIT.exec(format);
19145 parts = concat(parts, match, 1);
19146 format = parts.pop();
19148 parts.push(format);
19153 var dateTimezoneOffset = date.getTimezoneOffset();
19155 dateTimezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
19156 date = convertTimezoneToLocal(date, timezone, true);
19158 forEach(parts, function(value) {
19159 fn = DATE_FORMATS[value];
19160 text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset)
19161 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
19175 * Allows you to convert a JavaScript object into JSON string.
19177 * This filter is mostly useful for debugging. When using the double curly {{value}} notation
19178 * the binding is automatically converted to JSON.
19180 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
19181 * @param {number=} spacing The number of spaces to use per indentation, defaults to 2.
19182 * @returns {string} JSON string.
19187 <file name="index.html">
19188 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre>
19189 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre>
19191 <file name="protractor.js" type="protractor">
19192 it('should jsonify filtered objects', function() {
19193 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
19194 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
19200 function jsonFilter() {
19201 return function(object, spacing) {
19202 if (isUndefined(spacing)) {
19205 return toJson(object, spacing);
19215 * Converts string to lowercase.
19216 * @see angular.lowercase
19218 var lowercaseFilter = valueFn(lowercase);
19226 * Converts string to uppercase.
19227 * @see angular.uppercase
19229 var uppercaseFilter = valueFn(uppercase);
19237 * Creates a new array or string containing only a specified number of elements. The elements
19238 * are taken from either the beginning or the end of the source array, string or number, as specified by
19239 * the value and sign (positive or negative) of `limit`. If a number is used as input, it is
19240 * converted to a string.
19242 * @param {Array|string|number} input Source array, string or number to be limited.
19243 * @param {string|number} limit The length of the returned array or string. If the `limit` number
19244 * is positive, `limit` number of items from the beginning of the source array/string are copied.
19245 * If the number is negative, `limit` number of items from the end of the source array/string
19246 * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined,
19247 * the input will be returned unchanged.
19248 * @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin`
19249 * indicates an offset from the end of `input`. Defaults to `0`.
19250 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
19251 * had less than `limit` elements.
19254 <example module="limitToExample">
19255 <file name="index.html">
19257 angular.module('limitToExample', [])
19258 .controller('ExampleController', ['$scope', function($scope) {
19259 $scope.numbers = [1,2,3,4,5,6,7,8,9];
19260 $scope.letters = "abcdefghi";
19261 $scope.longNumber = 2345432342;
19262 $scope.numLimit = 3;
19263 $scope.letterLimit = 3;
19264 $scope.longNumberLimit = 3;
19267 <div ng-controller="ExampleController">
19269 Limit {{numbers}} to:
19270 <input type="number" step="1" ng-model="numLimit">
19272 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p>
19274 Limit {{letters}} to:
19275 <input type="number" step="1" ng-model="letterLimit">
19277 <p>Output letters: {{ letters | limitTo:letterLimit }}</p>
19279 Limit {{longNumber}} to:
19280 <input type="number" step="1" ng-model="longNumberLimit">
19282 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p>
19285 <file name="protractor.js" type="protractor">
19286 var numLimitInput = element(by.model('numLimit'));
19287 var letterLimitInput = element(by.model('letterLimit'));
19288 var longNumberLimitInput = element(by.model('longNumberLimit'));
19289 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit'));
19290 var limitedLetters = element(by.binding('letters | limitTo:letterLimit'));
19291 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit'));
19293 it('should limit the number array to first three items', function() {
19294 expect(numLimitInput.getAttribute('value')).toBe('3');
19295 expect(letterLimitInput.getAttribute('value')).toBe('3');
19296 expect(longNumberLimitInput.getAttribute('value')).toBe('3');
19297 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]');
19298 expect(limitedLetters.getText()).toEqual('Output letters: abc');
19299 expect(limitedLongNumber.getText()).toEqual('Output long number: 234');
19302 // There is a bug in safari and protractor that doesn't like the minus key
19303 // it('should update the output when -3 is entered', function() {
19304 // numLimitInput.clear();
19305 // numLimitInput.sendKeys('-3');
19306 // letterLimitInput.clear();
19307 // letterLimitInput.sendKeys('-3');
19308 // longNumberLimitInput.clear();
19309 // longNumberLimitInput.sendKeys('-3');
19310 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
19311 // expect(limitedLetters.getText()).toEqual('Output letters: ghi');
19312 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342');
19315 it('should not exceed the maximum size of input array', function() {
19316 numLimitInput.clear();
19317 numLimitInput.sendKeys('100');
19318 letterLimitInput.clear();
19319 letterLimitInput.sendKeys('100');
19320 longNumberLimitInput.clear();
19321 longNumberLimitInput.sendKeys('100');
19322 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]');
19323 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi');
19324 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342');
19329 function limitToFilter() {
19330 return function(input, limit, begin) {
19331 if (Math.abs(Number(limit)) === Infinity) {
19332 limit = Number(limit);
19334 limit = toInt(limit);
19336 if (isNaN(limit)) return input;
19338 if (isNumber(input)) input = input.toString();
19339 if (!isArray(input) && !isString(input)) return input;
19341 begin = (!begin || isNaN(begin)) ? 0 : toInt(begin);
19342 begin = (begin < 0) ? Math.max(0, input.length + begin) : begin;
19345 return input.slice(begin, begin + limit);
19348 return input.slice(limit, input.length);
19350 return input.slice(Math.max(0, begin + limit), begin);
19362 * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically
19363 * for strings and numerically for numbers. Note: if you notice numbers are not being sorted
19364 * as expected, make sure they are actually being saved as numbers and not strings.
19366 * @param {Array} array The array to sort.
19367 * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be
19368 * used by the comparator to determine the order of elements.
19372 * - `function`: Getter function. The result of this function will be sorted using the
19373 * `<`, `===`, `>` operator.
19374 * - `string`: An Angular expression. The result of this expression is used to compare elements
19375 * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by
19376 * 3 first characters of a property called `name`). The result of a constant expression
19377 * is interpreted as a property name to be used in comparisons (for example `"special name"`
19378 * to sort object by the value of their `special name` property). An expression can be
19379 * optionally prefixed with `+` or `-` to control ascending or descending sort order
19380 * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array
19381 * element itself is used to compare where sorting.
19382 * - `Array`: An array of function or string predicates. The first predicate in the array
19383 * is used for sorting, but when two items are equivalent, the next predicate is used.
19385 * If the predicate is missing or empty then it defaults to `'+'`.
19387 * @param {boolean=} reverse Reverse the order of the array.
19388 * @returns {Array} Sorted copy of the source array.
19392 * The example below demonstrates a simple ngRepeat, where the data is sorted
19393 * by age in descending order (predicate is set to `'-age'`).
19394 * `reverse` is not set, which means it defaults to `false`.
19395 <example module="orderByExample">
19396 <file name="index.html">
19398 angular.module('orderByExample', [])
19399 .controller('ExampleController', ['$scope', function($scope) {
19401 [{name:'John', phone:'555-1212', age:10},
19402 {name:'Mary', phone:'555-9876', age:19},
19403 {name:'Mike', phone:'555-4321', age:21},
19404 {name:'Adam', phone:'555-5678', age:35},
19405 {name:'Julie', phone:'555-8765', age:29}];
19408 <div ng-controller="ExampleController">
19409 <table class="friend">
19412 <th>Phone Number</th>
19415 <tr ng-repeat="friend in friends | orderBy:'-age'">
19416 <td>{{friend.name}}</td>
19417 <td>{{friend.phone}}</td>
19418 <td>{{friend.age}}</td>
19425 * The predicate and reverse parameters can be controlled dynamically through scope properties,
19426 * as shown in the next example.
19428 <example module="orderByExample">
19429 <file name="index.html">
19431 angular.module('orderByExample', [])
19432 .controller('ExampleController', ['$scope', function($scope) {
19434 [{name:'John', phone:'555-1212', age:10},
19435 {name:'Mary', phone:'555-9876', age:19},
19436 {name:'Mike', phone:'555-4321', age:21},
19437 {name:'Adam', phone:'555-5678', age:35},
19438 {name:'Julie', phone:'555-8765', age:29}];
19439 $scope.predicate = 'age';
19440 $scope.reverse = true;
19441 $scope.order = function(predicate) {
19442 $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
19443 $scope.predicate = predicate;
19447 <style type="text/css">
19451 .sortorder.reverse:after {
19455 <div ng-controller="ExampleController">
19456 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
19458 [ <a href="" ng-click="predicate=''">unsorted</a> ]
19459 <table class="friend">
19462 <a href="" ng-click="order('name')">Name</a>
19463 <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
19466 <a href="" ng-click="order('phone')">Phone Number</a>
19467 <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span>
19470 <a href="" ng-click="order('age')">Age</a>
19471 <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span>
19474 <tr ng-repeat="friend in friends | orderBy:predicate:reverse">
19475 <td>{{friend.name}}</td>
19476 <td>{{friend.phone}}</td>
19477 <td>{{friend.age}}</td>
19484 * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the
19485 * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the
19486 * desired parameters.
19491 <example module="orderByExample">
19492 <file name="index.html">
19493 <div ng-controller="ExampleController">
19494 <table class="friend">
19496 <th><a href="" ng-click="reverse=false;order('name', false)">Name</a>
19497 (<a href="" ng-click="order('-name',false)">^</a>)</th>
19498 <th><a href="" ng-click="reverse=!reverse;order('phone', reverse)">Phone Number</a></th>
19499 <th><a href="" ng-click="reverse=!reverse;order('age',reverse)">Age</a></th>
19501 <tr ng-repeat="friend in friends">
19502 <td>{{friend.name}}</td>
19503 <td>{{friend.phone}}</td>
19504 <td>{{friend.age}}</td>
19510 <file name="script.js">
19511 angular.module('orderByExample', [])
19512 .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) {
19513 var orderBy = $filter('orderBy');
19515 { name: 'John', phone: '555-1212', age: 10 },
19516 { name: 'Mary', phone: '555-9876', age: 19 },
19517 { name: 'Mike', phone: '555-4321', age: 21 },
19518 { name: 'Adam', phone: '555-5678', age: 35 },
19519 { name: 'Julie', phone: '555-8765', age: 29 }
19521 $scope.order = function(predicate, reverse) {
19522 $scope.friends = orderBy($scope.friends, predicate, reverse);
19524 $scope.order('-age',false);
19529 orderByFilter.$inject = ['$parse'];
19530 function orderByFilter($parse) {
19531 return function(array, sortPredicate, reverseOrder) {
19533 if (!(isArrayLike(array))) return array;
19535 if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; }
19536 if (sortPredicate.length === 0) { sortPredicate = ['+']; }
19538 var predicates = processPredicates(sortPredicate, reverseOrder);
19539 // Add a predicate at the end that evaluates to the element index. This makes the
19540 // sort stable as it works as a tie-breaker when all the input predicates cannot
19541 // distinguish between two elements.
19542 predicates.push({ get: function() { return {}; }, descending: reverseOrder ? -1 : 1});
19544 // The next three lines are a version of a Swartzian Transform idiom from Perl
19545 // (sometimes called the Decorate-Sort-Undecorate idiom)
19546 // See https://en.wikipedia.org/wiki/Schwartzian_transform
19547 var compareValues = Array.prototype.map.call(array, getComparisonObject);
19548 compareValues.sort(doComparison);
19549 array = compareValues.map(function(item) { return item.value; });
19553 function getComparisonObject(value, index) {
19556 predicateValues: predicates.map(function(predicate) {
19557 return getPredicateValue(predicate.get(value), index);
19562 function doComparison(v1, v2) {
19564 for (var index=0, length = predicates.length; index < length; ++index) {
19565 result = compare(v1.predicateValues[index], v2.predicateValues[index]) * predicates[index].descending;
19572 function processPredicates(sortPredicate, reverseOrder) {
19573 reverseOrder = reverseOrder ? -1 : 1;
19574 return sortPredicate.map(function(predicate) {
19575 var descending = 1, get = identity;
19577 if (isFunction(predicate)) {
19579 } else if (isString(predicate)) {
19580 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) {
19581 descending = predicate.charAt(0) == '-' ? -1 : 1;
19582 predicate = predicate.substring(1);
19584 if (predicate !== '') {
19585 get = $parse(predicate);
19586 if (get.constant) {
19588 get = function(value) { return value[key]; };
19592 return { get: get, descending: descending * reverseOrder };
19596 function isPrimitive(value) {
19597 switch (typeof value) {
19598 case 'number': /* falls through */
19599 case 'boolean': /* falls through */
19607 function objectValue(value, index) {
19608 // If `valueOf` is a valid function use that
19609 if (typeof value.valueOf === 'function') {
19610 value = value.valueOf();
19611 if (isPrimitive(value)) return value;
19613 // If `toString` is a valid function and not the one from `Object.prototype` use that
19614 if (hasCustomToString(value)) {
19615 value = value.toString();
19616 if (isPrimitive(value)) return value;
19618 // We have a basic object so we use the position of the object in the collection
19622 function getPredicateValue(value, index) {
19623 var type = typeof value;
19624 if (value === null) {
19627 } else if (type === 'string') {
19628 value = value.toLowerCase();
19629 } else if (type === 'object') {
19630 value = objectValue(value, index);
19632 return { value: value, type: type };
19635 function compare(v1, v2) {
19637 if (v1.type === v2.type) {
19638 if (v1.value !== v2.value) {
19639 result = v1.value < v2.value ? -1 : 1;
19642 result = v1.type < v2.type ? -1 : 1;
19648 function ngDirective(directive) {
19649 if (isFunction(directive)) {
19654 directive.restrict = directive.restrict || 'AC';
19655 return valueFn(directive);
19664 * Modifies the default behavior of the html A tag so that the default action is prevented when
19665 * the href attribute is empty.
19667 * This change permits the easy creation of action links with the `ngClick` directive
19668 * without changing the location or causing page reloads, e.g.:
19669 * `<a href="" ng-click="list.addItem()">Add Item</a>`
19671 var htmlAnchorDirective = valueFn({
19673 compile: function(element, attr) {
19674 if (!attr.href && !attr.xlinkHref) {
19675 return function(scope, element) {
19676 // If the linked element is not an anchor tag anymore, do nothing
19677 if (element[0].nodeName.toLowerCase() !== 'a') return;
19679 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
19680 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
19681 'xlink:href' : 'href';
19682 element.on('click', function(event) {
19683 // if we have no href url, then don't navigate anywhere.
19684 if (!element.attr(href)) {
19685 event.preventDefault();
19700 * Using Angular markup like `{{hash}}` in an href attribute will
19701 * make the link go to the wrong URL if the user clicks it before
19702 * Angular has a chance to replace the `{{hash}}` markup with its
19703 * value. Until Angular replaces the markup the link will be broken
19704 * and will most likely return a 404 error. The `ngHref` directive
19705 * solves this problem.
19707 * The wrong way to write it:
19709 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
19712 * The correct way to write it:
19714 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
19718 * @param {template} ngHref any string which can contain `{{}}` markup.
19721 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes
19722 * in links and their different behaviors:
19724 <file name="index.html">
19725 <input ng-model="value" /><br />
19726 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br />
19727 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br />
19728 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br />
19729 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br />
19730 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br />
19731 <a id="link-6" ng-href="{{value}}">link</a> (link, change location)
19733 <file name="protractor.js" type="protractor">
19734 it('should execute ng-click but not reload when href without value', function() {
19735 element(by.id('link-1')).click();
19736 expect(element(by.model('value')).getAttribute('value')).toEqual('1');
19737 expect(element(by.id('link-1')).getAttribute('href')).toBe('');
19740 it('should execute ng-click but not reload when href empty string', function() {
19741 element(by.id('link-2')).click();
19742 expect(element(by.model('value')).getAttribute('value')).toEqual('2');
19743 expect(element(by.id('link-2')).getAttribute('href')).toBe('');
19746 it('should execute ng-click and change url when ng-href specified', function() {
19747 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/);
19749 element(by.id('link-3')).click();
19751 // At this point, we navigate away from an Angular page, so we need
19752 // to use browser.driver to get the base webdriver.
19754 browser.wait(function() {
19755 return browser.driver.getCurrentUrl().then(function(url) {
19756 return url.match(/\/123$/);
19758 }, 5000, 'page should navigate to /123');
19761 it('should execute ng-click but not reload when href empty string and name specified', function() {
19762 element(by.id('link-4')).click();
19763 expect(element(by.model('value')).getAttribute('value')).toEqual('4');
19764 expect(element(by.id('link-4')).getAttribute('href')).toBe('');
19767 it('should execute ng-click but not reload when no href but name specified', function() {
19768 element(by.id('link-5')).click();
19769 expect(element(by.model('value')).getAttribute('value')).toEqual('5');
19770 expect(element(by.id('link-5')).getAttribute('href')).toBe(null);
19773 it('should only change url when only ng-href', function() {
19774 element(by.model('value')).clear();
19775 element(by.model('value')).sendKeys('6');
19776 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/);
19778 element(by.id('link-6')).click();
19780 // At this point, we navigate away from an Angular page, so we need
19781 // to use browser.driver to get the base webdriver.
19782 browser.wait(function() {
19783 return browser.driver.getCurrentUrl().then(function(url) {
19784 return url.match(/\/6$/);
19786 }, 5000, 'page should navigate to /6');
19799 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't
19800 * work right: The browser will fetch from the URL with the literal
19801 * text `{{hash}}` until Angular replaces the expression inside
19802 * `{{hash}}`. The `ngSrc` directive solves this problem.
19804 * The buggy way to write it:
19806 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/>
19809 * The correct way to write it:
19811 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" />
19815 * @param {template} ngSrc any string which can contain `{{}}` markup.
19825 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't
19826 * work right: The browser will fetch from the URL with the literal
19827 * text `{{hash}}` until Angular replaces the expression inside
19828 * `{{hash}}`. The `ngSrcset` directive solves this problem.
19830 * The buggy way to write it:
19832 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/>
19835 * The correct way to write it:
19837 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" />
19841 * @param {template} ngSrcset any string which can contain `{{}}` markup.
19852 * This directive sets the `disabled` attribute on the element if the
19853 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy.
19855 * A special directive is necessary because we cannot use interpolation inside the `disabled`
19856 * attribute. The following example would make the button enabled on Chrome/Firefox
19857 * but not on older IEs:
19860 * <!-- See below for an example of ng-disabled being used correctly -->
19861 * <div ng-init="isDisabled = false">
19862 * <button disabled="{{isDisabled}}">Disabled</button>
19866 * This is because the HTML specification does not require browsers to preserve the values of
19867 * boolean attributes such as `disabled` (Their presence means true and their absence means false.)
19868 * If we put an Angular interpolation expression into such an attribute then the
19869 * binding information would be lost when the browser removes the attribute.
19873 <file name="index.html">
19874 <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/>
19875 <button ng-model="button" ng-disabled="checked">Button</button>
19877 <file name="protractor.js" type="protractor">
19878 it('should toggle button', function() {
19879 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
19880 element(by.model('checked')).click();
19881 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy();
19887 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
19888 * then the `disabled` attribute will be set on the element
19899 * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy.
19901 * Note that this directive should not be used together with {@link ngModel `ngModel`},
19902 * as this can lead to unexpected behavior.
19904 * ### Why do we need `ngChecked`?
19906 * The HTML specification does not require browsers to preserve the values of boolean attributes
19907 * such as checked. (Their presence means true and their absence means false.)
19908 * If we put an Angular interpolation expression into such an attribute then the
19909 * binding information would be lost when the browser removes the attribute.
19910 * The `ngChecked` directive solves this problem for the `checked` attribute.
19911 * This complementary directive is not removed by the browser and so provides
19912 * a permanent reliable place to store the binding information.
19915 <file name="index.html">
19916 <label>Check me to check both: <input type="checkbox" ng-model="master"></label><br/>
19917 <input id="checkSlave" type="checkbox" ng-checked="master" aria-label="Slave input">
19919 <file name="protractor.js" type="protractor">
19920 it('should check both checkBoxes', function() {
19921 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy();
19922 element(by.model('master')).click();
19923 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy();
19929 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy,
19930 * then the `checked` attribute will be set on the element
19941 * The HTML specification does not require browsers to preserve the values of boolean attributes
19942 * such as readonly. (Their presence means true and their absence means false.)
19943 * If we put an Angular interpolation expression into such an attribute then the
19944 * binding information would be lost when the browser removes the attribute.
19945 * The `ngReadonly` directive solves this problem for the `readonly` attribute.
19946 * This complementary directive is not removed by the browser and so provides
19947 * a permanent reliable place to store the binding information.
19950 <file name="index.html">
19951 <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/>
19952 <input type="text" ng-readonly="checked" value="I'm Angular" aria-label="Readonly field" />
19954 <file name="protractor.js" type="protractor">
19955 it('should toggle readonly attr', function() {
19956 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy();
19957 element(by.model('checked')).click();
19958 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy();
19964 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy,
19965 * then special attribute "readonly" will be set on the element
19976 * The HTML specification does not require browsers to preserve the values of boolean attributes
19977 * such as selected. (Their presence means true and their absence means false.)
19978 * If we put an Angular interpolation expression into such an attribute then the
19979 * binding information would be lost when the browser removes the attribute.
19980 * The `ngSelected` directive solves this problem for the `selected` attribute.
19981 * This complementary directive is not removed by the browser and so provides
19982 * a permanent reliable place to store the binding information.
19986 <file name="index.html">
19987 <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/>
19988 <select aria-label="ngSelected demo">
19989 <option>Hello!</option>
19990 <option id="greet" ng-selected="selected">Greetings!</option>
19993 <file name="protractor.js" type="protractor">
19994 it('should select Greetings!', function() {
19995 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
19996 element(by.model('selected')).click();
19997 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy();
20003 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy,
20004 * then special attribute "selected" will be set on the element
20014 * The HTML specification does not require browsers to preserve the values of boolean attributes
20015 * such as open. (Their presence means true and their absence means false.)
20016 * If we put an Angular interpolation expression into such an attribute then the
20017 * binding information would be lost when the browser removes the attribute.
20018 * The `ngOpen` directive solves this problem for the `open` attribute.
20019 * This complementary directive is not removed by the browser and so provides
20020 * a permanent reliable place to store the binding information.
20023 <file name="index.html">
20024 <label>Check me check multiple: <input type="checkbox" ng-model="open"></label><br/>
20025 <details id="details" ng-open="open">
20026 <summary>Show/Hide me</summary>
20029 <file name="protractor.js" type="protractor">
20030 it('should toggle open', function() {
20031 expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
20032 element(by.model('open')).click();
20033 expect(element(by.id('details')).getAttribute('open')).toBeTruthy();
20039 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy,
20040 * then special attribute "open" will be set on the element
20043 var ngAttributeAliasDirectives = {};
20045 // boolean attrs are evaluated
20046 forEach(BOOLEAN_ATTR, function(propName, attrName) {
20047 // binding to multiple is not supported
20048 if (propName == "multiple") return;
20050 function defaultLinkFn(scope, element, attr) {
20051 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
20052 attr.$set(attrName, !!value);
20056 var normalized = directiveNormalize('ng-' + attrName);
20057 var linkFn = defaultLinkFn;
20059 if (propName === 'checked') {
20060 linkFn = function(scope, element, attr) {
20061 // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input
20062 if (attr.ngModel !== attr[normalized]) {
20063 defaultLinkFn(scope, element, attr);
20068 ngAttributeAliasDirectives[normalized] = function() {
20077 // aliased input attrs are evaluated
20078 forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) {
20079 ngAttributeAliasDirectives[ngAttr] = function() {
20082 link: function(scope, element, attr) {
20083 //special case ngPattern when a literal regular expression value
20084 //is used as the expression (this way we don't have to watch anything).
20085 if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") {
20086 var match = attr.ngPattern.match(REGEX_STRING_REGEXP);
20088 attr.$set("ngPattern", new RegExp(match[1], match[2]));
20093 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) {
20094 attr.$set(ngAttr, value);
20101 // ng-src, ng-srcset, ng-href are interpolated
20102 forEach(['src', 'srcset', 'href'], function(attrName) {
20103 var normalized = directiveNormalize('ng-' + attrName);
20104 ngAttributeAliasDirectives[normalized] = function() {
20106 priority: 99, // it needs to run after the attributes are interpolated
20107 link: function(scope, element, attr) {
20108 var propName = attrName,
20111 if (attrName === 'href' &&
20112 toString.call(element.prop('href')) === '[object SVGAnimatedString]') {
20113 name = 'xlinkHref';
20114 attr.$attr[name] = 'xlink:href';
20118 attr.$observe(normalized, function(value) {
20120 if (attrName === 'href') {
20121 attr.$set(name, null);
20126 attr.$set(name, value);
20128 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
20129 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
20130 // to set the property as well to achieve the desired effect.
20131 // we use attr[attrName] value since $set can sanitize the url.
20132 if (msie && propName) element.prop(propName, attr[name]);
20139 /* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true
20141 var nullFormCtrl = {
20143 $$renameControl: nullFormRenameControl,
20144 $removeControl: noop,
20145 $setValidity: noop,
20147 $setPristine: noop,
20148 $setSubmitted: noop
20150 SUBMITTED_CLASS = 'ng-submitted';
20152 function nullFormRenameControl(control, name) {
20153 control.$name = name;
20158 * @name form.FormController
20160 * @property {boolean} $pristine True if user has not interacted with the form yet.
20161 * @property {boolean} $dirty True if user has already interacted with the form.
20162 * @property {boolean} $valid True if all of the containing forms and controls are valid.
20163 * @property {boolean} $invalid True if at least one containing control or form is invalid.
20164 * @property {boolean} $pending True if at least one containing control or form is pending.
20165 * @property {boolean} $submitted True if user has submitted the form even if its invalid.
20167 * @property {Object} $error Is an object hash, containing references to controls or
20168 * forms with failing validators, where:
20170 * - keys are validation tokens (error names),
20171 * - values are arrays of controls or forms that have a failing validator for given error name.
20173 * Built-in validation tokens:
20185 * - `datetimelocal`
20191 * `FormController` keeps track of all its controls and nested forms as well as the state of them,
20192 * such as being valid/invalid or dirty/pristine.
20194 * Each {@link ng.directive:form form} directive creates an instance
20195 * of `FormController`.
20198 //asks for $scope to fool the BC controller module
20199 FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate'];
20200 function FormController(element, attrs, $scope, $animate, $interpolate) {
20206 form.$$success = {};
20207 form.$pending = undefined;
20208 form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope);
20209 form.$dirty = false;
20210 form.$pristine = true;
20211 form.$valid = true;
20212 form.$invalid = false;
20213 form.$submitted = false;
20214 form.$$parentForm = nullFormCtrl;
20218 * @name form.FormController#$rollbackViewValue
20221 * Rollback all form controls pending updates to the `$modelValue`.
20223 * Updates may be pending by a debounced event or because the input is waiting for a some future
20224 * event defined in `ng-model-options`. This method is typically needed by the reset button of
20225 * a form that uses `ng-model-options` to pend updates.
20227 form.$rollbackViewValue = function() {
20228 forEach(controls, function(control) {
20229 control.$rollbackViewValue();
20235 * @name form.FormController#$commitViewValue
20238 * Commit all form controls pending updates to the `$modelValue`.
20240 * Updates may be pending by a debounced event or because the input is waiting for a some future
20241 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController`
20242 * usually handles calling this in response to input events.
20244 form.$commitViewValue = function() {
20245 forEach(controls, function(control) {
20246 control.$commitViewValue();
20252 * @name form.FormController#$addControl
20253 * @param {object} control control object, either a {@link form.FormController} or an
20254 * {@link ngModel.NgModelController}
20257 * Register a control with the form. Input elements using ngModelController do this automatically
20258 * when they are linked.
20260 * Note that the current state of the control will not be reflected on the new parent form. This
20261 * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine`
20264 * However, if the method is used programmatically, for example by adding dynamically created controls,
20265 * or controls that have been previously removed without destroying their corresponding DOM element,
20266 * it's the developers responsiblity to make sure the current state propagates to the parent form.
20268 * For example, if an input control is added that is already `$dirty` and has `$error` properties,
20269 * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form.
20271 form.$addControl = function(control) {
20272 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored
20273 // and not added to the scope. Now we throw an error.
20274 assertNotHasOwnProperty(control.$name, 'input');
20275 controls.push(control);
20277 if (control.$name) {
20278 form[control.$name] = control;
20281 control.$$parentForm = form;
20284 // Private API: rename a form control
20285 form.$$renameControl = function(control, newName) {
20286 var oldName = control.$name;
20288 if (form[oldName] === control) {
20289 delete form[oldName];
20291 form[newName] = control;
20292 control.$name = newName;
20297 * @name form.FormController#$removeControl
20298 * @param {object} control control object, either a {@link form.FormController} or an
20299 * {@link ngModel.NgModelController}
20302 * Deregister a control from the form.
20304 * Input elements using ngModelController do this automatically when they are destroyed.
20306 * Note that only the removed control's validation state (`$errors`etc.) will be removed from the
20307 * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be
20308 * different from case to case. For example, removing the only `$dirty` control from a form may or
20309 * may not mean that the form is still `$dirty`.
20311 form.$removeControl = function(control) {
20312 if (control.$name && form[control.$name] === control) {
20313 delete form[control.$name];
20315 forEach(form.$pending, function(value, name) {
20316 form.$setValidity(name, null, control);
20318 forEach(form.$error, function(value, name) {
20319 form.$setValidity(name, null, control);
20321 forEach(form.$$success, function(value, name) {
20322 form.$setValidity(name, null, control);
20325 arrayRemove(controls, control);
20326 control.$$parentForm = nullFormCtrl;
20332 * @name form.FormController#$setValidity
20335 * Sets the validity of a form control.
20337 * This method will also propagate to parent forms.
20339 addSetValidityMethod({
20342 set: function(object, property, controller) {
20343 var list = object[property];
20345 object[property] = [controller];
20347 var index = list.indexOf(controller);
20348 if (index === -1) {
20349 list.push(controller);
20353 unset: function(object, property, controller) {
20354 var list = object[property];
20358 arrayRemove(list, controller);
20359 if (list.length === 0) {
20360 delete object[property];
20368 * @name form.FormController#$setDirty
20371 * Sets the form to a dirty state.
20373 * This method can be called to add the 'ng-dirty' class and set the form to a dirty
20374 * state (ng-dirty class). This method will also propagate to parent forms.
20376 form.$setDirty = function() {
20377 $animate.removeClass(element, PRISTINE_CLASS);
20378 $animate.addClass(element, DIRTY_CLASS);
20379 form.$dirty = true;
20380 form.$pristine = false;
20381 form.$$parentForm.$setDirty();
20386 * @name form.FormController#$setPristine
20389 * Sets the form to its pristine state.
20391 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine
20392 * state (ng-pristine class). This method will also propagate to all the controls contained
20395 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
20396 * saving or resetting it.
20398 form.$setPristine = function() {
20399 $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS);
20400 form.$dirty = false;
20401 form.$pristine = true;
20402 form.$submitted = false;
20403 forEach(controls, function(control) {
20404 control.$setPristine();
20410 * @name form.FormController#$setUntouched
20413 * Sets the form to its untouched state.
20415 * This method can be called to remove the 'ng-touched' class and set the form controls to their
20416 * untouched state (ng-untouched class).
20418 * Setting a form controls back to their untouched state is often useful when setting the form
20419 * back to its pristine state.
20421 form.$setUntouched = function() {
20422 forEach(controls, function(control) {
20423 control.$setUntouched();
20429 * @name form.FormController#$setSubmitted
20432 * Sets the form to its submitted state.
20434 form.$setSubmitted = function() {
20435 $animate.addClass(element, SUBMITTED_CLASS);
20436 form.$submitted = true;
20437 form.$$parentForm.$setSubmitted();
20447 * Nestable alias of {@link ng.directive:form `form`} directive. HTML
20448 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
20449 * sub-group of controls needs to be determined.
20451 * Note: the purpose of `ngForm` is to group controls,
20452 * but not to be a replacement for the `<form>` tag with all of its capabilities
20453 * (e.g. posting to the server, ...).
20455 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into
20456 * related scope, under this name.
20466 * Directive that instantiates
20467 * {@link form.FormController FormController}.
20469 * If the `name` attribute is specified, the form controller is published onto the current scope under
20472 * # Alias: {@link ng.directive:ngForm `ngForm`}
20474 * In Angular, forms can be nested. This means that the outer form is valid when all of the child
20475 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so
20476 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to
20477 * `<form>` but can be nested. This allows you to have nested forms, which is very useful when
20478 * using Angular validation directives in forms that are dynamically generated using the
20479 * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name`
20480 * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an
20481 * `ngForm` directive and nest these in an outer `form` element.
20485 * - `ng-valid` is set if the form is valid.
20486 * - `ng-invalid` is set if the form is invalid.
20487 * - `ng-pending` is set if the form is pending.
20488 * - `ng-pristine` is set if the form is pristine.
20489 * - `ng-dirty` is set if the form is dirty.
20490 * - `ng-submitted` is set if the form was submitted.
20492 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
20495 * # Submitting a form and preventing the default action
20497 * Since the role of forms in client-side Angular applications is different than in classical
20498 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full
20499 * page reload that sends the data to the server. Instead some javascript logic should be triggered
20500 * to handle the form submission in an application-specific way.
20502 * For this reason, Angular prevents the default action (form submission to the server) unless the
20503 * `<form>` element has an `action` attribute specified.
20505 * You can use one of the following two ways to specify what javascript method should be called when
20506 * a form is submitted:
20508 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element
20509 * - {@link ng.directive:ngClick ngClick} directive on the first
20510 * button or input field of type submit (input[type=submit])
20512 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit}
20513 * or {@link ng.directive:ngClick ngClick} directives.
20514 * This is because of the following form submission rules in the HTML specification:
20516 * - If a form has only one input field then hitting enter in this field triggers form submit
20518 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter
20519 * doesn't trigger submit
20520 * - if a form has one or more input fields and one or more buttons or input[type=submit] then
20521 * hitting enter in any of the input fields will trigger the click handler on the *first* button or
20522 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
20524 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is
20525 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
20526 * to have access to the updated model.
20528 * ## Animation Hooks
20530 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed.
20531 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any
20532 * other validations that are performed within the form. Animations in ngForm are similar to how
20533 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well
20534 * as JS animations.
20536 * The following example shows a simple way to utilize CSS transitions to style a form element
20537 * that has been rendered as invalid after it has been validated:
20540 * //be sure to include ngAnimate as a module to hook into more
20541 * //advanced animations
20543 * transition:0.5s linear all;
20544 * background: white;
20546 * .my-form.ng-invalid {
20553 <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample">
20554 <file name="index.html">
20556 angular.module('formExample', [])
20557 .controller('FormController', ['$scope', function($scope) {
20558 $scope.userType = 'guest';
20563 transition:all linear 0.5s;
20564 background: transparent;
20566 .my-form.ng-invalid {
20570 <form name="myForm" ng-controller="FormController" class="my-form">
20571 userType: <input name="input" ng-model="userType" required>
20572 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br>
20573 <code>userType = {{userType}}</code><br>
20574 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br>
20575 <code>myForm.input.$error = {{myForm.input.$error}}</code><br>
20576 <code>myForm.$valid = {{myForm.$valid}}</code><br>
20577 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br>
20580 <file name="protractor.js" type="protractor">
20581 it('should initialize to model', function() {
20582 var userType = element(by.binding('userType'));
20583 var valid = element(by.binding('myForm.input.$valid'));
20585 expect(userType.getText()).toContain('guest');
20586 expect(valid.getText()).toContain('true');
20589 it('should be invalid if empty', function() {
20590 var userType = element(by.binding('userType'));
20591 var valid = element(by.binding('myForm.input.$valid'));
20592 var userInput = element(by.model('userType'));
20595 userInput.sendKeys('');
20597 expect(userType.getText()).toEqual('userType =');
20598 expect(valid.getText()).toContain('false');
20603 * @param {string=} name Name of the form. If specified, the form controller will be published into
20604 * related scope, under this name.
20606 var formDirectiveFactory = function(isNgForm) {
20607 return ['$timeout', '$parse', function($timeout, $parse) {
20608 var formDirective = {
20610 restrict: isNgForm ? 'EAC' : 'E',
20611 require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form
20612 controller: FormController,
20613 compile: function ngFormCompile(formElement, attr) {
20614 // Setup initial state of the control
20615 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);
20617 var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false);
20620 pre: function ngFormPreLink(scope, formElement, attr, ctrls) {
20621 var controller = ctrls[0];
20623 // if `action` attr is not present on the form, prevent the default action (submission)
20624 if (!('action' in attr)) {
20625 // we can't use jq events because if a form is destroyed during submission the default
20626 // action is not prevented. see #1238
20628 // IE 9 is not affected because it doesn't fire a submit event and try to do a full
20629 // page reload if the form was destroyed by submission of the form via a click handler
20630 // on a button in the form. Looks like an IE9 specific bug.
20631 var handleFormSubmission = function(event) {
20632 scope.$apply(function() {
20633 controller.$commitViewValue();
20634 controller.$setSubmitted();
20637 event.preventDefault();
20640 addEventListenerFn(formElement[0], 'submit', handleFormSubmission);
20642 // unregister the preventDefault listener so that we don't not leak memory but in a
20643 // way that will achieve the prevention of the default action.
20644 formElement.on('$destroy', function() {
20645 $timeout(function() {
20646 removeEventListenerFn(formElement[0], 'submit', handleFormSubmission);
20651 var parentFormCtrl = ctrls[1] || controller.$$parentForm;
20652 parentFormCtrl.$addControl(controller);
20654 var setter = nameAttr ? getSetter(controller.$name) : noop;
20657 setter(scope, controller);
20658 attr.$observe(nameAttr, function(newValue) {
20659 if (controller.$name === newValue) return;
20660 setter(scope, undefined);
20661 controller.$$parentForm.$$renameControl(controller, newValue);
20662 setter = getSetter(controller.$name);
20663 setter(scope, controller);
20666 formElement.on('$destroy', function() {
20667 controller.$$parentForm.$removeControl(controller);
20668 setter(scope, undefined);
20669 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
20676 return formDirective;
20678 function getSetter(expression) {
20679 if (expression === '') {
20680 //create an assignable expression, so forms with an empty name can be renamed later
20681 return $parse('this[""]').assign;
20683 return $parse(expression).assign || noop;
20688 var formDirective = formDirectiveFactory();
20689 var ngFormDirective = formDirectiveFactory(true);
20691 /* global VALID_CLASS: false,
20692 INVALID_CLASS: false,
20693 PRISTINE_CLASS: false,
20694 DIRTY_CLASS: false,
20695 UNTOUCHED_CLASS: false,
20696 TOUCHED_CLASS: false,
20697 ngModelMinErr: false,
20700 // Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231
20701 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)/;
20702 // See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987)
20703 var URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.?+=&%@\-/]*)?$/;
20704 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;
20705 var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/;
20706 var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/;
20707 var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
20708 var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/;
20709 var MONTH_REGEXP = /^(\d{4})-(\d\d)$/;
20710 var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
20716 * @name input[text]
20719 * Standard HTML text input with angular data binding, inherited by most of the `input` elements.
20722 * @param {string} ngModel Assignable angular expression to data-bind to.
20723 * @param {string=} name Property name of the form under which the control is published.
20724 * @param {string=} required Adds `required` validation error key if the value is not entered.
20725 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20726 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20727 * `required` when you want to data-bind to the `required` attribute.
20728 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
20730 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
20731 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
20733 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
20734 * that contains the regular expression body that will be converted to a regular expression
20735 * as in the ngPattern directive.
20736 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
20737 * a RegExp found by evaluating the Angular expression given in the attribute value.
20738 * If the expression evaluates to a RegExp object, then this is used directly.
20739 * If the expression evaluates to a string, then it will be converted to a RegExp
20740 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
20741 * `new RegExp('^abc$')`.<br />
20742 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
20743 * start at the index of the last search's match, thus not taking the whole input value into
20745 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20746 * interaction with the input element.
20747 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
20748 * This parameter is ignored for input[type=password] controls, which will never trim the
20752 <example name="text-input-directive" module="textInputExample">
20753 <file name="index.html">
20755 angular.module('textInputExample', [])
20756 .controller('ExampleController', ['$scope', function($scope) {
20759 word: /^\s*\w*\s*$/
20763 <form name="myForm" ng-controller="ExampleController">
20764 <label>Single word:
20765 <input type="text" name="input" ng-model="example.text"
20766 ng-pattern="example.word" required ng-trim="false">
20769 <span class="error" ng-show="myForm.input.$error.required">
20771 <span class="error" ng-show="myForm.input.$error.pattern">
20772 Single word only!</span>
20774 <tt>text = {{example.text}}</tt><br/>
20775 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20776 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20777 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20778 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20781 <file name="protractor.js" type="protractor">
20782 var text = element(by.binding('example.text'));
20783 var valid = element(by.binding('myForm.input.$valid'));
20784 var input = element(by.model('example.text'));
20786 it('should initialize to model', function() {
20787 expect(text.getText()).toContain('guest');
20788 expect(valid.getText()).toContain('true');
20791 it('should be invalid if empty', function() {
20793 input.sendKeys('');
20795 expect(text.getText()).toEqual('text =');
20796 expect(valid.getText()).toContain('false');
20799 it('should be invalid if multi word', function() {
20801 input.sendKeys('hello world');
20803 expect(valid.getText()).toContain('false');
20808 'text': textInputType,
20812 * @name input[date]
20815 * Input with date validation and transformation. In browsers that do not yet support
20816 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601
20817 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many
20818 * modern browsers do not yet support this input type, it is important to provide cues to users on the
20819 * expected input format via a placeholder or label.
20821 * The model must always be a Date object, otherwise Angular will throw an error.
20822 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
20824 * The timezone to be used to read/write the `Date` instance in the model can be defined using
20825 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
20827 * @param {string} ngModel Assignable angular expression to data-bind to.
20828 * @param {string=} name Property name of the form under which the control is published.
20829 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
20830 * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
20831 * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5
20832 * constraint validation.
20833 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be
20834 * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
20835 * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5
20836 * constraint validation.
20837 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string
20838 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
20839 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string
20840 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
20841 * @param {string=} required Sets `required` validation error key if the value is not entered.
20842 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20843 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20844 * `required` when you want to data-bind to the `required` attribute.
20845 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20846 * interaction with the input element.
20849 <example name="date-input-directive" module="dateInputExample">
20850 <file name="index.html">
20852 angular.module('dateInputExample', [])
20853 .controller('DateController', ['$scope', function($scope) {
20855 value: new Date(2013, 9, 22)
20859 <form name="myForm" ng-controller="DateController as dateCtrl">
20860 <label for="exampleInput">Pick a date in 2013:</label>
20861 <input type="date" id="exampleInput" name="input" ng-model="example.value"
20862 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required />
20864 <span class="error" ng-show="myForm.input.$error.required">
20866 <span class="error" ng-show="myForm.input.$error.date">
20867 Not a valid date!</span>
20869 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/>
20870 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20871 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20872 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20873 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20876 <file name="protractor.js" type="protractor">
20877 var value = element(by.binding('example.value | date: "yyyy-MM-dd"'));
20878 var valid = element(by.binding('myForm.input.$valid'));
20879 var input = element(by.model('example.value'));
20881 // currently protractor/webdriver does not support
20882 // sending keys to all known HTML5 input controls
20883 // for various browsers (see https://github.com/angular/protractor/issues/562).
20884 function setInput(val) {
20885 // set the value of the element and force validation.
20886 var scr = "var ipt = document.getElementById('exampleInput'); " +
20887 "ipt.value = '" + val + "';" +
20888 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
20889 browser.executeScript(scr);
20892 it('should initialize to model', function() {
20893 expect(value.getText()).toContain('2013-10-22');
20894 expect(valid.getText()).toContain('myForm.input.$valid = true');
20897 it('should be invalid if empty', function() {
20899 expect(value.getText()).toEqual('value =');
20900 expect(valid.getText()).toContain('myForm.input.$valid = false');
20903 it('should be invalid if over max', function() {
20904 setInput('2015-01-01');
20905 expect(value.getText()).toContain('');
20906 expect(valid.getText()).toContain('myForm.input.$valid = false');
20911 'date': createDateInputType('date', DATE_REGEXP,
20912 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']),
20917 * @name input[datetime-local]
20920 * Input with datetime validation and transformation. In browsers that do not yet support
20921 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
20922 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`.
20924 * The model must always be a Date object, otherwise Angular will throw an error.
20925 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
20927 * The timezone to be used to read/write the `Date` instance in the model can be defined using
20928 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
20930 * @param {string} ngModel Assignable angular expression to data-bind to.
20931 * @param {string=} name Property name of the form under which the control is published.
20932 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
20933 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
20934 * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
20935 * Note that `min` will also add native HTML5 constraint validation.
20936 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
20937 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
20938 * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
20939 * Note that `max` will also add native HTML5 constraint validation.
20940 * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string
20941 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
20942 * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string
20943 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
20944 * @param {string=} required Sets `required` validation error key if the value is not entered.
20945 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20946 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20947 * `required` when you want to data-bind to the `required` attribute.
20948 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20949 * interaction with the input element.
20952 <example name="datetimelocal-input-directive" module="dateExample">
20953 <file name="index.html">
20955 angular.module('dateExample', [])
20956 .controller('DateController', ['$scope', function($scope) {
20958 value: new Date(2010, 11, 28, 14, 57)
20962 <form name="myForm" ng-controller="DateController as dateCtrl">
20963 <label for="exampleInput">Pick a date between in 2013:</label>
20964 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value"
20965 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required />
20967 <span class="error" ng-show="myForm.input.$error.required">
20969 <span class="error" ng-show="myForm.input.$error.datetimelocal">
20970 Not a valid date!</span>
20972 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/>
20973 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20974 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20975 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20976 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20979 <file name="protractor.js" type="protractor">
20980 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"'));
20981 var valid = element(by.binding('myForm.input.$valid'));
20982 var input = element(by.model('example.value'));
20984 // currently protractor/webdriver does not support
20985 // sending keys to all known HTML5 input controls
20986 // for various browsers (https://github.com/angular/protractor/issues/562).
20987 function setInput(val) {
20988 // set the value of the element and force validation.
20989 var scr = "var ipt = document.getElementById('exampleInput'); " +
20990 "ipt.value = '" + val + "';" +
20991 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
20992 browser.executeScript(scr);
20995 it('should initialize to model', function() {
20996 expect(value.getText()).toContain('2010-12-28T14:57:00');
20997 expect(valid.getText()).toContain('myForm.input.$valid = true');
21000 it('should be invalid if empty', function() {
21002 expect(value.getText()).toEqual('value =');
21003 expect(valid.getText()).toContain('myForm.input.$valid = false');
21006 it('should be invalid if over max', function() {
21007 setInput('2015-01-01T23:59:00');
21008 expect(value.getText()).toContain('');
21009 expect(valid.getText()).toContain('myForm.input.$valid = false');
21014 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP,
21015 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']),
21016 'yyyy-MM-ddTHH:mm:ss.sss'),
21020 * @name input[time]
21023 * Input with time validation and transformation. In browsers that do not yet support
21024 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
21025 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a
21026 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`.
21028 * The model must always be a Date object, otherwise Angular will throw an error.
21029 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
21031 * The timezone to be used to read/write the `Date` instance in the model can be defined using
21032 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
21034 * @param {string} ngModel Assignable angular expression to data-bind to.
21035 * @param {string=} name Property name of the form under which the control is published.
21036 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21037 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
21038 * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add
21039 * native HTML5 constraint validation.
21040 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21041 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
21042 * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add
21043 * native HTML5 constraint validation.
21044 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the
21045 * `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
21046 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the
21047 * `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
21048 * @param {string=} required Sets `required` validation error key if the value is not entered.
21049 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21050 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21051 * `required` when you want to data-bind to the `required` attribute.
21052 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21053 * interaction with the input element.
21056 <example name="time-input-directive" module="timeExample">
21057 <file name="index.html">
21059 angular.module('timeExample', [])
21060 .controller('DateController', ['$scope', function($scope) {
21062 value: new Date(1970, 0, 1, 14, 57, 0)
21066 <form name="myForm" ng-controller="DateController as dateCtrl">
21067 <label for="exampleInput">Pick a between 8am and 5pm:</label>
21068 <input type="time" id="exampleInput" name="input" ng-model="example.value"
21069 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required />
21071 <span class="error" ng-show="myForm.input.$error.required">
21073 <span class="error" ng-show="myForm.input.$error.time">
21074 Not a valid date!</span>
21076 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/>
21077 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21078 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21079 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21080 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21083 <file name="protractor.js" type="protractor">
21084 var value = element(by.binding('example.value | date: "HH:mm:ss"'));
21085 var valid = element(by.binding('myForm.input.$valid'));
21086 var input = element(by.model('example.value'));
21088 // currently protractor/webdriver does not support
21089 // sending keys to all known HTML5 input controls
21090 // for various browsers (https://github.com/angular/protractor/issues/562).
21091 function setInput(val) {
21092 // set the value of the element and force validation.
21093 var scr = "var ipt = document.getElementById('exampleInput'); " +
21094 "ipt.value = '" + val + "';" +
21095 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21096 browser.executeScript(scr);
21099 it('should initialize to model', function() {
21100 expect(value.getText()).toContain('14:57:00');
21101 expect(valid.getText()).toContain('myForm.input.$valid = true');
21104 it('should be invalid if empty', function() {
21106 expect(value.getText()).toEqual('value =');
21107 expect(valid.getText()).toContain('myForm.input.$valid = false');
21110 it('should be invalid if over max', function() {
21111 setInput('23:59:00');
21112 expect(value.getText()).toContain('');
21113 expect(valid.getText()).toContain('myForm.input.$valid = false');
21118 'time': createDateInputType('time', TIME_REGEXP,
21119 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']),
21124 * @name input[week]
21127 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support
21128 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
21129 * week format (yyyy-W##), for example: `2013-W02`.
21131 * The model must always be a Date object, otherwise Angular will throw an error.
21132 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
21134 * The timezone to be used to read/write the `Date` instance in the model can be defined using
21135 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
21137 * @param {string} ngModel Assignable angular expression to data-bind to.
21138 * @param {string=} name Property name of the form under which the control is published.
21139 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21140 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
21141 * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add
21142 * native HTML5 constraint validation.
21143 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21144 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
21145 * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add
21146 * native HTML5 constraint validation.
21147 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
21148 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
21149 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
21150 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
21151 * @param {string=} required Sets `required` validation error key if the value is not entered.
21152 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21153 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21154 * `required` when you want to data-bind to the `required` attribute.
21155 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21156 * interaction with the input element.
21159 <example name="week-input-directive" module="weekExample">
21160 <file name="index.html">
21162 angular.module('weekExample', [])
21163 .controller('DateController', ['$scope', function($scope) {
21165 value: new Date(2013, 0, 3)
21169 <form name="myForm" ng-controller="DateController as dateCtrl">
21170 <label>Pick a date between in 2013:
21171 <input id="exampleInput" type="week" name="input" ng-model="example.value"
21172 placeholder="YYYY-W##" min="2012-W32"
21173 max="2013-W52" required />
21176 <span class="error" ng-show="myForm.input.$error.required">
21178 <span class="error" ng-show="myForm.input.$error.week">
21179 Not a valid date!</span>
21181 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/>
21182 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21183 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21184 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21185 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21188 <file name="protractor.js" type="protractor">
21189 var value = element(by.binding('example.value | date: "yyyy-Www"'));
21190 var valid = element(by.binding('myForm.input.$valid'));
21191 var input = element(by.model('example.value'));
21193 // currently protractor/webdriver does not support
21194 // sending keys to all known HTML5 input controls
21195 // for various browsers (https://github.com/angular/protractor/issues/562).
21196 function setInput(val) {
21197 // set the value of the element and force validation.
21198 var scr = "var ipt = document.getElementById('exampleInput'); " +
21199 "ipt.value = '" + val + "';" +
21200 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21201 browser.executeScript(scr);
21204 it('should initialize to model', function() {
21205 expect(value.getText()).toContain('2013-W01');
21206 expect(valid.getText()).toContain('myForm.input.$valid = true');
21209 it('should be invalid if empty', function() {
21211 expect(value.getText()).toEqual('value =');
21212 expect(valid.getText()).toContain('myForm.input.$valid = false');
21215 it('should be invalid if over max', function() {
21216 setInput('2015-W01');
21217 expect(value.getText()).toContain('');
21218 expect(valid.getText()).toContain('myForm.input.$valid = false');
21223 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'),
21227 * @name input[month]
21230 * Input with month validation and transformation. In browsers that do not yet support
21231 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
21232 * month format (yyyy-MM), for example: `2009-01`.
21234 * The model must always be a Date object, otherwise Angular will throw an error.
21235 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
21236 * If the model is not set to the first of the month, the next view to model update will set it
21237 * to the first of the month.
21239 * The timezone to be used to read/write the `Date` instance in the model can be defined using
21240 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
21242 * @param {string} ngModel Assignable angular expression to data-bind to.
21243 * @param {string=} name Property name of the form under which the control is published.
21244 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21245 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
21246 * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add
21247 * native HTML5 constraint validation.
21248 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21249 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
21250 * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add
21251 * native HTML5 constraint validation.
21252 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
21253 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
21254 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
21255 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
21257 * @param {string=} required Sets `required` validation error key if the value is not entered.
21258 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21259 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21260 * `required` when you want to data-bind to the `required` attribute.
21261 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21262 * interaction with the input element.
21265 <example name="month-input-directive" module="monthExample">
21266 <file name="index.html">
21268 angular.module('monthExample', [])
21269 .controller('DateController', ['$scope', function($scope) {
21271 value: new Date(2013, 9, 1)
21275 <form name="myForm" ng-controller="DateController as dateCtrl">
21276 <label for="exampleInput">Pick a month in 2013:</label>
21277 <input id="exampleInput" type="month" name="input" ng-model="example.value"
21278 placeholder="yyyy-MM" min="2013-01" max="2013-12" required />
21280 <span class="error" ng-show="myForm.input.$error.required">
21282 <span class="error" ng-show="myForm.input.$error.month">
21283 Not a valid month!</span>
21285 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/>
21286 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21287 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21288 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21289 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21292 <file name="protractor.js" type="protractor">
21293 var value = element(by.binding('example.value | date: "yyyy-MM"'));
21294 var valid = element(by.binding('myForm.input.$valid'));
21295 var input = element(by.model('example.value'));
21297 // currently protractor/webdriver does not support
21298 // sending keys to all known HTML5 input controls
21299 // for various browsers (https://github.com/angular/protractor/issues/562).
21300 function setInput(val) {
21301 // set the value of the element and force validation.
21302 var scr = "var ipt = document.getElementById('exampleInput'); " +
21303 "ipt.value = '" + val + "';" +
21304 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21305 browser.executeScript(scr);
21308 it('should initialize to model', function() {
21309 expect(value.getText()).toContain('2013-10');
21310 expect(valid.getText()).toContain('myForm.input.$valid = true');
21313 it('should be invalid if empty', function() {
21315 expect(value.getText()).toEqual('value =');
21316 expect(valid.getText()).toContain('myForm.input.$valid = false');
21319 it('should be invalid if over max', function() {
21320 setInput('2015-01');
21321 expect(value.getText()).toContain('');
21322 expect(valid.getText()).toContain('myForm.input.$valid = false');
21327 'month': createDateInputType('month', MONTH_REGEXP,
21328 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']),
21333 * @name input[number]
21336 * Text input with number validation and transformation. Sets the `number` validation
21337 * error if not a valid number.
21339 * <div class="alert alert-warning">
21340 * The model must always be of type `number` otherwise Angular will throw an error.
21341 * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt}
21342 * error docs for more information and an example of how to convert your model if necessary.
21345 * ## Issues with HTML5 constraint validation
21347 * In browsers that follow the
21348 * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29),
21349 * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}.
21350 * If a non-number is entered in the input, the browser will report the value as an empty string,
21351 * which means the view / model values in `ngModel` and subsequently the scope value
21352 * will also be an empty string.
21355 * @param {string} ngModel Assignable angular expression to data-bind to.
21356 * @param {string=} name Property name of the form under which the control is published.
21357 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21358 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21359 * @param {string=} required Sets `required` validation error key if the value is not entered.
21360 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21361 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21362 * `required` when you want to data-bind to the `required` attribute.
21363 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21365 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21366 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21368 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21369 * that contains the regular expression body that will be converted to a regular expression
21370 * as in the ngPattern directive.
21371 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21372 * a RegExp found by evaluating the Angular expression given in the attribute value.
21373 * If the expression evaluates to a RegExp object, then this is used directly.
21374 * If the expression evaluates to a string, then it will be converted to a RegExp
21375 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21376 * `new RegExp('^abc$')`.<br />
21377 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21378 * start at the index of the last search's match, thus not taking the whole input value into
21380 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21381 * interaction with the input element.
21384 <example name="number-input-directive" module="numberExample">
21385 <file name="index.html">
21387 angular.module('numberExample', [])
21388 .controller('ExampleController', ['$scope', function($scope) {
21394 <form name="myForm" ng-controller="ExampleController">
21396 <input type="number" name="input" ng-model="example.value"
21397 min="0" max="99" required>
21400 <span class="error" ng-show="myForm.input.$error.required">
21402 <span class="error" ng-show="myForm.input.$error.number">
21403 Not valid number!</span>
21405 <tt>value = {{example.value}}</tt><br/>
21406 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21407 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21408 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21409 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21412 <file name="protractor.js" type="protractor">
21413 var value = element(by.binding('example.value'));
21414 var valid = element(by.binding('myForm.input.$valid'));
21415 var input = element(by.model('example.value'));
21417 it('should initialize to model', function() {
21418 expect(value.getText()).toContain('12');
21419 expect(valid.getText()).toContain('true');
21422 it('should be invalid if empty', function() {
21424 input.sendKeys('');
21425 expect(value.getText()).toEqual('value =');
21426 expect(valid.getText()).toContain('false');
21429 it('should be invalid if over max', function() {
21431 input.sendKeys('123');
21432 expect(value.getText()).toEqual('value =');
21433 expect(valid.getText()).toContain('false');
21438 'number': numberInputType,
21446 * Text input with URL validation. Sets the `url` validation error key if the content is not a
21449 * <div class="alert alert-warning">
21450 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex
21451 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify
21452 * the built-in validators (see the {@link guide/forms Forms guide})
21455 * @param {string} ngModel Assignable angular expression to data-bind to.
21456 * @param {string=} name Property name of the form under which the control is published.
21457 * @param {string=} required Sets `required` validation error key if the value is not entered.
21458 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21459 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21460 * `required` when you want to data-bind to the `required` attribute.
21461 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21463 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21464 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21466 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21467 * that contains the regular expression body that will be converted to a regular expression
21468 * as in the ngPattern directive.
21469 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21470 * a RegExp found by evaluating the Angular expression given in the attribute value.
21471 * If the expression evaluates to a RegExp object, then this is used directly.
21472 * If the expression evaluates to a string, then it will be converted to a RegExp
21473 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21474 * `new RegExp('^abc$')`.<br />
21475 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21476 * start at the index of the last search's match, thus not taking the whole input value into
21478 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21479 * interaction with the input element.
21482 <example name="url-input-directive" module="urlExample">
21483 <file name="index.html">
21485 angular.module('urlExample', [])
21486 .controller('ExampleController', ['$scope', function($scope) {
21488 text: 'http://google.com'
21492 <form name="myForm" ng-controller="ExampleController">
21494 <input type="url" name="input" ng-model="url.text" required>
21497 <span class="error" ng-show="myForm.input.$error.required">
21499 <span class="error" ng-show="myForm.input.$error.url">
21500 Not valid url!</span>
21502 <tt>text = {{url.text}}</tt><br/>
21503 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21504 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21505 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21506 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21507 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/>
21510 <file name="protractor.js" type="protractor">
21511 var text = element(by.binding('url.text'));
21512 var valid = element(by.binding('myForm.input.$valid'));
21513 var input = element(by.model('url.text'));
21515 it('should initialize to model', function() {
21516 expect(text.getText()).toContain('http://google.com');
21517 expect(valid.getText()).toContain('true');
21520 it('should be invalid if empty', function() {
21522 input.sendKeys('');
21524 expect(text.getText()).toEqual('text =');
21525 expect(valid.getText()).toContain('false');
21528 it('should be invalid if not url', function() {
21530 input.sendKeys('box');
21532 expect(valid.getText()).toContain('false');
21537 'url': urlInputType,
21542 * @name input[email]
21545 * Text input with email validation. Sets the `email` validation error key if not a valid email
21548 * <div class="alert alert-warning">
21549 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex
21550 * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can
21551 * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide})
21554 * @param {string} ngModel Assignable angular expression to data-bind to.
21555 * @param {string=} name Property name of the form under which the control is published.
21556 * @param {string=} required Sets `required` validation error key if the value is not entered.
21557 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21558 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21559 * `required` when you want to data-bind to the `required` attribute.
21560 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21562 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21563 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21565 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21566 * that contains the regular expression body that will be converted to a regular expression
21567 * as in the ngPattern directive.
21568 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21569 * a RegExp found by evaluating the Angular expression given in the attribute value.
21570 * If the expression evaluates to a RegExp object, then this is used directly.
21571 * If the expression evaluates to a string, then it will be converted to a RegExp
21572 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21573 * `new RegExp('^abc$')`.<br />
21574 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21575 * start at the index of the last search's match, thus not taking the whole input value into
21577 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21578 * interaction with the input element.
21581 <example name="email-input-directive" module="emailExample">
21582 <file name="index.html">
21584 angular.module('emailExample', [])
21585 .controller('ExampleController', ['$scope', function($scope) {
21587 text: 'me@example.com'
21591 <form name="myForm" ng-controller="ExampleController">
21593 <input type="email" name="input" ng-model="email.text" required>
21596 <span class="error" ng-show="myForm.input.$error.required">
21598 <span class="error" ng-show="myForm.input.$error.email">
21599 Not valid email!</span>
21601 <tt>text = {{email.text}}</tt><br/>
21602 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21603 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21604 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21605 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21606 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/>
21609 <file name="protractor.js" type="protractor">
21610 var text = element(by.binding('email.text'));
21611 var valid = element(by.binding('myForm.input.$valid'));
21612 var input = element(by.model('email.text'));
21614 it('should initialize to model', function() {
21615 expect(text.getText()).toContain('me@example.com');
21616 expect(valid.getText()).toContain('true');
21619 it('should be invalid if empty', function() {
21621 input.sendKeys('');
21622 expect(text.getText()).toEqual('text =');
21623 expect(valid.getText()).toContain('false');
21626 it('should be invalid if not email', function() {
21628 input.sendKeys('xxx');
21630 expect(valid.getText()).toContain('false');
21635 'email': emailInputType,
21640 * @name input[radio]
21643 * HTML radio button.
21645 * @param {string} ngModel Assignable angular expression to data-bind to.
21646 * @param {string} value The value to which the `ngModel` expression should be set when selected.
21647 * Note that `value` only supports `string` values, i.e. the scope model needs to be a string,
21648 * too. Use `ngValue` if you need complex models (`number`, `object`, ...).
21649 * @param {string=} name Property name of the form under which the control is published.
21650 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21651 * interaction with the input element.
21652 * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio
21653 * is selected. Should be used instead of the `value` attribute if you need
21654 * a non-string `ngModel` (`boolean`, `array`, ...).
21657 <example name="radio-input-directive" module="radioExample">
21658 <file name="index.html">
21660 angular.module('radioExample', [])
21661 .controller('ExampleController', ['$scope', function($scope) {
21665 $scope.specialValue = {
21671 <form name="myForm" ng-controller="ExampleController">
21673 <input type="radio" ng-model="color.name" value="red">
21677 <input type="radio" ng-model="color.name" ng-value="specialValue">
21681 <input type="radio" ng-model="color.name" value="blue">
21684 <tt>color = {{color.name | json}}</tt><br/>
21686 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
21688 <file name="protractor.js" type="protractor">
21689 it('should change state', function() {
21690 var color = element(by.binding('color.name'));
21692 expect(color.getText()).toContain('blue');
21694 element.all(by.model('color.name')).get(0).click();
21696 expect(color.getText()).toContain('red');
21701 'radio': radioInputType,
21706 * @name input[checkbox]
21711 * @param {string} ngModel Assignable angular expression to data-bind to.
21712 * @param {string=} name Property name of the form under which the control is published.
21713 * @param {expression=} ngTrueValue The value to which the expression should be set when selected.
21714 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected.
21715 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21716 * interaction with the input element.
21719 <example name="checkbox-input-directive" module="checkboxExample">
21720 <file name="index.html">
21722 angular.module('checkboxExample', [])
21723 .controller('ExampleController', ['$scope', function($scope) {
21724 $scope.checkboxModel = {
21730 <form name="myForm" ng-controller="ExampleController">
21732 <input type="checkbox" ng-model="checkboxModel.value1">
21735 <input type="checkbox" ng-model="checkboxModel.value2"
21736 ng-true-value="'YES'" ng-false-value="'NO'">
21738 <tt>value1 = {{checkboxModel.value1}}</tt><br/>
21739 <tt>value2 = {{checkboxModel.value2}}</tt><br/>
21742 <file name="protractor.js" type="protractor">
21743 it('should change state', function() {
21744 var value1 = element(by.binding('checkboxModel.value1'));
21745 var value2 = element(by.binding('checkboxModel.value2'));
21747 expect(value1.getText()).toContain('true');
21748 expect(value2.getText()).toContain('YES');
21750 element(by.model('checkboxModel.value1')).click();
21751 element(by.model('checkboxModel.value2')).click();
21753 expect(value1.getText()).toContain('false');
21754 expect(value2.getText()).toContain('NO');
21759 'checkbox': checkboxInputType,
21768 function stringBasedInputType(ctrl) {
21769 ctrl.$formatters.push(function(value) {
21770 return ctrl.$isEmpty(value) ? value : value.toString();
21774 function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
21775 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
21776 stringBasedInputType(ctrl);
21779 function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
21780 var type = lowercase(element[0].type);
21782 // In composition mode, users are still inputing intermediate text buffer,
21783 // hold the listener until composition is done.
21784 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
21785 if (!$sniffer.android) {
21786 var composing = false;
21788 element.on('compositionstart', function(data) {
21792 element.on('compositionend', function() {
21798 var listener = function(ev) {
21800 $browser.defer.cancel(timeout);
21803 if (composing) return;
21804 var value = element.val(),
21805 event = ev && ev.type;
21807 // By default we will trim the value
21808 // If the attribute ng-trim exists we will avoid trimming
21809 // If input type is 'password', the value is never trimmed
21810 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) {
21811 value = trim(value);
21814 // If a control is suffering from bad input (due to native validators), browsers discard its
21815 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the
21816 // control's value is the same empty value twice in a row.
21817 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) {
21818 ctrl.$setViewValue(value, event);
21822 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the
21823 // input event on backspace, delete or cut
21824 if ($sniffer.hasEvent('input')) {
21825 element.on('input', listener);
21829 var deferListener = function(ev, input, origValue) {
21831 timeout = $browser.defer(function() {
21833 if (!input || input.value !== origValue) {
21840 element.on('keydown', function(event) {
21841 var key = event.keyCode;
21844 // command modifiers arrows
21845 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
21847 deferListener(event, this, this.value);
21850 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
21851 if ($sniffer.hasEvent('paste')) {
21852 element.on('paste cut', deferListener);
21856 // if user paste into input using mouse on older browser
21857 // or form autocomplete on newer browser, we need "change" event to catch it
21858 element.on('change', listener);
21860 ctrl.$render = function() {
21861 // Workaround for Firefox validation #12102.
21862 var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue;
21863 if (element.val() !== value) {
21864 element.val(value);
21869 function weekParser(isoWeek, existingDate) {
21870 if (isDate(isoWeek)) {
21874 if (isString(isoWeek)) {
21875 WEEK_REGEXP.lastIndex = 0;
21876 var parts = WEEK_REGEXP.exec(isoWeek);
21878 var year = +parts[1],
21884 firstThurs = getFirstThursdayOfYear(year),
21885 addDays = (week - 1) * 7;
21887 if (existingDate) {
21888 hours = existingDate.getHours();
21889 minutes = existingDate.getMinutes();
21890 seconds = existingDate.getSeconds();
21891 milliseconds = existingDate.getMilliseconds();
21894 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds);
21901 function createDateParser(regexp, mapping) {
21902 return function(iso, date) {
21909 if (isString(iso)) {
21910 // When a date is JSON'ified to wraps itself inside of an extra
21911 // set of double quotes. This makes the date parsing code unable
21912 // to match the date string and parse it as a date.
21913 if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') {
21914 iso = iso.substring(1, iso.length - 1);
21916 if (ISO_DATE_REGEXP.test(iso)) {
21917 return new Date(iso);
21919 regexp.lastIndex = 0;
21920 parts = regexp.exec(iso);
21926 yyyy: date.getFullYear(),
21927 MM: date.getMonth() + 1,
21928 dd: date.getDate(),
21929 HH: date.getHours(),
21930 mm: date.getMinutes(),
21931 ss: date.getSeconds(),
21932 sss: date.getMilliseconds() / 1000
21935 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 };
21938 forEach(parts, function(part, index) {
21939 if (index < mapping.length) {
21940 map[mapping[index]] = +part;
21943 return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0);
21951 function createDateInputType(type, regexp, parseDate, format) {
21952 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
21953 badInputChecker(scope, element, attr, ctrl);
21954 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
21955 var timezone = ctrl && ctrl.$options && ctrl.$options.timezone;
21958 ctrl.$$parserName = type;
21959 ctrl.$parsers.push(function(value) {
21960 if (ctrl.$isEmpty(value)) return null;
21961 if (regexp.test(value)) {
21962 // Note: We cannot read ctrl.$modelValue, as there might be a different
21963 // parser/formatter in the processing chain so that the model
21964 // contains some different data format!
21965 var parsedDate = parseDate(value, previousDate);
21967 parsedDate = convertTimezoneToLocal(parsedDate, timezone);
21974 ctrl.$formatters.push(function(value) {
21975 if (value && !isDate(value)) {
21976 throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
21978 if (isValidDate(value)) {
21979 previousDate = value;
21980 if (previousDate && timezone) {
21981 previousDate = convertTimezoneToLocal(previousDate, timezone, true);
21983 return $filter('date')(value, format, timezone);
21985 previousDate = null;
21990 if (isDefined(attr.min) || attr.ngMin) {
21992 ctrl.$validators.min = function(value) {
21993 return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal;
21995 attr.$observe('min', function(val) {
21996 minVal = parseObservedDateValue(val);
22001 if (isDefined(attr.max) || attr.ngMax) {
22003 ctrl.$validators.max = function(value) {
22004 return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal;
22006 attr.$observe('max', function(val) {
22007 maxVal = parseObservedDateValue(val);
22012 function isValidDate(value) {
22013 // Invalid Date: getTime() returns NaN
22014 return value && !(value.getTime && value.getTime() !== value.getTime());
22017 function parseObservedDateValue(val) {
22018 return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val;
22023 function badInputChecker(scope, element, attr, ctrl) {
22024 var node = element[0];
22025 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity);
22026 if (nativeValidation) {
22027 ctrl.$parsers.push(function(value) {
22028 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {};
22029 // Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430):
22030 // - also sets validity.badInput (should only be validity.typeMismatch).
22031 // - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email)
22032 // - can ignore this case as we can still read out the erroneous email...
22033 return validity.badInput && !validity.typeMismatch ? undefined : value;
22038 function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
22039 badInputChecker(scope, element, attr, ctrl);
22040 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
22042 ctrl.$$parserName = 'number';
22043 ctrl.$parsers.push(function(value) {
22044 if (ctrl.$isEmpty(value)) return null;
22045 if (NUMBER_REGEXP.test(value)) return parseFloat(value);
22049 ctrl.$formatters.push(function(value) {
22050 if (!ctrl.$isEmpty(value)) {
22051 if (!isNumber(value)) {
22052 throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value);
22054 value = value.toString();
22059 if (isDefined(attr.min) || attr.ngMin) {
22061 ctrl.$validators.min = function(value) {
22062 return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal;
22065 attr.$observe('min', function(val) {
22066 if (isDefined(val) && !isNumber(val)) {
22067 val = parseFloat(val, 10);
22069 minVal = isNumber(val) && !isNaN(val) ? val : undefined;
22070 // TODO(matsko): implement validateLater to reduce number of validations
22075 if (isDefined(attr.max) || attr.ngMax) {
22077 ctrl.$validators.max = function(value) {
22078 return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal;
22081 attr.$observe('max', function(val) {
22082 if (isDefined(val) && !isNumber(val)) {
22083 val = parseFloat(val, 10);
22085 maxVal = isNumber(val) && !isNaN(val) ? val : undefined;
22086 // TODO(matsko): implement validateLater to reduce number of validations
22092 function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
22093 // Note: no badInputChecker here by purpose as `url` is only a validation
22094 // in browsers, i.e. we can always read out input.value even if it is not valid!
22095 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
22096 stringBasedInputType(ctrl);
22098 ctrl.$$parserName = 'url';
22099 ctrl.$validators.url = function(modelValue, viewValue) {
22100 var value = modelValue || viewValue;
22101 return ctrl.$isEmpty(value) || URL_REGEXP.test(value);
22105 function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
22106 // Note: no badInputChecker here by purpose as `url` is only a validation
22107 // in browsers, i.e. we can always read out input.value even if it is not valid!
22108 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
22109 stringBasedInputType(ctrl);
22111 ctrl.$$parserName = 'email';
22112 ctrl.$validators.email = function(modelValue, viewValue) {
22113 var value = modelValue || viewValue;
22114 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value);
22118 function radioInputType(scope, element, attr, ctrl) {
22119 // make the name unique, if not defined
22120 if (isUndefined(attr.name)) {
22121 element.attr('name', nextUid());
22124 var listener = function(ev) {
22125 if (element[0].checked) {
22126 ctrl.$setViewValue(attr.value, ev && ev.type);
22130 element.on('click', listener);
22132 ctrl.$render = function() {
22133 var value = attr.value;
22134 element[0].checked = (value == ctrl.$viewValue);
22137 attr.$observe('value', ctrl.$render);
22140 function parseConstantExpr($parse, context, name, expression, fallback) {
22142 if (isDefined(expression)) {
22143 parseFn = $parse(expression);
22144 if (!parseFn.constant) {
22145 throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' +
22146 '`{1}`.', name, expression);
22148 return parseFn(context);
22153 function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
22154 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true);
22155 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false);
22157 var listener = function(ev) {
22158 ctrl.$setViewValue(element[0].checked, ev && ev.type);
22161 element.on('click', listener);
22163 ctrl.$render = function() {
22164 element[0].checked = ctrl.$viewValue;
22167 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false`
22168 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert
22169 // it to a boolean.
22170 ctrl.$isEmpty = function(value) {
22171 return value === false;
22174 ctrl.$formatters.push(function(value) {
22175 return equals(value, trueValue);
22178 ctrl.$parsers.push(function(value) {
22179 return value ? trueValue : falseValue;
22190 * HTML textarea element control with angular data-binding. The data-binding and validation
22191 * properties of this element are exactly the same as those of the
22192 * {@link ng.directive:input input element}.
22194 * @param {string} ngModel Assignable angular expression to data-bind to.
22195 * @param {string=} name Property name of the form under which the control is published.
22196 * @param {string=} required Sets `required` validation error key if the value is not entered.
22197 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
22198 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
22199 * `required` when you want to data-bind to the `required` attribute.
22200 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
22202 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
22203 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
22205 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
22206 * a RegExp found by evaluating the Angular expression given in the attribute value.
22207 * If the expression evaluates to a RegExp object, then this is used directly.
22208 * If the expression evaluates to a string, then it will be converted to a RegExp
22209 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
22210 * `new RegExp('^abc$')`.<br />
22211 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
22212 * start at the index of the last search's match, thus not taking the whole input value into
22214 * @param {string=} ngChange Angular expression to be executed when input changes due to user
22215 * interaction with the input element.
22216 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
22226 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding,
22227 * input state control, and validation.
22228 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers.
22230 * <div class="alert alert-warning">
22231 * **Note:** Not every feature offered is available for all input types.
22232 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`.
22235 * @param {string} ngModel Assignable angular expression to data-bind to.
22236 * @param {string=} name Property name of the form under which the control is published.
22237 * @param {string=} required Sets `required` validation error key if the value is not entered.
22238 * @param {boolean=} ngRequired Sets `required` attribute if set to true
22239 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
22241 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
22242 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
22244 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
22245 * a RegExp found by evaluating the Angular expression given in the attribute value.
22246 * If the expression evaluates to a RegExp object, then this is used directly.
22247 * If the expression evaluates to a string, then it will be converted to a RegExp
22248 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
22249 * `new RegExp('^abc$')`.<br />
22250 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
22251 * start at the index of the last search's match, thus not taking the whole input value into
22253 * @param {string=} ngChange Angular expression to be executed when input changes due to user
22254 * interaction with the input element.
22255 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
22256 * This parameter is ignored for input[type=password] controls, which will never trim the
22260 <example name="input-directive" module="inputExample">
22261 <file name="index.html">
22263 angular.module('inputExample', [])
22264 .controller('ExampleController', ['$scope', function($scope) {
22265 $scope.user = {name: 'guest', last: 'visitor'};
22268 <div ng-controller="ExampleController">
22269 <form name="myForm">
22272 <input type="text" name="userName" ng-model="user.name" required>
22275 <span class="error" ng-show="myForm.userName.$error.required">
22280 <input type="text" name="lastName" ng-model="user.last"
22281 ng-minlength="3" ng-maxlength="10">
22284 <span class="error" ng-show="myForm.lastName.$error.minlength">
22286 <span class="error" ng-show="myForm.lastName.$error.maxlength">
22291 <tt>user = {{user}}</tt><br/>
22292 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/>
22293 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/>
22294 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/>
22295 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/>
22296 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
22297 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
22298 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/>
22299 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/>
22302 <file name="protractor.js" type="protractor">
22303 var user = element(by.exactBinding('user'));
22304 var userNameValid = element(by.binding('myForm.userName.$valid'));
22305 var lastNameValid = element(by.binding('myForm.lastName.$valid'));
22306 var lastNameError = element(by.binding('myForm.lastName.$error'));
22307 var formValid = element(by.binding('myForm.$valid'));
22308 var userNameInput = element(by.model('user.name'));
22309 var userLastInput = element(by.model('user.last'));
22311 it('should initialize to model', function() {
22312 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}');
22313 expect(userNameValid.getText()).toContain('true');
22314 expect(formValid.getText()).toContain('true');
22317 it('should be invalid if empty when required', function() {
22318 userNameInput.clear();
22319 userNameInput.sendKeys('');
22321 expect(user.getText()).toContain('{"last":"visitor"}');
22322 expect(userNameValid.getText()).toContain('false');
22323 expect(formValid.getText()).toContain('false');
22326 it('should be valid if empty when min length is set', function() {
22327 userLastInput.clear();
22328 userLastInput.sendKeys('');
22330 expect(user.getText()).toContain('{"name":"guest","last":""}');
22331 expect(lastNameValid.getText()).toContain('true');
22332 expect(formValid.getText()).toContain('true');
22335 it('should be invalid if less than required min length', function() {
22336 userLastInput.clear();
22337 userLastInput.sendKeys('xx');
22339 expect(user.getText()).toContain('{"name":"guest"}');
22340 expect(lastNameValid.getText()).toContain('false');
22341 expect(lastNameError.getText()).toContain('minlength');
22342 expect(formValid.getText()).toContain('false');
22345 it('should be invalid if longer than max length', function() {
22346 userLastInput.clear();
22347 userLastInput.sendKeys('some ridiculously long name');
22349 expect(user.getText()).toContain('{"name":"guest"}');
22350 expect(lastNameValid.getText()).toContain('false');
22351 expect(lastNameError.getText()).toContain('maxlength');
22352 expect(formValid.getText()).toContain('false');
22357 var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
22358 function($browser, $sniffer, $filter, $parse) {
22361 require: ['?ngModel'],
22363 pre: function(scope, element, attr, ctrls) {
22365 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
22366 $browser, $filter, $parse);
22375 var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
22381 * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`},
22382 * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to
22385 * `ngValue` is useful when dynamically generating lists of radio buttons using
22386 * {@link ngRepeat `ngRepeat`}, as shown below.
22388 * Likewise, `ngValue` can be used to generate `<option>` elements for
22389 * the {@link select `select`} element. In that case however, only strings are supported
22390 * for the `value `attribute, so the resulting `ngModel` will always be a string.
22391 * Support for `select` models with non-string values is available via `ngOptions`.
22394 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute
22395 * of the `input` element
22398 <example name="ngValue-directive" module="valueExample">
22399 <file name="index.html">
22401 angular.module('valueExample', [])
22402 .controller('ExampleController', ['$scope', function($scope) {
22403 $scope.names = ['pizza', 'unicorns', 'robots'];
22404 $scope.my = { favorite: 'unicorns' };
22407 <form ng-controller="ExampleController">
22408 <h2>Which is your favorite?</h2>
22409 <label ng-repeat="name in names" for="{{name}}">
22411 <input type="radio"
22412 ng-model="my.favorite"
22417 <div>You chose {{my.favorite}}</div>
22420 <file name="protractor.js" type="protractor">
22421 var favorite = element(by.binding('my.favorite'));
22423 it('should initialize to model', function() {
22424 expect(favorite.getText()).toContain('unicorns');
22426 it('should bind the values to the inputs', function() {
22427 element.all(by.model('my.favorite')).get(0).click();
22428 expect(favorite.getText()).toContain('pizza');
22433 var ngValueDirective = function() {
22437 compile: function(tpl, tplAttr) {
22438 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) {
22439 return function ngValueConstantLink(scope, elm, attr) {
22440 attr.$set('value', scope.$eval(attr.ngValue));
22443 return function ngValueLink(scope, elm, attr) {
22444 scope.$watch(attr.ngValue, function valueWatchAction(value) {
22445 attr.$set('value', value);
22459 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element
22460 * with the value of a given expression, and to update the text content when the value of that
22461 * expression changes.
22463 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like
22464 * `{{ expression }}` which is similar but less verbose.
22466 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily
22467 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an
22468 * element attribute, it makes the bindings invisible to the user while the page is loading.
22470 * An alternative solution to this problem would be using the
22471 * {@link ng.directive:ngCloak ngCloak} directive.
22475 * @param {expression} ngBind {@link guide/expression Expression} to evaluate.
22478 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly.
22479 <example module="bindExample">
22480 <file name="index.html">
22482 angular.module('bindExample', [])
22483 .controller('ExampleController', ['$scope', function($scope) {
22484 $scope.name = 'Whirled';
22487 <div ng-controller="ExampleController">
22488 <label>Enter name: <input type="text" ng-model="name"></label><br>
22489 Hello <span ng-bind="name"></span>!
22492 <file name="protractor.js" type="protractor">
22493 it('should check ng-bind', function() {
22494 var nameInput = element(by.model('name'));
22496 expect(element(by.binding('name')).getText()).toBe('Whirled');
22498 nameInput.sendKeys('world');
22499 expect(element(by.binding('name')).getText()).toBe('world');
22504 var ngBindDirective = ['$compile', function($compile) {
22507 compile: function ngBindCompile(templateElement) {
22508 $compile.$$addBindingClass(templateElement);
22509 return function ngBindLink(scope, element, attr) {
22510 $compile.$$addBindingInfo(element, attr.ngBind);
22511 element = element[0];
22512 scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
22513 element.textContent = isUndefined(value) ? '' : value;
22523 * @name ngBindTemplate
22526 * The `ngBindTemplate` directive specifies that the element
22527 * text content should be replaced with the interpolation of the template
22528 * in the `ngBindTemplate` attribute.
22529 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}`
22530 * expressions. This directive is needed since some HTML elements
22531 * (such as TITLE and OPTION) cannot contain SPAN elements.
22534 * @param {string} ngBindTemplate template of form
22535 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval.
22538 * Try it here: enter text in text box and watch the greeting change.
22539 <example module="bindExample">
22540 <file name="index.html">
22542 angular.module('bindExample', [])
22543 .controller('ExampleController', ['$scope', function($scope) {
22544 $scope.salutation = 'Hello';
22545 $scope.name = 'World';
22548 <div ng-controller="ExampleController">
22549 <label>Salutation: <input type="text" ng-model="salutation"></label><br>
22550 <label>Name: <input type="text" ng-model="name"></label><br>
22551 <pre ng-bind-template="{{salutation}} {{name}}!"></pre>
22554 <file name="protractor.js" type="protractor">
22555 it('should check ng-bind', function() {
22556 var salutationElem = element(by.binding('salutation'));
22557 var salutationInput = element(by.model('salutation'));
22558 var nameInput = element(by.model('name'));
22560 expect(salutationElem.getText()).toBe('Hello World!');
22562 salutationInput.clear();
22563 salutationInput.sendKeys('Greetings');
22565 nameInput.sendKeys('user');
22567 expect(salutationElem.getText()).toBe('Greetings user!');
22572 var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) {
22574 compile: function ngBindTemplateCompile(templateElement) {
22575 $compile.$$addBindingClass(templateElement);
22576 return function ngBindTemplateLink(scope, element, attr) {
22577 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
22578 $compile.$$addBindingInfo(element, interpolateFn.expressions);
22579 element = element[0];
22580 attr.$observe('ngBindTemplate', function(value) {
22581 element.textContent = isUndefined(value) ? '' : value;
22594 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default,
22595 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service.
22596 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link
22597 * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize}
22598 * in your module's dependencies, you need to include "angular-sanitize.js" in your application.
22600 * You may also bypass sanitization for values you know are safe. To do so, bind to
22601 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
22602 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}.
22604 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you
22605 * will have an exception (instead of an exploit.)
22608 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
22612 <example module="bindHtmlExample" deps="angular-sanitize.js">
22613 <file name="index.html">
22614 <div ng-controller="ExampleController">
22615 <p ng-bind-html="myHTML"></p>
22619 <file name="script.js">
22620 angular.module('bindHtmlExample', ['ngSanitize'])
22621 .controller('ExampleController', ['$scope', function($scope) {
22623 'I am an <code>HTML</code>string with ' +
22624 '<a href="#">links!</a> and other <em>stuff</em>';
22628 <file name="protractor.js" type="protractor">
22629 it('should check ng-bind-html', function() {
22630 expect(element(by.binding('myHTML')).getText()).toBe(
22631 'I am an HTMLstring with links! and other stuff');
22636 var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) {
22639 compile: function ngBindHtmlCompile(tElement, tAttrs) {
22640 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml);
22641 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) {
22642 return (value || '').toString();
22644 $compile.$$addBindingClass(tElement);
22646 return function ngBindHtmlLink(scope, element, attr) {
22647 $compile.$$addBindingInfo(element, attr.ngBindHtml);
22649 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() {
22650 // we re-evaluate the expr because we want a TrustedValueHolderType
22651 // for $sce, not a string
22652 element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || '');
22664 * Evaluate the given expression when the user changes the input.
22665 * The expression is evaluated immediately, unlike the JavaScript onchange event
22666 * which only triggers at the end of a change (usually, when the user leaves the
22667 * form element or presses the return key).
22669 * The `ngChange` expression is only evaluated when a change in the input value causes
22670 * a new value to be committed to the model.
22672 * It will not be evaluated:
22673 * * if the value returned from the `$parsers` transformation pipeline has not changed
22674 * * if the input has continued to be invalid since the model will stay `null`
22675 * * if the model is changed programmatically and not by a change to the input value
22678 * Note, this directive requires `ngModel` to be present.
22681 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change
22685 * <example name="ngChange-directive" module="changeExample">
22686 * <file name="index.html">
22688 * angular.module('changeExample', [])
22689 * .controller('ExampleController', ['$scope', function($scope) {
22690 * $scope.counter = 0;
22691 * $scope.change = function() {
22692 * $scope.counter++;
22696 * <div ng-controller="ExampleController">
22697 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
22698 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
22699 * <label for="ng-change-example2">Confirmed</label><br />
22700 * <tt>debug = {{confirmed}}</tt><br/>
22701 * <tt>counter = {{counter}}</tt><br/>
22704 * <file name="protractor.js" type="protractor">
22705 * var counter = element(by.binding('counter'));
22706 * var debug = element(by.binding('confirmed'));
22708 * it('should evaluate the expression if changing from view', function() {
22709 * expect(counter.getText()).toContain('0');
22711 * element(by.id('ng-change-example1')).click();
22713 * expect(counter.getText()).toContain('1');
22714 * expect(debug.getText()).toContain('true');
22717 * it('should not evaluate the expression if changing from model', function() {
22718 * element(by.id('ng-change-example2')).click();
22720 * expect(counter.getText()).toContain('0');
22721 * expect(debug.getText()).toContain('true');
22726 var ngChangeDirective = valueFn({
22728 require: 'ngModel',
22729 link: function(scope, element, attr, ctrl) {
22730 ctrl.$viewChangeListeners.push(function() {
22731 scope.$eval(attr.ngChange);
22736 function classDirective(name, selector) {
22737 name = 'ngClass' + name;
22738 return ['$animate', function($animate) {
22741 link: function(scope, element, attr) {
22744 scope.$watch(attr[name], ngClassWatchAction, true);
22746 attr.$observe('class', function(value) {
22747 ngClassWatchAction(scope.$eval(attr[name]));
22751 if (name !== 'ngClass') {
22752 scope.$watch('$index', function($index, old$index) {
22753 // jshint bitwise: false
22754 var mod = $index & 1;
22755 if (mod !== (old$index & 1)) {
22756 var classes = arrayClasses(scope.$eval(attr[name]));
22758 addClasses(classes) :
22759 removeClasses(classes);
22764 function addClasses(classes) {
22765 var newClasses = digestClassCounts(classes, 1);
22766 attr.$addClass(newClasses);
22769 function removeClasses(classes) {
22770 var newClasses = digestClassCounts(classes, -1);
22771 attr.$removeClass(newClasses);
22774 function digestClassCounts(classes, count) {
22775 // Use createMap() to prevent class assumptions involving property
22776 // names in Object.prototype
22777 var classCounts = element.data('$classCounts') || createMap();
22778 var classesToUpdate = [];
22779 forEach(classes, function(className) {
22780 if (count > 0 || classCounts[className]) {
22781 classCounts[className] = (classCounts[className] || 0) + count;
22782 if (classCounts[className] === +(count > 0)) {
22783 classesToUpdate.push(className);
22787 element.data('$classCounts', classCounts);
22788 return classesToUpdate.join(' ');
22791 function updateClasses(oldClasses, newClasses) {
22792 var toAdd = arrayDifference(newClasses, oldClasses);
22793 var toRemove = arrayDifference(oldClasses, newClasses);
22794 toAdd = digestClassCounts(toAdd, 1);
22795 toRemove = digestClassCounts(toRemove, -1);
22796 if (toAdd && toAdd.length) {
22797 $animate.addClass(element, toAdd);
22799 if (toRemove && toRemove.length) {
22800 $animate.removeClass(element, toRemove);
22804 function ngClassWatchAction(newVal) {
22805 if (selector === true || scope.$index % 2 === selector) {
22806 var newClasses = arrayClasses(newVal || []);
22808 addClasses(newClasses);
22809 } else if (!equals(newVal,oldVal)) {
22810 var oldClasses = arrayClasses(oldVal);
22811 updateClasses(oldClasses, newClasses);
22814 oldVal = shallowCopy(newVal);
22819 function arrayDifference(tokens1, tokens2) {
22823 for (var i = 0; i < tokens1.length; i++) {
22824 var token = tokens1[i];
22825 for (var j = 0; j < tokens2.length; j++) {
22826 if (token == tokens2[j]) continue outer;
22828 values.push(token);
22833 function arrayClasses(classVal) {
22835 if (isArray(classVal)) {
22836 forEach(classVal, function(v) {
22837 classes = classes.concat(arrayClasses(v));
22840 } else if (isString(classVal)) {
22841 return classVal.split(' ');
22842 } else if (isObject(classVal)) {
22843 forEach(classVal, function(v, k) {
22845 classes = classes.concat(k.split(' '));
22861 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding
22862 * an expression that represents all classes to be added.
22864 * The directive operates in three different ways, depending on which of three types the expression
22867 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class
22870 * 2. If the expression evaluates to an object, then for each key-value pair of the
22871 * object with a truthy value the corresponding key is used as a class name.
22873 * 3. If the expression evaluates to an array, each element of the array should either be a string as in
22874 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array
22875 * to give you more control over what CSS classes appear. See the code below for an example of this.
22878 * The directive won't add duplicate classes if a particular class was already set.
22880 * When the expression changes, the previously added classes are removed and only then are the
22881 * new classes added.
22884 * **add** - happens just before the class is applied to the elements
22886 * **remove** - happens just before the class is removed from the element
22889 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result
22890 * of the evaluation can be a string representing space delimited class
22891 * names, an array, or a map of class names to boolean values. In the case of a map, the
22892 * names of the properties whose values are truthy will be added as css classes to the
22895 * @example Example that demonstrates basic bindings via ngClass directive.
22897 <file name="index.html">
22898 <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p>
22900 <input type="checkbox" ng-model="deleted">
22901 deleted (apply "strike" class)
22904 <input type="checkbox" ng-model="important">
22905 important (apply "bold" class)
22908 <input type="checkbox" ng-model="error">
22909 error (apply "has-error" class)
22912 <p ng-class="style">Using String Syntax</p>
22913 <input type="text" ng-model="style"
22914 placeholder="Type: bold strike red" aria-label="Type: bold strike red">
22916 <p ng-class="[style1, style2, style3]">Using Array Syntax</p>
22917 <input ng-model="style1"
22918 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br>
22919 <input ng-model="style2"
22920 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br>
22921 <input ng-model="style3"
22922 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br>
22924 <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p>
22925 <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br>
22926 <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label>
22928 <file name="style.css">
22930 text-decoration: line-through;
22940 background-color: yellow;
22946 <file name="protractor.js" type="protractor">
22947 var ps = element.all(by.css('p'));
22949 it('should let you toggle the class', function() {
22951 expect(ps.first().getAttribute('class')).not.toMatch(/bold/);
22952 expect(ps.first().getAttribute('class')).not.toMatch(/has-error/);
22954 element(by.model('important')).click();
22955 expect(ps.first().getAttribute('class')).toMatch(/bold/);
22957 element(by.model('error')).click();
22958 expect(ps.first().getAttribute('class')).toMatch(/has-error/);
22961 it('should let you toggle string example', function() {
22962 expect(ps.get(1).getAttribute('class')).toBe('');
22963 element(by.model('style')).clear();
22964 element(by.model('style')).sendKeys('red');
22965 expect(ps.get(1).getAttribute('class')).toBe('red');
22968 it('array example should have 3 classes', function() {
22969 expect(ps.get(2).getAttribute('class')).toBe('');
22970 element(by.model('style1')).sendKeys('bold');
22971 element(by.model('style2')).sendKeys('strike');
22972 element(by.model('style3')).sendKeys('red');
22973 expect(ps.get(2).getAttribute('class')).toBe('bold strike red');
22976 it('array with map example should have 2 classes', function() {
22977 expect(ps.last().getAttribute('class')).toBe('');
22978 element(by.model('style4')).sendKeys('bold');
22979 element(by.model('warning')).click();
22980 expect(ps.last().getAttribute('class')).toBe('bold orange');
22987 The example below demonstrates how to perform animations using ngClass.
22989 <example module="ngAnimate" deps="angular-animate.js" animations="true">
22990 <file name="index.html">
22991 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'">
22992 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''">
22994 <span class="base-class" ng-class="myVar">Sample Text</span>
22996 <file name="style.css">
22998 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
23001 .base-class.my-class {
23006 <file name="protractor.js" type="protractor">
23007 it('should check ng-class', function() {
23008 expect(element(by.css('.base-class')).getAttribute('class')).not.
23009 toMatch(/my-class/);
23011 element(by.id('setbtn')).click();
23013 expect(element(by.css('.base-class')).getAttribute('class')).
23014 toMatch(/my-class/);
23016 element(by.id('clearbtn')).click();
23018 expect(element(by.css('.base-class')).getAttribute('class')).not.
23019 toMatch(/my-class/);
23025 ## ngClass and pre-existing CSS3 Transitions/Animations
23026 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure.
23027 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder
23028 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure
23029 to view the step by step details of {@link $animate#addClass $animate.addClass} and
23030 {@link $animate#removeClass $animate.removeClass}.
23032 var ngClassDirective = classDirective('', true);
23040 * The `ngClassOdd` and `ngClassEven` directives work exactly as
23041 * {@link ng.directive:ngClass ngClass}, except they work in
23042 * conjunction with `ngRepeat` and take effect only on odd (even) rows.
23044 * This directive can be applied only within the scope of an
23045 * {@link ng.directive:ngRepeat ngRepeat}.
23048 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result
23049 * of the evaluation can be a string representing space delimited class names or an array.
23053 <file name="index.html">
23054 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
23055 <li ng-repeat="name in names">
23056 <span ng-class-odd="'odd'" ng-class-even="'even'">
23062 <file name="style.css">
23070 <file name="protractor.js" type="protractor">
23071 it('should check ng-class-odd and ng-class-even', function() {
23072 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
23074 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
23080 var ngClassOddDirective = classDirective('Odd', 0);
23084 * @name ngClassEven
23088 * The `ngClassOdd` and `ngClassEven` directives work exactly as
23089 * {@link ng.directive:ngClass ngClass}, except they work in
23090 * conjunction with `ngRepeat` and take effect only on odd (even) rows.
23092 * This directive can be applied only within the scope of an
23093 * {@link ng.directive:ngRepeat ngRepeat}.
23096 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The
23097 * result of the evaluation can be a string representing space delimited class names or an array.
23101 <file name="index.html">
23102 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
23103 <li ng-repeat="name in names">
23104 <span ng-class-odd="'odd'" ng-class-even="'even'">
23105 {{name}}
23110 <file name="style.css">
23118 <file name="protractor.js" type="protractor">
23119 it('should check ng-class-odd and ng-class-even', function() {
23120 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
23122 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
23128 var ngClassEvenDirective = classDirective('Even', 1);
23136 * The `ngCloak` directive is used to prevent the Angular html template from being briefly
23137 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this
23138 * directive to avoid the undesirable flicker effect caused by the html template display.
23140 * The directive can be applied to the `<body>` element, but the preferred usage is to apply
23141 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering
23142 * of the browser view.
23144 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and
23145 * `angular.min.js`.
23146 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
23149 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
23150 * display: none !important;
23154 * When this css rule is loaded by the browser, all html elements (including their children) that
23155 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive
23156 * during the compilation of the template it deletes the `ngCloak` element attribute, making
23157 * the compiled element visible.
23159 * For the best result, the `angular.js` script must be loaded in the head section of the html
23160 * document; alternatively, the css rule above must be included in the external stylesheet of the
23167 <file name="index.html">
23168 <div id="template1" ng-cloak>{{ 'hello' }}</div>
23169 <div id="template2" class="ng-cloak">{{ 'world' }}</div>
23171 <file name="protractor.js" type="protractor">
23172 it('should remove the template directive and css class', function() {
23173 expect($('#template1').getAttribute('ng-cloak')).
23175 expect($('#template2').getAttribute('ng-cloak')).
23182 var ngCloakDirective = ngDirective({
23183 compile: function(element, attr) {
23184 attr.$set('ngCloak', undefined);
23185 element.removeClass('ng-cloak');
23191 * @name ngController
23194 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular
23195 * supports the principles behind the Model-View-Controller design pattern.
23197 * MVC components in angular:
23199 * * Model — Models are the properties of a scope; scopes are attached to the DOM where scope properties
23200 * are accessed through bindings.
23201 * * View — The template (HTML with data bindings) that is rendered into the View.
23202 * * Controller — The `ngController` directive specifies a Controller class; the class contains business
23203 * logic behind the application to decorate the scope with functions and values
23205 * Note that you can also attach controllers to the DOM by declaring it in a route definition
23206 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller
23207 * again using `ng-controller` in the template itself. This will cause the controller to be attached
23208 * and executed twice.
23213 * @param {expression} ngController Name of a constructor function registered with the current
23214 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression}
23215 * that on the current scope evaluates to a constructor function.
23217 * The controller instance can be published into a scope property by specifying
23218 * `ng-controller="as propertyName"`.
23220 * If the current `$controllerProvider` is configured to use globals (via
23221 * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may
23222 * also be the name of a globally accessible constructor function (not recommended).
23225 * Here is a simple form for editing user contact information. Adding, removing, clearing, and
23226 * greeting are methods declared on the controller (see source tab). These methods can
23227 * easily be called from the angular markup. Any changes to the data are automatically reflected
23228 * in the View without the need for a manual update.
23230 * Two different declaration styles are included below:
23232 * * one binds methods and properties directly onto the controller using `this`:
23233 * `ng-controller="SettingsController1 as settings"`
23234 * * one injects `$scope` into the controller:
23235 * `ng-controller="SettingsController2"`
23237 * The second option is more common in the Angular community, and is generally used in boilerplates
23238 * and in this guide. However, there are advantages to binding properties directly to the controller
23239 * and avoiding scope.
23241 * * Using `controller as` makes it obvious which controller you are accessing in the template when
23242 * multiple controllers apply to an element.
23243 * * If you are writing your controllers as classes you have easier access to the properties and
23244 * methods, which will appear on the scope, from inside the controller code.
23245 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal
23246 * inheritance masking primitives.
23248 * This example demonstrates the `controller as` syntax.
23250 * <example name="ngControllerAs" module="controllerAsExample">
23251 * <file name="index.html">
23252 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings">
23253 * <label>Name: <input type="text" ng-model="settings.name"/></label>
23254 * <button ng-click="settings.greet()">greet</button><br/>
23257 * <li ng-repeat="contact in settings.contacts">
23258 * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}">
23259 * <option>phone</option>
23260 * <option>email</option>
23262 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
23263 * <button ng-click="settings.clearContact(contact)">clear</button>
23264 * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button>
23266 * <li><button ng-click="settings.addContact()">add</button></li>
23270 * <file name="app.js">
23271 * angular.module('controllerAsExample', [])
23272 * .controller('SettingsController1', SettingsController1);
23274 * function SettingsController1() {
23275 * this.name = "John Smith";
23276 * this.contacts = [
23277 * {type: 'phone', value: '408 555 1212'},
23278 * {type: 'email', value: 'john.smith@example.org'} ];
23281 * SettingsController1.prototype.greet = function() {
23282 * alert(this.name);
23285 * SettingsController1.prototype.addContact = function() {
23286 * this.contacts.push({type: 'email', value: 'yourname@example.org'});
23289 * SettingsController1.prototype.removeContact = function(contactToRemove) {
23290 * var index = this.contacts.indexOf(contactToRemove);
23291 * this.contacts.splice(index, 1);
23294 * SettingsController1.prototype.clearContact = function(contact) {
23295 * contact.type = 'phone';
23296 * contact.value = '';
23299 * <file name="protractor.js" type="protractor">
23300 * it('should check controller as', function() {
23301 * var container = element(by.id('ctrl-as-exmpl'));
23302 * expect(container.element(by.model('settings.name'))
23303 * .getAttribute('value')).toBe('John Smith');
23305 * var firstRepeat =
23306 * container.element(by.repeater('contact in settings.contacts').row(0));
23307 * var secondRepeat =
23308 * container.element(by.repeater('contact in settings.contacts').row(1));
23310 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23311 * .toBe('408 555 1212');
23313 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
23314 * .toBe('john.smith@example.org');
23316 * firstRepeat.element(by.buttonText('clear')).click();
23318 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23321 * container.element(by.buttonText('add')).click();
23323 * expect(container.element(by.repeater('contact in settings.contacts').row(2))
23324 * .element(by.model('contact.value'))
23325 * .getAttribute('value'))
23326 * .toBe('yourname@example.org');
23331 * This example demonstrates the "attach to `$scope`" style of controller.
23333 * <example name="ngController" module="controllerExample">
23334 * <file name="index.html">
23335 * <div id="ctrl-exmpl" ng-controller="SettingsController2">
23336 * <label>Name: <input type="text" ng-model="name"/></label>
23337 * <button ng-click="greet()">greet</button><br/>
23340 * <li ng-repeat="contact in contacts">
23341 * <select ng-model="contact.type" id="select_{{$index}}">
23342 * <option>phone</option>
23343 * <option>email</option>
23345 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
23346 * <button ng-click="clearContact(contact)">clear</button>
23347 * <button ng-click="removeContact(contact)">X</button>
23349 * <li>[ <button ng-click="addContact()">add</button> ]</li>
23353 * <file name="app.js">
23354 * angular.module('controllerExample', [])
23355 * .controller('SettingsController2', ['$scope', SettingsController2]);
23357 * function SettingsController2($scope) {
23358 * $scope.name = "John Smith";
23359 * $scope.contacts = [
23360 * {type:'phone', value:'408 555 1212'},
23361 * {type:'email', value:'john.smith@example.org'} ];
23363 * $scope.greet = function() {
23364 * alert($scope.name);
23367 * $scope.addContact = function() {
23368 * $scope.contacts.push({type:'email', value:'yourname@example.org'});
23371 * $scope.removeContact = function(contactToRemove) {
23372 * var index = $scope.contacts.indexOf(contactToRemove);
23373 * $scope.contacts.splice(index, 1);
23376 * $scope.clearContact = function(contact) {
23377 * contact.type = 'phone';
23378 * contact.value = '';
23382 * <file name="protractor.js" type="protractor">
23383 * it('should check controller', function() {
23384 * var container = element(by.id('ctrl-exmpl'));
23386 * expect(container.element(by.model('name'))
23387 * .getAttribute('value')).toBe('John Smith');
23389 * var firstRepeat =
23390 * container.element(by.repeater('contact in contacts').row(0));
23391 * var secondRepeat =
23392 * container.element(by.repeater('contact in contacts').row(1));
23394 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23395 * .toBe('408 555 1212');
23396 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
23397 * .toBe('john.smith@example.org');
23399 * firstRepeat.element(by.buttonText('clear')).click();
23401 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23404 * container.element(by.buttonText('add')).click();
23406 * expect(container.element(by.repeater('contact in contacts').row(2))
23407 * .element(by.model('contact.value'))
23408 * .getAttribute('value'))
23409 * .toBe('yourname@example.org');
23415 var ngControllerDirective = [function() {
23431 * Angular has some features that can break certain
23432 * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules.
23434 * If you intend to implement these rules then you must tell Angular not to use these features.
23436 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps.
23439 * The following rules affect Angular:
23441 * * `unsafe-eval`: this rule forbids apps to use `eval` or `Function(string)` generated functions
23442 * (among other things). Angular makes use of this in the {@link $parse} service to provide a 30%
23443 * increase in the speed of evaluating Angular expressions.
23445 * * `unsafe-inline`: this rule forbids apps from inject custom styles into the document. Angular
23446 * makes use of this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}).
23447 * To make these directives work when a CSP rule is blocking inline styles, you must link to the
23448 * `angular-csp.css` in your HTML manually.
23450 * If you do not provide `ngCsp` then Angular tries to autodetect if CSP is blocking unsafe-eval
23451 * and automatically deactivates this feature in the {@link $parse} service. This autodetection,
23452 * however, triggers a CSP error to be logged in the console:
23455 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of
23456 * script in the following Content Security Policy directive: "default-src 'self'". Note that
23457 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
23460 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp`
23461 * directive on an element of the HTML document that appears before the `<script>` tag that loads
23462 * the `angular.js` file.
23464 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.*
23466 * You can specify which of the CSP related Angular features should be deactivated by providing
23467 * a value for the `ng-csp` attribute. The options are as follows:
23469 * * no-inline-style: this stops Angular from injecting CSS styles into the DOM
23471 * * no-unsafe-eval: this stops Angular from optimising $parse with unsafe eval of strings
23473 * You can use these values in the following combinations:
23476 * * No declaration means that Angular will assume that you can do inline styles, but it will do
23477 * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous versions
23480 * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell Angular to deactivate both inline
23481 * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous versions
23484 * * Specifying only `no-unsafe-eval` tells Angular that we must not use eval, but that we can inject
23485 * inline styles. E.g. `<body ng-csp="no-unsafe-eval">`.
23487 * * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can
23488 * run eval - no automcatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">`
23490 * * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject
23491 * styles nor use eval, which is the same as an empty: ng-csp.
23492 * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">`
23495 * This example shows how to apply the `ngCsp` directive to the `html` tag.
23498 <html ng-app ng-csp>
23504 // Note: the suffix `.csp` in the example name triggers
23505 // csp mode in our http server!
23506 <example name="example.csp" module="cspExample" ng-csp="true">
23507 <file name="index.html">
23508 <div ng-controller="MainController as ctrl">
23510 <button ng-click="ctrl.inc()" id="inc">Increment</button>
23511 <span id="counter">
23517 <button ng-click="ctrl.evil()" id="evil">Evil</button>
23518 <span id="evilError">
23524 <file name="script.js">
23525 angular.module('cspExample', [])
23526 .controller('MainController', function() {
23528 this.inc = function() {
23531 this.evil = function() {
23532 // jshint evil:true
23536 this.evilError = e.message;
23541 <file name="protractor.js" type="protractor">
23542 var util, webdriver;
23544 var incBtn = element(by.id('inc'));
23545 var counter = element(by.id('counter'));
23546 var evilBtn = element(by.id('evil'));
23547 var evilError = element(by.id('evilError'));
23549 function getAndClearSevereErrors() {
23550 return browser.manage().logs().get('browser').then(function(browserLog) {
23551 return browserLog.filter(function(logEntry) {
23552 return logEntry.level.value > webdriver.logging.Level.WARNING.value;
23557 function clearErrors() {
23558 getAndClearSevereErrors();
23561 function expectNoErrors() {
23562 getAndClearSevereErrors().then(function(filteredLog) {
23563 expect(filteredLog.length).toEqual(0);
23564 if (filteredLog.length) {
23565 console.log('browser console errors: ' + util.inspect(filteredLog));
23570 function expectError(regex) {
23571 getAndClearSevereErrors().then(function(filteredLog) {
23573 filteredLog.forEach(function(log) {
23574 if (log.message.match(regex)) {
23579 throw new Error('expected an error that matches ' + regex);
23584 beforeEach(function() {
23585 util = require('util');
23586 webdriver = require('protractor/node_modules/selenium-webdriver');
23589 // For now, we only test on Chrome,
23590 // as Safari does not load the page with Protractor's injected scripts,
23591 // and Firefox webdriver always disables content security policy (#6358)
23592 if (browser.params.browser !== 'chrome') {
23596 it('should not report errors when the page is loaded', function() {
23597 // clear errors so we are not dependent on previous tests
23599 // Need to reload the page as the page is already loaded when
23601 browser.driver.getCurrentUrl().then(function(url) {
23607 it('should evaluate expressions', function() {
23608 expect(counter.getText()).toEqual('0');
23610 expect(counter.getText()).toEqual('1');
23614 it('should throw and report an error when using "eval"', function() {
23616 expect(evilError.getText()).toMatch(/Content Security Policy/);
23617 expectError(/Content Security Policy/);
23623 // ngCsp is not implemented as a proper directive any more, because we need it be processed while we
23624 // bootstrap the system (before $parse is instantiated), for this reason we just have
23625 // the csp() fn that looks for the `ng-csp` attribute anywhere in the current doc
23632 * The ngClick directive allows you to specify custom behavior when
23633 * an element is clicked.
23637 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
23638 * click. ({@link guide/expression#-event- Event object is available as `$event`})
23642 <file name="index.html">
23643 <button ng-click="count = count + 1" ng-init="count=0">
23650 <file name="protractor.js" type="protractor">
23651 it('should check ng-click', function() {
23652 expect(element(by.binding('count')).getText()).toMatch('0');
23653 element(by.css('button')).click();
23654 expect(element(by.binding('count')).getText()).toMatch('1');
23660 * A collection of directives that allows creation of custom event handlers that are defined as
23661 * angular expressions and are compiled and executed within the current scope.
23663 var ngEventDirectives = {};
23665 // For events that might fire synchronously during DOM manipulation
23666 // we need to execute their event handlers asynchronously using $evalAsync,
23667 // so that they are not executed in an inconsistent state.
23668 var forceAsyncEvents = {
23673 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
23674 function(eventName) {
23675 var directiveName = directiveNormalize('ng-' + eventName);
23676 ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) {
23679 compile: function($element, attr) {
23680 // We expose the powerful $event object on the scope that provides access to the Window,
23681 // etc. that isn't protected by the fast paths in $parse. We explicitly request better
23682 // checks at the cost of speed since event handler expressions are not executed as
23683 // frequently as regular change detection.
23684 var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true);
23685 return function ngEventHandler(scope, element) {
23686 element.on(eventName, function(event) {
23687 var callback = function() {
23688 fn(scope, {$event:event});
23690 if (forceAsyncEvents[eventName] && $rootScope.$$phase) {
23691 scope.$evalAsync(callback);
23693 scope.$apply(callback);
23708 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event.
23712 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon
23713 * a dblclick. (The Event object is available as `$event`)
23717 <file name="index.html">
23718 <button ng-dblclick="count = count + 1" ng-init="count=0">
23719 Increment (on double click)
23729 * @name ngMousedown
23732 * The ngMousedown directive allows you to specify custom behavior on mousedown event.
23736 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon
23737 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`})
23741 <file name="index.html">
23742 <button ng-mousedown="count = count + 1" ng-init="count=0">
23743 Increment (on mouse down)
23756 * Specify custom behavior on mouseup event.
23760 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon
23761 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`})
23765 <file name="index.html">
23766 <button ng-mouseup="count = count + 1" ng-init="count=0">
23767 Increment (on mouse up)
23776 * @name ngMouseover
23779 * Specify custom behavior on mouseover event.
23783 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon
23784 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`})
23788 <file name="index.html">
23789 <button ng-mouseover="count = count + 1" ng-init="count=0">
23790 Increment (when mouse is over)
23800 * @name ngMouseenter
23803 * Specify custom behavior on mouseenter event.
23807 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon
23808 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`})
23812 <file name="index.html">
23813 <button ng-mouseenter="count = count + 1" ng-init="count=0">
23814 Increment (when mouse enters)
23824 * @name ngMouseleave
23827 * Specify custom behavior on mouseleave event.
23831 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon
23832 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`})
23836 <file name="index.html">
23837 <button ng-mouseleave="count = count + 1" ng-init="count=0">
23838 Increment (when mouse leaves)
23848 * @name ngMousemove
23851 * Specify custom behavior on mousemove event.
23855 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon
23856 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`})
23860 <file name="index.html">
23861 <button ng-mousemove="count = count + 1" ng-init="count=0">
23862 Increment (when mouse moves)
23875 * Specify custom behavior on keydown event.
23879 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon
23880 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
23884 <file name="index.html">
23885 <input ng-keydown="count = count + 1" ng-init="count=0">
23886 key down count: {{count}}
23897 * Specify custom behavior on keyup event.
23901 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon
23902 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
23906 <file name="index.html">
23907 <p>Typing in the input box below updates the key count</p>
23908 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}}
23910 <p>Typing in the input box below updates the keycode</p>
23911 <input ng-keyup="event=$event">
23912 <p>event keyCode: {{ event.keyCode }}</p>
23913 <p>event altKey: {{ event.altKey }}</p>
23924 * Specify custom behavior on keypress event.
23927 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon
23928 * keypress. ({@link guide/expression#-event- Event object is available as `$event`}
23929 * and can be interrogated for keyCode, altKey, etc.)
23933 <file name="index.html">
23934 <input ng-keypress="count = count + 1" ng-init="count=0">
23935 key press count: {{count}}
23946 * Enables binding angular expressions to onsubmit events.
23948 * Additionally it prevents the default action (which for form means sending the request to the
23949 * server and reloading the current page), but only if the form does not contain `action`,
23950 * `data-action`, or `x-action` attributes.
23952 * <div class="alert alert-warning">
23953 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and
23954 * `ngSubmit` handlers together. See the
23955 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation}
23956 * for a detailed discussion of when `ngSubmit` may be triggered.
23961 * @param {expression} ngSubmit {@link guide/expression Expression} to eval.
23962 * ({@link guide/expression#-event- Event object is available as `$event`})
23965 <example module="submitExample">
23966 <file name="index.html">
23968 angular.module('submitExample', [])
23969 .controller('ExampleController', ['$scope', function($scope) {
23971 $scope.text = 'hello';
23972 $scope.submit = function() {
23974 $scope.list.push(this.text);
23980 <form ng-submit="submit()" ng-controller="ExampleController">
23981 Enter text and hit enter:
23982 <input type="text" ng-model="text" name="text" />
23983 <input type="submit" id="submit" value="Submit" />
23984 <pre>list={{list}}</pre>
23987 <file name="protractor.js" type="protractor">
23988 it('should check ng-submit', function() {
23989 expect(element(by.binding('list')).getText()).toBe('list=[]');
23990 element(by.css('#submit')).click();
23991 expect(element(by.binding('list')).getText()).toContain('hello');
23992 expect(element(by.model('text')).getAttribute('value')).toBe('');
23994 it('should ignore empty strings', function() {
23995 expect(element(by.binding('list')).getText()).toBe('list=[]');
23996 element(by.css('#submit')).click();
23997 element(by.css('#submit')).click();
23998 expect(element(by.binding('list')).getText()).toContain('hello');
24009 * Specify custom behavior on focus event.
24011 * Note: As the `focus` event is executed synchronously when calling `input.focus()`
24012 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired
24013 * during an `$apply` to ensure a consistent state.
24015 * @element window, input, select, textarea, a
24017 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon
24018 * focus. ({@link guide/expression#-event- Event object is available as `$event`})
24021 * See {@link ng.directive:ngClick ngClick}
24029 * Specify custom behavior on blur event.
24031 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when
24032 * an element has lost focus.
24034 * Note: As the `blur` event is executed synchronously also during DOM manipulations
24035 * (e.g. removing a focussed input),
24036 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired
24037 * during an `$apply` to ensure a consistent state.
24039 * @element window, input, select, textarea, a
24041 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon
24042 * blur. ({@link guide/expression#-event- Event object is available as `$event`})
24045 * See {@link ng.directive:ngClick ngClick}
24053 * Specify custom behavior on copy event.
24055 * @element window, input, select, textarea, a
24057 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon
24058 * copy. ({@link guide/expression#-event- Event object is available as `$event`})
24062 <file name="index.html">
24063 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value">
24074 * Specify custom behavior on cut event.
24076 * @element window, input, select, textarea, a
24078 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon
24079 * cut. ({@link guide/expression#-event- Event object is available as `$event`})
24083 <file name="index.html">
24084 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value">
24095 * Specify custom behavior on paste event.
24097 * @element window, input, select, textarea, a
24099 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon
24100 * paste. ({@link guide/expression#-event- Event object is available as `$event`})
24104 <file name="index.html">
24105 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'>
24118 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an
24119 * {expression}. If the expression assigned to `ngIf` evaluates to a false
24120 * value then the element is removed from the DOM, otherwise a clone of the
24121 * element is reinserted into the DOM.
24123 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the
24124 * element in the DOM rather than changing its visibility via the `display` css property. A common
24125 * case when this difference is significant is when using css selectors that rely on an element's
24126 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classes.
24128 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope
24129 * is created when the element is restored. The scope created within `ngIf` inherits from
24130 * its parent scope using
24131 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance).
24132 * An important implication of this is if `ngModel` is used within `ngIf` to bind to
24133 * a javascript primitive defined in the parent scope. In this case any modifications made to the
24134 * variable within the child scope will override (hide) the value in the parent scope.
24136 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior
24137 * is if an element's class attribute is directly modified after it's compiled, using something like
24138 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element
24139 * the added class will be lost because the original compiled state is used to regenerate the element.
24141 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter`
24142 * and `leave` effects.
24145 * enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container
24146 * leave - happens just before the `ngIf` contents are removed from the DOM
24151 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then
24152 * the element is removed from the DOM tree. If it is truthy a copy of the compiled
24153 * element is added to the DOM tree.
24156 <example module="ngAnimate" deps="angular-animate.js" animations="true">
24157 <file name="index.html">
24158 <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/>
24160 <span ng-if="checked" class="animate-if">
24161 This is removed when the checkbox is unchecked.
24164 <file name="animations.css">
24167 border:1px solid black;
24171 .animate-if.ng-enter, .animate-if.ng-leave {
24172 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
24175 .animate-if.ng-enter,
24176 .animate-if.ng-leave.ng-leave-active {
24180 .animate-if.ng-leave,
24181 .animate-if.ng-enter.ng-enter-active {
24187 var ngIfDirective = ['$animate', function($animate) {
24189 multiElement: true,
24190 transclude: 'element',
24195 link: function($scope, $element, $attr, ctrl, $transclude) {
24196 var block, childScope, previousElements;
24197 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) {
24201 $transclude(function(clone, newScope) {
24202 childScope = newScope;
24203 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ');
24204 // Note: We only need the first/last node of the cloned nodes.
24205 // However, we need to keep the reference to the jqlite wrapper as it might be changed later
24206 // by a directive with templateUrl when its template arrives.
24210 $animate.enter(clone, $element.parent(), $element);
24214 if (previousElements) {
24215 previousElements.remove();
24216 previousElements = null;
24219 childScope.$destroy();
24223 previousElements = getBlockNodes(block.clone);
24224 $animate.leave(previousElements).then(function() {
24225 previousElements = null;
24241 * Fetches, compiles and includes an external HTML fragment.
24243 * By default, the template URL is restricted to the same domain and protocol as the
24244 * application document. This is done by calling {@link $sce#getTrustedResourceUrl
24245 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols
24246 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or
24247 * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link
24248 * ng.$sce Strict Contextual Escaping}.
24250 * In addition, the browser's
24251 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
24252 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
24253 * policy may further restrict whether the template is successfully loaded.
24254 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://`
24255 * access on some browsers.
24258 * enter - animation is used to bring new content into the browser.
24259 * leave - animation is used to animate existing content away.
24261 * The enter and leave animation occur concurrently.
24266 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
24267 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`.
24268 * @param {string=} onload Expression to evaluate when a new partial is loaded.
24269 * <div class="alert alert-warning">
24270 * **Note:** When using onload on SVG elements in IE11, the browser will try to call
24271 * a function with the name on the window element, which will usually throw a
24272 * "function is undefined" error. To fix this, you can instead use `data-onload` or a
24273 * different form that {@link guide/directive#normalization matches} `onload`.
24276 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll
24277 * $anchorScroll} to scroll the viewport after the content is loaded.
24279 * - If the attribute is not set, disable scrolling.
24280 * - If the attribute is set without value, enable scrolling.
24281 * - Otherwise enable scrolling only if the expression evaluates to truthy value.
24284 <example module="includeExample" deps="angular-animate.js" animations="true">
24285 <file name="index.html">
24286 <div ng-controller="ExampleController">
24287 <select ng-model="template" ng-options="t.name for t in templates">
24288 <option value="">(blank)</option>
24290 url of the template: <code>{{template.url}}</code>
24292 <div class="slide-animate-container">
24293 <div class="slide-animate" ng-include="template.url"></div>
24297 <file name="script.js">
24298 angular.module('includeExample', ['ngAnimate'])
24299 .controller('ExampleController', ['$scope', function($scope) {
24301 [ { name: 'template1.html', url: 'template1.html'},
24302 { name: 'template2.html', url: 'template2.html'} ];
24303 $scope.template = $scope.templates[0];
24306 <file name="template1.html">
24307 Content of template1.html
24309 <file name="template2.html">
24310 Content of template2.html
24312 <file name="animations.css">
24313 .slide-animate-container {
24316 border:1px solid black;
24325 .slide-animate.ng-enter, .slide-animate.ng-leave {
24326 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
24337 .slide-animate.ng-enter {
24340 .slide-animate.ng-enter.ng-enter-active {
24344 .slide-animate.ng-leave {
24347 .slide-animate.ng-leave.ng-leave-active {
24351 <file name="protractor.js" type="protractor">
24352 var templateSelect = element(by.model('template'));
24353 var includeElem = element(by.css('[ng-include]'));
24355 it('should load template1.html', function() {
24356 expect(includeElem.getText()).toMatch(/Content of template1.html/);
24359 it('should load template2.html', function() {
24360 if (browser.params.browser == 'firefox') {
24361 // Firefox can't handle using selects
24362 // See https://github.com/angular/protractor/issues/480
24365 templateSelect.click();
24366 templateSelect.all(by.css('option')).get(2).click();
24367 expect(includeElem.getText()).toMatch(/Content of template2.html/);
24370 it('should change to blank', function() {
24371 if (browser.params.browser == 'firefox') {
24372 // Firefox can't handle using selects
24375 templateSelect.click();
24376 templateSelect.all(by.css('option')).get(0).click();
24377 expect(includeElem.isPresent()).toBe(false);
24386 * @name ngInclude#$includeContentRequested
24387 * @eventType emit on the scope ngInclude was declared in
24389 * Emitted every time the ngInclude content is requested.
24391 * @param {Object} angularEvent Synthetic event object.
24392 * @param {String} src URL of content to load.
24398 * @name ngInclude#$includeContentLoaded
24399 * @eventType emit on the current ngInclude scope
24401 * Emitted every time the ngInclude content is reloaded.
24403 * @param {Object} angularEvent Synthetic event object.
24404 * @param {String} src URL of content to load.
24410 * @name ngInclude#$includeContentError
24411 * @eventType emit on the scope ngInclude was declared in
24413 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299)
24415 * @param {Object} angularEvent Synthetic event object.
24416 * @param {String} src URL of content to load.
24418 var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate',
24419 function($templateRequest, $anchorScroll, $animate) {
24424 transclude: 'element',
24425 controller: angular.noop,
24426 compile: function(element, attr) {
24427 var srcExp = attr.ngInclude || attr.src,
24428 onloadExp = attr.onload || '',
24429 autoScrollExp = attr.autoscroll;
24431 return function(scope, $element, $attr, ctrl, $transclude) {
24432 var changeCounter = 0,
24437 var cleanupLastIncludeContent = function() {
24438 if (previousElement) {
24439 previousElement.remove();
24440 previousElement = null;
24442 if (currentScope) {
24443 currentScope.$destroy();
24444 currentScope = null;
24446 if (currentElement) {
24447 $animate.leave(currentElement).then(function() {
24448 previousElement = null;
24450 previousElement = currentElement;
24451 currentElement = null;
24455 scope.$watch(srcExp, function ngIncludeWatchAction(src) {
24456 var afterAnimation = function() {
24457 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
24461 var thisChangeId = ++changeCounter;
24464 //set the 2nd param to true to ignore the template request error so that the inner
24465 //contents and scope can be cleaned up.
24466 $templateRequest(src, true).then(function(response) {
24467 if (thisChangeId !== changeCounter) return;
24468 var newScope = scope.$new();
24469 ctrl.template = response;
24471 // Note: This will also link all children of ng-include that were contained in the original
24472 // html. If that content contains controllers, ... they could pollute/change the scope.
24473 // However, using ng-include on an element with additional content does not make sense...
24474 // Note: We can't remove them in the cloneAttchFn of $transclude as that
24475 // function is called before linking the content, which would apply child
24476 // directives to non existing elements.
24477 var clone = $transclude(newScope, function(clone) {
24478 cleanupLastIncludeContent();
24479 $animate.enter(clone, null, $element).then(afterAnimation);
24482 currentScope = newScope;
24483 currentElement = clone;
24485 currentScope.$emit('$includeContentLoaded', src);
24486 scope.$eval(onloadExp);
24488 if (thisChangeId === changeCounter) {
24489 cleanupLastIncludeContent();
24490 scope.$emit('$includeContentError', src);
24493 scope.$emit('$includeContentRequested', src);
24495 cleanupLastIncludeContent();
24496 ctrl.template = null;
24504 // This directive is called during the $transclude call of the first `ngInclude` directive.
24505 // It will replace and compile the content of the element with the loaded template.
24506 // We need this directive so that the element content is already filled when
24507 // the link function of another directive on the same element as ngInclude
24509 var ngIncludeFillContentDirective = ['$compile',
24510 function($compile) {
24514 require: 'ngInclude',
24515 link: function(scope, $element, $attr, ctrl) {
24516 if (/SVG/.test($element[0].toString())) {
24517 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not
24518 // support innerHTML, so detect this here and try to generate the contents
24521 $compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope,
24522 function namespaceAdaptedClone(clone) {
24523 $element.append(clone);
24524 }, {futureParentElement: $element});
24528 $element.html(ctrl.template);
24529 $compile($element.contents())(scope);
24540 * The `ngInit` directive allows you to evaluate an expression in the
24543 * <div class="alert alert-danger">
24544 * This directive can be abused to add unnecessary amounts of logic into your templates.
24545 * There are only a few appropriate uses of `ngInit`, such as for aliasing special properties of
24546 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below; and for injecting data via
24547 * server side scripting. Besides these few cases, you should use {@link guide/controller controllers}
24548 * rather than `ngInit` to initialize values on a scope.
24551 * <div class="alert alert-warning">
24552 * **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make
24553 * sure you have parentheses to ensure correct operator precedence:
24554 * <pre class="prettyprint">
24555 * `<div ng-init="test1 = ($index | toString)"></div>`
24562 * @param {expression} ngInit {@link guide/expression Expression} to eval.
24565 <example module="initExample">
24566 <file name="index.html">
24568 angular.module('initExample', [])
24569 .controller('ExampleController', ['$scope', function($scope) {
24570 $scope.list = [['a', 'b'], ['c', 'd']];
24573 <div ng-controller="ExampleController">
24574 <div ng-repeat="innerList in list" ng-init="outerIndex = $index">
24575 <div ng-repeat="value in innerList" ng-init="innerIndex = $index">
24576 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span>
24581 <file name="protractor.js" type="protractor">
24582 it('should alias index positions', function() {
24583 var elements = element.all(by.css('.example-init'));
24584 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;');
24585 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;');
24586 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;');
24587 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;');
24592 var ngInitDirective = ngDirective({
24594 compile: function() {
24596 pre: function(scope, element, attrs) {
24597 scope.$eval(attrs.ngInit);
24608 * Text input that converts between a delimited string and an array of strings. The default
24609 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom
24610 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`.
24612 * The behaviour of the directive is affected by the use of the `ngTrim` attribute.
24613 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each
24614 * list item is respected. This implies that the user of the directive is responsible for
24615 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a
24616 * tab or newline character.
24617 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected
24618 * when joining the list items back together) and whitespace around each list item is stripped
24619 * before it is added to the model.
24621 * ### Example with Validation
24623 * <example name="ngList-directive" module="listExample">
24624 * <file name="app.js">
24625 * angular.module('listExample', [])
24626 * .controller('ExampleController', ['$scope', function($scope) {
24627 * $scope.names = ['morpheus', 'neo', 'trinity'];
24630 * <file name="index.html">
24631 * <form name="myForm" ng-controller="ExampleController">
24632 * <label>List: <input name="namesInput" ng-model="names" ng-list required></label>
24633 * <span role="alert">
24634 * <span class="error" ng-show="myForm.namesInput.$error.required">
24638 * <tt>names = {{names}}</tt><br/>
24639 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
24640 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
24641 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
24642 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
24645 * <file name="protractor.js" type="protractor">
24646 * var listInput = element(by.model('names'));
24647 * var names = element(by.exactBinding('names'));
24648 * var valid = element(by.binding('myForm.namesInput.$valid'));
24649 * var error = element(by.css('span.error'));
24651 * it('should initialize to model', function() {
24652 * expect(names.getText()).toContain('["morpheus","neo","trinity"]');
24653 * expect(valid.getText()).toContain('true');
24654 * expect(error.getCssValue('display')).toBe('none');
24657 * it('should be invalid if empty', function() {
24658 * listInput.clear();
24659 * listInput.sendKeys('');
24661 * expect(names.getText()).toContain('');
24662 * expect(valid.getText()).toContain('false');
24663 * expect(error.getCssValue('display')).not.toBe('none');
24668 * ### Example - splitting on newline
24669 * <example name="ngList-directive-newlines">
24670 * <file name="index.html">
24671 * <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea>
24672 * <pre>{{ list | json }}</pre>
24674 * <file name="protractor.js" type="protractor">
24675 * it("should split the text by newlines", function() {
24676 * var listInput = element(by.model('list'));
24677 * var output = element(by.binding('list | json'));
24678 * listInput.sendKeys('abc\ndef\nghi');
24679 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]');
24685 * @param {string=} ngList optional delimiter that should be used to split the value.
24687 var ngListDirective = function() {
24691 require: 'ngModel',
24692 link: function(scope, element, attr, ctrl) {
24693 // We want to control whitespace trimming so we use this convoluted approach
24694 // to access the ngList attribute, which doesn't pre-trim the attribute
24695 var ngList = element.attr(attr.$attr.ngList) || ', ';
24696 var trimValues = attr.ngTrim !== 'false';
24697 var separator = trimValues ? trim(ngList) : ngList;
24699 var parse = function(viewValue) {
24700 // If the viewValue is invalid (say required but empty) it will be `undefined`
24701 if (isUndefined(viewValue)) return;
24706 forEach(viewValue.split(separator), function(value) {
24707 if (value) list.push(trimValues ? trim(value) : value);
24714 ctrl.$parsers.push(parse);
24715 ctrl.$formatters.push(function(value) {
24716 if (isArray(value)) {
24717 return value.join(ngList);
24723 // Override the standard $isEmpty because an empty array means the input is empty.
24724 ctrl.$isEmpty = function(value) {
24725 return !value || !value.length;
24731 /* global VALID_CLASS: true,
24732 INVALID_CLASS: true,
24733 PRISTINE_CLASS: true,
24735 UNTOUCHED_CLASS: true,
24736 TOUCHED_CLASS: true,
24739 var VALID_CLASS = 'ng-valid',
24740 INVALID_CLASS = 'ng-invalid',
24741 PRISTINE_CLASS = 'ng-pristine',
24742 DIRTY_CLASS = 'ng-dirty',
24743 UNTOUCHED_CLASS = 'ng-untouched',
24744 TOUCHED_CLASS = 'ng-touched',
24745 PENDING_CLASS = 'ng-pending';
24747 var ngModelMinErr = minErr('ngModel');
24751 * @name ngModel.NgModelController
24753 * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a
24754 * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue
24756 * @property {*} $modelValue The value in the model that the control is bound to.
24757 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
24758 the control reads value from the DOM. The functions are called in array order, each passing
24759 its return value through to the next. The last return value is forwarded to the
24760 {@link ngModel.NgModelController#$validators `$validators`} collection.
24762 Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue
24765 Returning `undefined` from a parser means a parse error occurred. In that case,
24766 no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel`
24767 will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`}
24768 is set to `true`. The parse error is stored in `ngModel.$error.parse`.
24771 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
24772 the model value changes. The functions are called in reverse array order, each passing the value through to the
24773 next. The last return value is used as the actual DOM value.
24774 Used to format / convert values for display in the control.
24776 * function formatter(value) {
24778 * return value.toUpperCase();
24781 * ngModel.$formatters.push(formatter);
24784 * @property {Object.<string, function>} $validators A collection of validators that are applied
24785 * whenever the model value changes. The key value within the object refers to the name of the
24786 * validator while the function refers to the validation operation. The validation operation is
24787 * provided with the model value as an argument and must return a true or false value depending
24788 * on the response of that validation.
24791 * ngModel.$validators.validCharacters = function(modelValue, viewValue) {
24792 * var value = modelValue || viewValue;
24793 * return /[0-9]+/.test(value) &&
24794 * /[a-z]+/.test(value) &&
24795 * /[A-Z]+/.test(value) &&
24796 * /\W+/.test(value);
24800 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to
24801 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided
24802 * is expected to return a promise when it is run during the model validation process. Once the promise
24803 * is delivered then the validation status will be set to true when fulfilled and false when rejected.
24804 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model
24805 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator
24806 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators
24807 * will only run once all synchronous validators have passed.
24809 * Please note that if $http is used then it is important that the server returns a success HTTP response code
24810 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation.
24813 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
24814 * var value = modelValue || viewValue;
24816 * // Lookup user by username
24817 * return $http.get('/api/users/' + value).
24818 * then(function resolved() {
24819 * //username exists, this means validation fails
24820 * return $q.reject('exists');
24821 * }, function rejected() {
24822 * //username does not exist, therefore this validation passes
24828 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the
24829 * view value has changed. It is called with no arguments, and its return value is ignored.
24830 * This can be used in place of additional $watches against the model value.
24832 * @property {Object} $error An object hash with all failing validator ids as keys.
24833 * @property {Object} $pending An object hash with all pending validator ids as keys.
24835 * @property {boolean} $untouched True if control has not lost focus yet.
24836 * @property {boolean} $touched True if control has lost focus.
24837 * @property {boolean} $pristine True if user has not interacted with the control yet.
24838 * @property {boolean} $dirty True if user has already interacted with the control.
24839 * @property {boolean} $valid True if there is no error.
24840 * @property {boolean} $invalid True if at least one error on the control.
24841 * @property {string} $name The name attribute of the control.
24845 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive.
24846 * The controller contains services for data-binding, validation, CSS updates, and value formatting
24847 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or
24848 * listening to DOM events.
24849 * Such DOM related logic should be provided by other directives which make use of
24850 * `NgModelController` for data-binding to control elements.
24851 * Angular provides this DOM logic for most {@link input `input`} elements.
24852 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example
24853 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements.
24856 * ### Custom Control Example
24857 * This example shows how to use `NgModelController` with a custom control to achieve
24858 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
24859 * collaborate together to achieve the desired result.
24861 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element
24862 * contents be edited in place by the user.
24864 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize}
24865 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`).
24866 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks
24867 * that content using the `$sce` service.
24869 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js">
24870 <file name="style.css">
24871 [contenteditable] {
24872 border: 1px solid black;
24873 background-color: white;
24878 border: 1px solid red;
24882 <file name="script.js">
24883 angular.module('customControl', ['ngSanitize']).
24884 directive('contenteditable', ['$sce', function($sce) {
24886 restrict: 'A', // only activate on element attribute
24887 require: '?ngModel', // get a hold of NgModelController
24888 link: function(scope, element, attrs, ngModel) {
24889 if (!ngModel) return; // do nothing if no ng-model
24891 // Specify how UI should be updated
24892 ngModel.$render = function() {
24893 element.html($sce.getTrustedHtml(ngModel.$viewValue || ''));
24896 // Listen for change events to enable binding
24897 element.on('blur keyup change', function() {
24898 scope.$evalAsync(read);
24900 read(); // initialize
24902 // Write data to the model
24904 var html = element.html();
24905 // When we clear the content editable the browser leaves a <br> behind
24906 // If strip-br attribute is provided then we strip this out
24907 if ( attrs.stripBr && html == '<br>' ) {
24910 ngModel.$setViewValue(html);
24916 <file name="index.html">
24917 <form name="myForm">
24918 <div contenteditable
24919 name="myWidget" ng-model="userContent"
24921 required>Change me!</div>
24922 <span ng-show="myForm.myWidget.$error.required">Required!</span>
24924 <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea>
24927 <file name="protractor.js" type="protractor">
24928 it('should data-bind and become invalid', function() {
24929 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') {
24930 // SafariDriver can't handle contenteditable
24931 // and Firefox driver can't clear contenteditables very well
24934 var contentEditable = element(by.css('[contenteditable]'));
24935 var content = 'Change me!';
24937 expect(contentEditable.getText()).toEqual(content);
24939 contentEditable.clear();
24940 contentEditable.sendKeys(protractor.Key.BACK_SPACE);
24941 expect(contentEditable.getText()).toEqual('');
24942 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/);
24949 var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate',
24950 function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) {
24951 this.$viewValue = Number.NaN;
24952 this.$modelValue = Number.NaN;
24953 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity.
24954 this.$validators = {};
24955 this.$asyncValidators = {};
24956 this.$parsers = [];
24957 this.$formatters = [];
24958 this.$viewChangeListeners = [];
24959 this.$untouched = true;
24960 this.$touched = false;
24961 this.$pristine = true;
24962 this.$dirty = false;
24963 this.$valid = true;
24964 this.$invalid = false;
24965 this.$error = {}; // keep invalid keys here
24966 this.$$success = {}; // keep valid keys here
24967 this.$pending = undefined; // keep pending keys here
24968 this.$name = $interpolate($attr.name || '', false)($scope);
24969 this.$$parentForm = nullFormCtrl;
24971 var parsedNgModel = $parse($attr.ngModel),
24972 parsedNgModelAssign = parsedNgModel.assign,
24973 ngModelGet = parsedNgModel,
24974 ngModelSet = parsedNgModelAssign,
24975 pendingDebounce = null,
24979 this.$$setOptions = function(options) {
24980 ctrl.$options = options;
24981 if (options && options.getterSetter) {
24982 var invokeModelGetter = $parse($attr.ngModel + '()'),
24983 invokeModelSetter = $parse($attr.ngModel + '($$$p)');
24985 ngModelGet = function($scope) {
24986 var modelValue = parsedNgModel($scope);
24987 if (isFunction(modelValue)) {
24988 modelValue = invokeModelGetter($scope);
24992 ngModelSet = function($scope, newValue) {
24993 if (isFunction(parsedNgModel($scope))) {
24994 invokeModelSetter($scope, {$$$p: ctrl.$modelValue});
24996 parsedNgModelAssign($scope, ctrl.$modelValue);
24999 } else if (!parsedNgModel.assign) {
25000 throw ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}",
25001 $attr.ngModel, startingTag($element));
25007 * @name ngModel.NgModelController#$render
25010 * Called when the view needs to be updated. It is expected that the user of the ng-model
25011 * directive will implement this method.
25013 * The `$render()` method is invoked in the following situations:
25015 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last
25016 * committed value then `$render()` is called to update the input control.
25017 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and
25018 * the `$viewValue` are different from last time.
25020 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of
25021 * `$modelValue` and `$viewValue` are actually different from their previous value. If `$modelValue`
25022 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be
25023 * invoked if you only change a property on the objects.
25025 this.$render = noop;
25029 * @name ngModel.NgModelController#$isEmpty
25032 * This is called when we need to determine if the value of an input is empty.
25034 * For instance, the required directive does this to work out if the input has data or not.
25036 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`.
25038 * You can override this for input directives whose concept of being empty is different from the
25039 * default. The `checkboxInputType` directive does this because in its case a value of `false`
25042 * @param {*} value The value of the input to check for emptiness.
25043 * @returns {boolean} True if `value` is "empty".
25045 this.$isEmpty = function(value) {
25046 return isUndefined(value) || value === '' || value === null || value !== value;
25049 var currentValidationRunId = 0;
25053 * @name ngModel.NgModelController#$setValidity
25056 * Change the validity state, and notify the form.
25058 * This method can be called within $parsers/$formatters or a custom validation implementation.
25059 * However, in most cases it should be sufficient to use the `ngModel.$validators` and
25060 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically.
25062 * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned
25063 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]`
25064 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding.
25065 * The `validationErrorKey` should be in camelCase and will get converted into dash-case
25066 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
25067 * class and can be bound to as `{{someForm.someControl.$error.myError}}` .
25068 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined),
25069 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`.
25070 * Skipped is used by Angular when validators do not run because of parse errors and
25071 * when `$asyncValidators` do not run because any of the `$validators` failed.
25073 addSetValidityMethod({
25075 $element: $element,
25076 set: function(object, property) {
25077 object[property] = true;
25079 unset: function(object, property) {
25080 delete object[property];
25087 * @name ngModel.NgModelController#$setPristine
25090 * Sets the control to its pristine state.
25092 * This method can be called to remove the `ng-dirty` class and set the control to its pristine
25093 * state (`ng-pristine` class). A model is considered to be pristine when the control
25094 * has not been changed from when first compiled.
25096 this.$setPristine = function() {
25097 ctrl.$dirty = false;
25098 ctrl.$pristine = true;
25099 $animate.removeClass($element, DIRTY_CLASS);
25100 $animate.addClass($element, PRISTINE_CLASS);
25105 * @name ngModel.NgModelController#$setDirty
25108 * Sets the control to its dirty state.
25110 * This method can be called to remove the `ng-pristine` class and set the control to its dirty
25111 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed
25112 * from when first compiled.
25114 this.$setDirty = function() {
25115 ctrl.$dirty = true;
25116 ctrl.$pristine = false;
25117 $animate.removeClass($element, PRISTINE_CLASS);
25118 $animate.addClass($element, DIRTY_CLASS);
25119 ctrl.$$parentForm.$setDirty();
25124 * @name ngModel.NgModelController#$setUntouched
25127 * Sets the control to its untouched state.
25129 * This method can be called to remove the `ng-touched` class and set the control to its
25130 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched
25131 * by default, however this function can be used to restore that state if the model has
25132 * already been touched by the user.
25134 this.$setUntouched = function() {
25135 ctrl.$touched = false;
25136 ctrl.$untouched = true;
25137 $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS);
25142 * @name ngModel.NgModelController#$setTouched
25145 * Sets the control to its touched state.
25147 * This method can be called to remove the `ng-untouched` class and set the control to its
25148 * touched state (`ng-touched` class). A model is considered to be touched when the user has
25149 * first focused the control element and then shifted focus away from the control (blur event).
25151 this.$setTouched = function() {
25152 ctrl.$touched = true;
25153 ctrl.$untouched = false;
25154 $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS);
25159 * @name ngModel.NgModelController#$rollbackViewValue
25162 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`,
25163 * which may be caused by a pending debounced event or because the input is waiting for a some
25166 * If you have an input that uses `ng-model-options` to set up debounced events or events such
25167 * as blur you can have a situation where there is a period when the `$viewValue`
25168 * is out of synch with the ngModel's `$modelValue`.
25170 * In this case, you can run into difficulties if you try to update the ngModel's `$modelValue`
25171 * programmatically before these debounced/future events have resolved/occurred, because Angular's
25172 * dirty checking mechanism is not able to tell whether the model has actually changed or not.
25174 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an
25175 * input which may have such events pending. This is important in order to make sure that the
25176 * input field will be updated with the new model value and any pending operations are cancelled.
25178 * <example name="ng-model-cancel-update" module="cancel-update-example">
25179 * <file name="app.js">
25180 * angular.module('cancel-update-example', [])
25182 * .controller('CancelUpdateController', ['$scope', function($scope) {
25183 * $scope.resetWithCancel = function(e) {
25184 * if (e.keyCode == 27) {
25185 * $scope.myForm.myInput1.$rollbackViewValue();
25186 * $scope.myValue = '';
25189 * $scope.resetWithoutCancel = function(e) {
25190 * if (e.keyCode == 27) {
25191 * $scope.myValue = '';
25196 * <file name="index.html">
25197 * <div ng-controller="CancelUpdateController">
25198 * <p>Try typing something in each input. See that the model only updates when you
25199 * blur off the input.
25201 * <p>Now see what happens if you start typing then press the Escape key</p>
25203 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }">
25204 * <p id="inputDescription1">With $rollbackViewValue()</p>
25205 * <input name="myInput1" aria-describedby="inputDescription1" ng-model="myValue"
25206 * ng-keydown="resetWithCancel($event)"><br/>
25207 * myValue: "{{ myValue }}"
25209 * <p id="inputDescription2">Without $rollbackViewValue()</p>
25210 * <input name="myInput2" aria-describedby="inputDescription2" ng-model="myValue"
25211 * ng-keydown="resetWithoutCancel($event)"><br/>
25212 * myValue: "{{ myValue }}"
25218 this.$rollbackViewValue = function() {
25219 $timeout.cancel(pendingDebounce);
25220 ctrl.$viewValue = ctrl.$$lastCommittedViewValue;
25226 * @name ngModel.NgModelController#$validate
25229 * Runs each of the registered validators (first synchronous validators and then
25230 * asynchronous validators).
25231 * If the validity changes to invalid, the model will be set to `undefined`,
25232 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`.
25233 * If the validity changes to valid, it will set the model to the last available valid
25234 * `$modelValue`, i.e. either the last parsed value or the last value set from the scope.
25236 this.$validate = function() {
25237 // ignore $validate before model is initialized
25238 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
25242 var viewValue = ctrl.$$lastCommittedViewValue;
25243 // Note: we use the $$rawModelValue as $modelValue might have been
25244 // set to undefined during a view -> model update that found validation
25245 // errors. We can't parse the view here, since that could change
25246 // the model although neither viewValue nor the model on the scope changed
25247 var modelValue = ctrl.$$rawModelValue;
25249 var prevValid = ctrl.$valid;
25250 var prevModelValue = ctrl.$modelValue;
25252 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
25254 ctrl.$$runValidators(modelValue, viewValue, function(allValid) {
25255 // If there was no change in validity, don't update the model
25256 // This prevents changing an invalid modelValue to undefined
25257 if (!allowInvalid && prevValid !== allValid) {
25258 // Note: Don't check ctrl.$valid here, as we could have
25259 // external validators (e.g. calculated on the server),
25260 // that just call $setValidity and need the model value
25261 // to calculate their validity.
25262 ctrl.$modelValue = allValid ? modelValue : undefined;
25264 if (ctrl.$modelValue !== prevModelValue) {
25265 ctrl.$$writeModelToScope();
25272 this.$$runValidators = function(modelValue, viewValue, doneCallback) {
25273 currentValidationRunId++;
25274 var localValidationRunId = currentValidationRunId;
25276 // check parser error
25277 if (!processParseErrors()) {
25278 validationDone(false);
25281 if (!processSyncValidators()) {
25282 validationDone(false);
25285 processAsyncValidators();
25287 function processParseErrors() {
25288 var errorKey = ctrl.$$parserName || 'parse';
25289 if (isUndefined(parserValid)) {
25290 setValidity(errorKey, null);
25292 if (!parserValid) {
25293 forEach(ctrl.$validators, function(v, name) {
25294 setValidity(name, null);
25296 forEach(ctrl.$asyncValidators, function(v, name) {
25297 setValidity(name, null);
25300 // Set the parse error last, to prevent unsetting it, should a $validators key == parserName
25301 setValidity(errorKey, parserValid);
25302 return parserValid;
25307 function processSyncValidators() {
25308 var syncValidatorsValid = true;
25309 forEach(ctrl.$validators, function(validator, name) {
25310 var result = validator(modelValue, viewValue);
25311 syncValidatorsValid = syncValidatorsValid && result;
25312 setValidity(name, result);
25314 if (!syncValidatorsValid) {
25315 forEach(ctrl.$asyncValidators, function(v, name) {
25316 setValidity(name, null);
25323 function processAsyncValidators() {
25324 var validatorPromises = [];
25325 var allValid = true;
25326 forEach(ctrl.$asyncValidators, function(validator, name) {
25327 var promise = validator(modelValue, viewValue);
25328 if (!isPromiseLike(promise)) {
25329 throw ngModelMinErr("$asyncValidators",
25330 "Expected asynchronous validator to return a promise but got '{0}' instead.", promise);
25332 setValidity(name, undefined);
25333 validatorPromises.push(promise.then(function() {
25334 setValidity(name, true);
25335 }, function(error) {
25337 setValidity(name, false);
25340 if (!validatorPromises.length) {
25341 validationDone(true);
25343 $q.all(validatorPromises).then(function() {
25344 validationDone(allValid);
25349 function setValidity(name, isValid) {
25350 if (localValidationRunId === currentValidationRunId) {
25351 ctrl.$setValidity(name, isValid);
25355 function validationDone(allValid) {
25356 if (localValidationRunId === currentValidationRunId) {
25358 doneCallback(allValid);
25365 * @name ngModel.NgModelController#$commitViewValue
25368 * Commit a pending update to the `$modelValue`.
25370 * Updates may be pending by a debounced event or because the input is waiting for a some future
25371 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController`
25372 * usually handles calling this in response to input events.
25374 this.$commitViewValue = function() {
25375 var viewValue = ctrl.$viewValue;
25377 $timeout.cancel(pendingDebounce);
25379 // If the view value has not changed then we should just exit, except in the case where there is
25380 // a native validator on the element. In this case the validation state may have changed even though
25381 // the viewValue has stayed empty.
25382 if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) {
25385 ctrl.$$lastCommittedViewValue = viewValue;
25388 if (ctrl.$pristine) {
25391 this.$$parseAndValidate();
25394 this.$$parseAndValidate = function() {
25395 var viewValue = ctrl.$$lastCommittedViewValue;
25396 var modelValue = viewValue;
25397 parserValid = isUndefined(modelValue) ? undefined : true;
25400 for (var i = 0; i < ctrl.$parsers.length; i++) {
25401 modelValue = ctrl.$parsers[i](modelValue);
25402 if (isUndefined(modelValue)) {
25403 parserValid = false;
25408 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
25409 // ctrl.$modelValue has not been touched yet...
25410 ctrl.$modelValue = ngModelGet($scope);
25412 var prevModelValue = ctrl.$modelValue;
25413 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
25414 ctrl.$$rawModelValue = modelValue;
25416 if (allowInvalid) {
25417 ctrl.$modelValue = modelValue;
25418 writeToModelIfNeeded();
25421 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
25422 // This can happen if e.g. $setViewValue is called from inside a parser
25423 ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
25424 if (!allowInvalid) {
25425 // Note: Don't check ctrl.$valid here, as we could have
25426 // external validators (e.g. calculated on the server),
25427 // that just call $setValidity and need the model value
25428 // to calculate their validity.
25429 ctrl.$modelValue = allValid ? modelValue : undefined;
25430 writeToModelIfNeeded();
25434 function writeToModelIfNeeded() {
25435 if (ctrl.$modelValue !== prevModelValue) {
25436 ctrl.$$writeModelToScope();
25441 this.$$writeModelToScope = function() {
25442 ngModelSet($scope, ctrl.$modelValue);
25443 forEach(ctrl.$viewChangeListeners, function(listener) {
25447 $exceptionHandler(e);
25454 * @name ngModel.NgModelController#$setViewValue
25457 * Update the view value.
25459 * This method should be called when a control wants to change the view value; typically,
25460 * this is done from within a DOM event handler. For example, the {@link ng.directive:input input}
25461 * directive calls it when the value of the input changes and {@link ng.directive:select select}
25462 * calls it when an option is selected.
25464 * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers`
25465 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged
25466 * value sent directly for processing, finally to be applied to `$modelValue` and then the
25467 * **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners,
25468 * in the `$viewChangeListeners` list, are called.
25470 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn`
25471 * and the `default` trigger is not listed, all those actions will remain pending until one of the
25472 * `updateOn` events is triggered on the DOM element.
25473 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions}
25474 * directive is used with a custom debounce for this particular event.
25475 * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce`
25476 * is specified, once the timer runs out.
25478 * When used with standard inputs, the view value will always be a string (which is in some cases
25479 * parsed into another type, such as a `Date` object for `input[date]`.)
25480 * However, custom controls might also pass objects to this method. In this case, we should make
25481 * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not
25482 * perform a deep watch of objects, it only looks for a change of identity. If you only change
25483 * the property of the object then ngModel will not realise that the object has changed and
25484 * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should
25485 * not change properties of the copy once it has been passed to `$setViewValue`.
25486 * Otherwise you may cause the model value on the scope to change incorrectly.
25488 * <div class="alert alert-info">
25489 * In any case, the value passed to the method should always reflect the current value
25490 * of the control. For example, if you are calling `$setViewValue` for an input element,
25491 * you should pass the input DOM value. Otherwise, the control and the scope model become
25492 * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change
25493 * the control's DOM value in any way. If we want to change the control's DOM value
25494 * programmatically, we should update the `ngModel` scope expression. Its new value will be
25495 * picked up by the model controller, which will run it through the `$formatters`, `$render` it
25496 * to update the DOM, and finally call `$validate` on it.
25499 * @param {*} value value from the view.
25500 * @param {string} trigger Event that triggered the update.
25502 this.$setViewValue = function(value, trigger) {
25503 ctrl.$viewValue = value;
25504 if (!ctrl.$options || ctrl.$options.updateOnDefault) {
25505 ctrl.$$debounceViewValueCommit(trigger);
25509 this.$$debounceViewValueCommit = function(trigger) {
25510 var debounceDelay = 0,
25511 options = ctrl.$options,
25514 if (options && isDefined(options.debounce)) {
25515 debounce = options.debounce;
25516 if (isNumber(debounce)) {
25517 debounceDelay = debounce;
25518 } else if (isNumber(debounce[trigger])) {
25519 debounceDelay = debounce[trigger];
25520 } else if (isNumber(debounce['default'])) {
25521 debounceDelay = debounce['default'];
25525 $timeout.cancel(pendingDebounce);
25526 if (debounceDelay) {
25527 pendingDebounce = $timeout(function() {
25528 ctrl.$commitViewValue();
25530 } else if ($rootScope.$$phase) {
25531 ctrl.$commitViewValue();
25533 $scope.$apply(function() {
25534 ctrl.$commitViewValue();
25540 // Note: we cannot use a normal scope.$watch as we want to detect the following:
25541 // 1. scope value is 'a'
25542 // 2. user enters 'b'
25543 // 3. ng-change kicks in and reverts scope value to 'a'
25544 // -> scope value did not change since the last digest as
25545 // ng-change executes in apply phase
25546 // 4. view should be changed back to 'a'
25547 $scope.$watch(function ngModelWatch() {
25548 var modelValue = ngModelGet($scope);
25550 // if scope model value and ngModel value are out of sync
25551 // TODO(perf): why not move this to the action fn?
25552 if (modelValue !== ctrl.$modelValue &&
25553 // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator
25554 (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue)
25556 ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
25557 parserValid = undefined;
25559 var formatters = ctrl.$formatters,
25560 idx = formatters.length;
25562 var viewValue = modelValue;
25564 viewValue = formatters[idx](viewValue);
25566 if (ctrl.$viewValue !== viewValue) {
25567 ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
25570 ctrl.$$runValidators(modelValue, viewValue, noop);
25587 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a
25588 * property on the scope using {@link ngModel.NgModelController NgModelController},
25589 * which is created and exposed by this directive.
25591 * `ngModel` is responsible for:
25593 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select`
25595 * - Providing validation behavior (i.e. required, number, email, url).
25596 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors).
25597 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations.
25598 * - Registering the control with its parent {@link ng.directive:form form}.
25600 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the
25601 * current scope. If the property doesn't already exist on this scope, it will be created
25602 * implicitly and added to the scope.
25604 * For best practices on using `ngModel`, see:
25606 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes)
25608 * For basic examples, how to use `ngModel`, see:
25610 * - {@link ng.directive:input input}
25611 * - {@link input[text] text}
25612 * - {@link input[checkbox] checkbox}
25613 * - {@link input[radio] radio}
25614 * - {@link input[number] number}
25615 * - {@link input[email] email}
25616 * - {@link input[url] url}
25617 * - {@link input[date] date}
25618 * - {@link input[datetime-local] datetime-local}
25619 * - {@link input[time] time}
25620 * - {@link input[month] month}
25621 * - {@link input[week] week}
25622 * - {@link ng.directive:select select}
25623 * - {@link ng.directive:textarea textarea}
25626 * The following CSS classes are added and removed on the associated input/select/textarea element
25627 * depending on the validity of the model.
25629 * - `ng-valid`: the model is valid
25630 * - `ng-invalid`: the model is invalid
25631 * - `ng-valid-[key]`: for each valid key added by `$setValidity`
25632 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity`
25633 * - `ng-pristine`: the control hasn't been interacted with yet
25634 * - `ng-dirty`: the control has been interacted with
25635 * - `ng-touched`: the control has been blurred
25636 * - `ng-untouched`: the control hasn't been blurred
25637 * - `ng-pending`: any `$asyncValidators` are unfulfilled
25639 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
25641 * ## Animation Hooks
25643 * Animations within models are triggered when any of the associated CSS classes are added and removed
25644 * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`,
25645 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself.
25646 * The animations that are triggered within ngModel are similar to how they work in ngClass and
25647 * animations can be hooked into using CSS transitions, keyframes as well as JS animations.
25649 * The following example shows a simple way to utilize CSS transitions to style an input element
25650 * that has been rendered as invalid after it has been validated:
25653 * //be sure to include ngAnimate as a module to hook into more
25654 * //advanced animations
25656 * transition:0.5s linear all;
25657 * background: white;
25659 * .my-input.ng-invalid {
25666 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample">
25667 <file name="index.html">
25669 angular.module('inputExample', [])
25670 .controller('ExampleController', ['$scope', function($scope) {
25676 transition:all linear 0.5s;
25677 background: transparent;
25679 .my-input.ng-invalid {
25684 <p id="inputDescription">
25685 Update input to see transitions when valid/invalid.
25686 Integer is a valid value.
25688 <form name="testForm" ng-controller="ExampleController">
25689 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input"
25690 aria-describedby="inputDescription" />
25695 * ## Binding to a getter/setter
25697 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a
25698 * function that returns a representation of the model when called with zero arguments, and sets
25699 * the internal state of a model when called with an argument. It's sometimes useful to use this
25700 * for models that have an internal representation that's different from what the model exposes
25703 * <div class="alert alert-success">
25704 * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more
25705 * frequently than other parts of your code.
25708 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that
25709 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to
25710 * a `<form>`, which will enable this behavior for all `<input>`s within it. See
25711 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more.
25713 * The following example shows how to use `ngModel` with a getter/setter:
25716 * <example name="ngModel-getter-setter" module="getterSetterExample">
25717 <file name="index.html">
25718 <div ng-controller="ExampleController">
25719 <form name="userForm">
25721 <input type="text" name="userName"
25722 ng-model="user.name"
25723 ng-model-options="{ getterSetter: true }" />
25726 <pre>user.name = <span ng-bind="user.name()"></span></pre>
25729 <file name="app.js">
25730 angular.module('getterSetterExample', [])
25731 .controller('ExampleController', ['$scope', function($scope) {
25732 var _name = 'Brian';
25734 name: function(newName) {
25735 // Note that newName can be undefined for two reasons:
25736 // 1. Because it is called as a getter and thus called with no arguments
25737 // 2. Because the property should actually be set to undefined. This happens e.g. if the
25738 // input is invalid
25739 return arguments.length ? (_name = newName) : _name;
25746 var ngModelDirective = ['$rootScope', function($rootScope) {
25749 require: ['ngModel', '^?form', '^?ngModelOptions'],
25750 controller: NgModelController,
25751 // Prelink needs to run before any input directive
25752 // so that we can set the NgModelOptions in NgModelController
25753 // before anyone else uses it.
25755 compile: function ngModelCompile(element) {
25756 // Setup initial state of the control
25757 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);
25760 pre: function ngModelPreLink(scope, element, attr, ctrls) {
25761 var modelCtrl = ctrls[0],
25762 formCtrl = ctrls[1] || modelCtrl.$$parentForm;
25764 modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
25766 // notify others, especially parent forms
25767 formCtrl.$addControl(modelCtrl);
25769 attr.$observe('name', function(newValue) {
25770 if (modelCtrl.$name !== newValue) {
25771 modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue);
25775 scope.$on('$destroy', function() {
25776 modelCtrl.$$parentForm.$removeControl(modelCtrl);
25779 post: function ngModelPostLink(scope, element, attr, ctrls) {
25780 var modelCtrl = ctrls[0];
25781 if (modelCtrl.$options && modelCtrl.$options.updateOn) {
25782 element.on(modelCtrl.$options.updateOn, function(ev) {
25783 modelCtrl.$$debounceViewValueCommit(ev && ev.type);
25787 element.on('blur', function(ev) {
25788 if (modelCtrl.$touched) return;
25790 if ($rootScope.$$phase) {
25791 scope.$evalAsync(modelCtrl.$setTouched);
25793 scope.$apply(modelCtrl.$setTouched);
25802 var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
25806 * @name ngModelOptions
25809 * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of
25810 * events that will trigger a model update and/or a debouncing delay so that the actual update only
25811 * takes place when a timer expires; this timer will be reset after another change takes place.
25813 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might
25814 * be different from the value in the actual model. This means that if you update the model you
25815 * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in
25816 * order to make sure it is synchronized with the model and that any debounced action is canceled.
25818 * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`}
25819 * method is by making sure the input is placed inside a form that has a `name` attribute. This is
25820 * important because `form` controllers are published to the related scope under the name in their
25821 * `name` attribute.
25823 * Any pending changes will take place immediately when an enclosing form is submitted via the
25824 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
25825 * to have access to the updated model.
25827 * `ngModelOptions` has an effect on the element it's declared on and its descendants.
25829 * @param {Object} ngModelOptions options to apply to the current model. Valid keys are:
25830 * - `updateOn`: string specifying which event should the input be bound to. You can set several
25831 * events using an space delimited list. There is a special event called `default` that
25832 * matches the default events belonging of the control.
25833 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A
25834 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a
25835 * custom value for each event. For example:
25836 * `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }"`
25837 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did
25838 * not validate correctly instead of the default behavior of setting the model to undefined.
25839 * - `getterSetter`: boolean value which determines whether or not to treat functions bound to
25840 `ngModel` as getters/setters.
25841 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
25842 * `<input type="date">`, `<input type="time">`, ... . It understands UTC/GMT and the
25843 * continental US time zone abbreviations, but for general use, use a time zone offset, for
25844 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
25845 * If not specified, the timezone of the browser will be used.
25849 The following example shows how to override immediate updates. Changes on the inputs within the
25850 form will update the model only when the control loses focus (blur event). If `escape` key is
25851 pressed while the input field is focused, the value is reset to the value in the current model.
25853 <example name="ngModelOptions-directive-blur" module="optionsExample">
25854 <file name="index.html">
25855 <div ng-controller="ExampleController">
25856 <form name="userForm">
25858 <input type="text" name="userName"
25859 ng-model="user.name"
25860 ng-model-options="{ updateOn: 'blur' }"
25861 ng-keyup="cancel($event)" />
25864 <input type="text" ng-model="user.data" />
25867 <pre>user.name = <span ng-bind="user.name"></span></pre>
25868 <pre>user.data = <span ng-bind="user.data"></span></pre>
25871 <file name="app.js">
25872 angular.module('optionsExample', [])
25873 .controller('ExampleController', ['$scope', function($scope) {
25874 $scope.user = { name: 'John', data: '' };
25876 $scope.cancel = function(e) {
25877 if (e.keyCode == 27) {
25878 $scope.userForm.userName.$rollbackViewValue();
25883 <file name="protractor.js" type="protractor">
25884 var model = element(by.binding('user.name'));
25885 var input = element(by.model('user.name'));
25886 var other = element(by.model('user.data'));
25888 it('should allow custom events', function() {
25889 input.sendKeys(' Doe');
25891 expect(model.getText()).toEqual('John');
25893 expect(model.getText()).toEqual('John Doe');
25896 it('should $rollbackViewValue when model changes', function() {
25897 input.sendKeys(' Doe');
25898 expect(input.getAttribute('value')).toEqual('John Doe');
25899 input.sendKeys(protractor.Key.ESCAPE);
25900 expect(input.getAttribute('value')).toEqual('John');
25902 expect(model.getText()).toEqual('John');
25907 This one shows how to debounce model changes. Model will be updated only 1 sec after last change.
25908 If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.
25910 <example name="ngModelOptions-directive-debounce" module="optionsExample">
25911 <file name="index.html">
25912 <div ng-controller="ExampleController">
25913 <form name="userForm">
25915 <input type="text" name="userName"
25916 ng-model="user.name"
25917 ng-model-options="{ debounce: 1000 }" />
25919 <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button>
25922 <pre>user.name = <span ng-bind="user.name"></span></pre>
25925 <file name="app.js">
25926 angular.module('optionsExample', [])
25927 .controller('ExampleController', ['$scope', function($scope) {
25928 $scope.user = { name: 'Igor' };
25933 This one shows how to bind to getter/setters:
25935 <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample">
25936 <file name="index.html">
25937 <div ng-controller="ExampleController">
25938 <form name="userForm">
25940 <input type="text" name="userName"
25941 ng-model="user.name"
25942 ng-model-options="{ getterSetter: true }" />
25945 <pre>user.name = <span ng-bind="user.name()"></span></pre>
25948 <file name="app.js">
25949 angular.module('getterSetterExample', [])
25950 .controller('ExampleController', ['$scope', function($scope) {
25951 var _name = 'Brian';
25953 name: function(newName) {
25954 // Note that newName can be undefined for two reasons:
25955 // 1. Because it is called as a getter and thus called with no arguments
25956 // 2. Because the property should actually be set to undefined. This happens e.g. if the
25957 // input is invalid
25958 return arguments.length ? (_name = newName) : _name;
25965 var ngModelOptionsDirective = function() {
25968 controller: ['$scope', '$attrs', function($scope, $attrs) {
25970 this.$options = copy($scope.$eval($attrs.ngModelOptions));
25971 // Allow adding/overriding bound events
25972 if (isDefined(this.$options.updateOn)) {
25973 this.$options.updateOnDefault = false;
25974 // extract "default" pseudo-event from list of events that can trigger a model update
25975 this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() {
25976 that.$options.updateOnDefault = true;
25980 this.$options.updateOnDefault = true;
25989 function addSetValidityMethod(context) {
25990 var ctrl = context.ctrl,
25991 $element = context.$element,
25994 unset = context.unset,
25995 $animate = context.$animate;
25997 classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS));
25999 ctrl.$setValidity = setValidity;
26001 function setValidity(validationErrorKey, state, controller) {
26002 if (isUndefined(state)) {
26003 createAndSet('$pending', validationErrorKey, controller);
26005 unsetAndCleanup('$pending', validationErrorKey, controller);
26007 if (!isBoolean(state)) {
26008 unset(ctrl.$error, validationErrorKey, controller);
26009 unset(ctrl.$$success, validationErrorKey, controller);
26012 unset(ctrl.$error, validationErrorKey, controller);
26013 set(ctrl.$$success, validationErrorKey, controller);
26015 set(ctrl.$error, validationErrorKey, controller);
26016 unset(ctrl.$$success, validationErrorKey, controller);
26019 if (ctrl.$pending) {
26020 cachedToggleClass(PENDING_CLASS, true);
26021 ctrl.$valid = ctrl.$invalid = undefined;
26022 toggleValidationCss('', null);
26024 cachedToggleClass(PENDING_CLASS, false);
26025 ctrl.$valid = isObjectEmpty(ctrl.$error);
26026 ctrl.$invalid = !ctrl.$valid;
26027 toggleValidationCss('', ctrl.$valid);
26030 // re-read the state as the set/unset methods could have
26031 // combined state in ctrl.$error[validationError] (used for forms),
26032 // where setting/unsetting only increments/decrements the value,
26033 // and does not replace it.
26035 if (ctrl.$pending && ctrl.$pending[validationErrorKey]) {
26036 combinedState = undefined;
26037 } else if (ctrl.$error[validationErrorKey]) {
26038 combinedState = false;
26039 } else if (ctrl.$$success[validationErrorKey]) {
26040 combinedState = true;
26042 combinedState = null;
26045 toggleValidationCss(validationErrorKey, combinedState);
26046 ctrl.$$parentForm.$setValidity(validationErrorKey, combinedState, ctrl);
26049 function createAndSet(name, value, controller) {
26053 set(ctrl[name], value, controller);
26056 function unsetAndCleanup(name, value, controller) {
26058 unset(ctrl[name], value, controller);
26060 if (isObjectEmpty(ctrl[name])) {
26061 ctrl[name] = undefined;
26065 function cachedToggleClass(className, switchValue) {
26066 if (switchValue && !classCache[className]) {
26067 $animate.addClass($element, className);
26068 classCache[className] = true;
26069 } else if (!switchValue && classCache[className]) {
26070 $animate.removeClass($element, className);
26071 classCache[className] = false;
26075 function toggleValidationCss(validationErrorKey, isValid) {
26076 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
26078 cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true);
26079 cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false);
26083 function isObjectEmpty(obj) {
26085 for (var prop in obj) {
26086 if (obj.hasOwnProperty(prop)) {
26096 * @name ngNonBindable
26101 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current
26102 * DOM element. This is useful if the element contains what appears to be Angular directives and
26103 * bindings but which should be ignored by Angular. This could be the case if you have a site that
26104 * displays snippets of code, for instance.
26109 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present,
26110 * but the one wrapped in `ngNonBindable` is left alone.
26114 <file name="index.html">
26115 <div>Normal: {{1 + 2}}</div>
26116 <div ng-non-bindable>Ignored: {{1 + 2}}</div>
26118 <file name="protractor.js" type="protractor">
26119 it('should check ng-non-bindable', function() {
26120 expect(element(by.binding('1 + 2')).getText()).toContain('3');
26121 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/);
26126 var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
26128 /* global jqLiteRemove */
26130 var ngOptionsMinErr = minErr('ngOptions');
26139 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
26140 * elements for the `<select>` element using the array or object obtained by evaluating the
26141 * `ngOptions` comprehension expression.
26143 * In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
26144 * similar result. However, `ngOptions` provides some benefits such as reducing memory and
26145 * increasing speed by not creating a new scope for each repeated instance, as well as providing
26146 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
26147 * comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound
26148 * to a non-string value. This is because an option element can only be bound to string values at
26151 * When an item in the `<select>` menu is selected, the array element or object property
26152 * represented by the selected option will be bound to the model identified by the `ngModel`
26155 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
26156 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
26157 * option. See example below for demonstration.
26159 * ## Complex Models (objects or collections)
26161 * By default, `ngModel` watches the model by reference, not value. This is important to know when
26162 * binding the select to a model that is an object or a collection.
26164 * One issue occurs if you want to preselect an option. For example, if you set
26165 * the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection,
26166 * because the objects are not identical. So by default, you should always reference the item in your collection
26167 * for preselections, e.g.: `$scope.selected = $scope.collection[3]`.
26169 * Another solution is to use a `track by` clause, because then `ngOptions` will track the identity
26170 * of the item not by reference, but by the result of the `track by` expression. For example, if your
26171 * collection items have an id property, you would `track by item.id`.
26173 * A different issue with objects or collections is that ngModel won't detect if an object property or
26174 * a collection item changes. For that reason, `ngOptions` additionally watches the model using
26175 * `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute.
26176 * This allows ngOptions to trigger a re-rendering of the options even if the actual object/collection
26177 * has not changed identity, but only a property on the object or an item in the collection changes.
26179 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection
26180 * if the model is an array). This means that changing a property deeper than the first level inside the
26181 * object/collection will not trigger a re-rendering.
26183 * ## `select` **`as`**
26185 * Using `select` **`as`** will bind the result of the `select` expression to the model, but
26186 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
26187 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression
26188 * is used, the result of that expression will be set as the value of the `option` and `select` elements.
26191 * ### `select` **`as`** and **`track by`**
26193 * <div class="alert alert-warning">
26194 * Be careful when using `select` **`as`** and **`track by`** in the same expression.
26197 * Given this array of items on the $scope:
26200 * $scope.items = [{
26203 * subItem: { name: 'aSubItem' }
26207 * subItem: { name: 'bSubItem' }
26214 * <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select>
26217 * $scope.selected = $scope.items[0];
26220 * but this will not work:
26223 * <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select>
26226 * $scope.selected = $scope.items[0].subItem;
26229 * In both examples, the **`track by`** expression is applied successfully to each `item` in the
26230 * `items` array. Because the selected option has been set programmatically in the controller, the
26231 * **`track by`** expression is also applied to the `ngModel` value. In the first example, the
26232 * `ngModel` value is `items[0]` and the **`track by`** expression evaluates to `items[0].id` with
26233 * no issue. In the second example, the `ngModel` value is `items[0].subItem` and the **`track by`**
26234 * expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value
26235 * is not matched against any `<option>` and the `<select>` appears as having no selected value.
26238 * @param {string} ngModel Assignable angular expression to data-bind to.
26239 * @param {string=} name Property name of the form under which the control is published.
26240 * @param {string=} required The control is considered valid only if value is entered.
26241 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
26242 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
26243 * `required` when you want to data-bind to the `required` attribute.
26244 * @param {comprehension_expression=} ngOptions in one of the following forms:
26246 * * for array data sources:
26247 * * `label` **`for`** `value` **`in`** `array`
26248 * * `select` **`as`** `label` **`for`** `value` **`in`** `array`
26249 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
26250 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array`
26251 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
26252 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
26253 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
26254 * (for including a filter with `track by`)
26255 * * for object data sources:
26256 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
26257 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
26258 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
26259 * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object`
26260 * * `select` **`as`** `label` **`group by`** `group`
26261 * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
26262 * * `select` **`as`** `label` **`disable when`** `disable`
26263 * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
26267 * * `array` / `object`: an expression which evaluates to an array / object to iterate over.
26268 * * `value`: local variable which will refer to each item in the `array` or each property value
26269 * of `object` during iteration.
26270 * * `key`: local variable which will refer to a property name in `object` during iteration.
26271 * * `label`: The result of this expression will be the label for `<option>` element. The
26272 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
26273 * * `select`: The result of this expression will be bound to the model of the parent `<select>`
26274 * element. If not specified, `select` expression will default to `value`.
26275 * * `group`: The result of this expression will be used to group options using the `<optgroup>`
26277 * * `disable`: The result of this expression will be used to disable the rendered `<option>`
26278 * element. Return `true` to disable.
26279 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be
26280 * used to identify the objects in the array. The `trackexpr` will most likely refer to the
26281 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved
26282 * even when the options are recreated (e.g. reloaded from the server).
26285 <example module="selectExample">
26286 <file name="index.html">
26288 angular.module('selectExample', [])
26289 .controller('ExampleController', ['$scope', function($scope) {
26291 {name:'black', shade:'dark'},
26292 {name:'white', shade:'light', notAnOption: true},
26293 {name:'red', shade:'dark'},
26294 {name:'blue', shade:'dark', notAnOption: true},
26295 {name:'yellow', shade:'light', notAnOption: false}
26297 $scope.myColor = $scope.colors[2]; // red
26300 <div ng-controller="ExampleController">
26302 <li ng-repeat="color in colors">
26303 <label>Name: <input ng-model="color.name"></label>
26304 <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label>
26305 <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button>
26308 <button ng-click="colors.push({})">add</button>
26312 <label>Color (null not allowed):
26313 <select ng-model="myColor" ng-options="color.name for color in colors"></select>
26315 <label>Color (null allowed):
26316 <span class="nullable">
26317 <select ng-model="myColor" ng-options="color.name for color in colors">
26318 <option value="">-- choose color --</option>
26320 </span></label><br/>
26322 <label>Color grouped by shade:
26323 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors">
26327 <label>Color grouped by shade, with some disabled:
26328 <select ng-model="myColor"
26329 ng-options="color.name group by color.shade disable when color.notAnOption for color in colors">
26335 Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>.
26338 Currently selected: {{ {selected_color:myColor} }}
26339 <div style="border:solid 1px black; height:20px"
26340 ng-style="{'background-color':myColor.name}">
26344 <file name="protractor.js" type="protractor">
26345 it('should check ng-options', function() {
26346 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red');
26347 element.all(by.model('myColor')).first().click();
26348 element.all(by.css('select[ng-model="myColor"] option')).first().click();
26349 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black');
26350 element(by.css('.nullable select[ng-model="myColor"]')).click();
26351 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click();
26352 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null');
26358 // jshint maxlen: false
26359 // //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555550000000006666666666666660000000777777777777777000000000000000888888888800000000000000000009999999999
26360 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]+?))?$/;
26361 // 1: value expression (valueFn)
26362 // 2: label expression (displayFn)
26363 // 3: group by expression (groupByFn)
26364 // 4: disable when expression (disableWhenFn)
26365 // 5: array item variable name
26366 // 6: object item key variable name
26367 // 7: object item value variable name
26368 // 8: collection expression
26369 // 9: track by expression
26370 // jshint maxlen: 100
26373 var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
26375 function parseOptionsExpression(optionsExp, selectElement, scope) {
26377 var match = optionsExp.match(NG_OPTIONS_REGEXP);
26379 throw ngOptionsMinErr('iexp',
26380 "Expected expression in form of " +
26381 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
26382 " but got '{0}'. Element: {1}",
26383 optionsExp, startingTag(selectElement));
26386 // Extract the parts from the ngOptions expression
26388 // The variable name for the value of the item in the collection
26389 var valueName = match[5] || match[7];
26390 // The variable name for the key of the item in the collection
26391 var keyName = match[6];
26393 // An expression that generates the viewValue for an option if there is a label expression
26394 var selectAs = / as /.test(match[0]) && match[1];
26395 // An expression that is used to track the id of each object in the options collection
26396 var trackBy = match[9];
26397 // An expression that generates the viewValue for an option if there is no label expression
26398 var valueFn = $parse(match[2] ? match[1] : valueName);
26399 var selectAsFn = selectAs && $parse(selectAs);
26400 var viewValueFn = selectAsFn || valueFn;
26401 var trackByFn = trackBy && $parse(trackBy);
26403 // Get the value by which we are going to track the option
26404 // if we have a trackFn then use that (passing scope and locals)
26405 // otherwise just hash the given viewValue
26406 var getTrackByValueFn = trackBy ?
26407 function(value, locals) { return trackByFn(scope, locals); } :
26408 function getHashOfValue(value) { return hashKey(value); };
26409 var getTrackByValue = function(value, key) {
26410 return getTrackByValueFn(value, getLocals(value, key));
26413 var displayFn = $parse(match[2] || match[1]);
26414 var groupByFn = $parse(match[3] || '');
26415 var disableWhenFn = $parse(match[4] || '');
26416 var valuesFn = $parse(match[8]);
26419 var getLocals = keyName ? function(value, key) {
26420 locals[keyName] = key;
26421 locals[valueName] = value;
26423 } : function(value) {
26424 locals[valueName] = value;
26429 function Option(selectValue, viewValue, label, group, disabled) {
26430 this.selectValue = selectValue;
26431 this.viewValue = viewValue;
26432 this.label = label;
26433 this.group = group;
26434 this.disabled = disabled;
26437 function getOptionValuesKeys(optionValues) {
26438 var optionValuesKeys;
26440 if (!keyName && isArrayLike(optionValues)) {
26441 optionValuesKeys = optionValues;
26443 // if object, extract keys, in enumeration order, unsorted
26444 optionValuesKeys = [];
26445 for (var itemKey in optionValues) {
26446 if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') {
26447 optionValuesKeys.push(itemKey);
26451 return optionValuesKeys;
26456 getTrackByValue: getTrackByValue,
26457 getWatchables: $parse(valuesFn, function(optionValues) {
26458 // Create a collection of things that we would like to watch (watchedArray)
26459 // so that they can all be watched using a single $watchCollection
26460 // that only runs the handler once if anything changes
26461 var watchedArray = [];
26462 optionValues = optionValues || [];
26464 var optionValuesKeys = getOptionValuesKeys(optionValues);
26465 var optionValuesLength = optionValuesKeys.length;
26466 for (var index = 0; index < optionValuesLength; index++) {
26467 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
26468 var value = optionValues[key];
26470 var locals = getLocals(optionValues[key], key);
26471 var selectValue = getTrackByValueFn(optionValues[key], locals);
26472 watchedArray.push(selectValue);
26474 // Only need to watch the displayFn if there is a specific label expression
26475 if (match[2] || match[1]) {
26476 var label = displayFn(scope, locals);
26477 watchedArray.push(label);
26480 // Only need to watch the disableWhenFn if there is a specific disable expression
26482 var disableWhen = disableWhenFn(scope, locals);
26483 watchedArray.push(disableWhen);
26486 return watchedArray;
26489 getOptions: function() {
26491 var optionItems = [];
26492 var selectValueMap = {};
26494 // The option values were already computed in the `getWatchables` fn,
26495 // which must have been called to trigger `getOptions`
26496 var optionValues = valuesFn(scope) || [];
26497 var optionValuesKeys = getOptionValuesKeys(optionValues);
26498 var optionValuesLength = optionValuesKeys.length;
26500 for (var index = 0; index < optionValuesLength; index++) {
26501 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
26502 var value = optionValues[key];
26503 var locals = getLocals(value, key);
26504 var viewValue = viewValueFn(scope, locals);
26505 var selectValue = getTrackByValueFn(viewValue, locals);
26506 var label = displayFn(scope, locals);
26507 var group = groupByFn(scope, locals);
26508 var disabled = disableWhenFn(scope, locals);
26509 var optionItem = new Option(selectValue, viewValue, label, group, disabled);
26511 optionItems.push(optionItem);
26512 selectValueMap[selectValue] = optionItem;
26516 items: optionItems,
26517 selectValueMap: selectValueMap,
26518 getOptionFromViewValue: function(value) {
26519 return selectValueMap[getTrackByValue(value)];
26521 getViewValueFromOption: function(option) {
26522 // If the viewValue could be an object that may be mutated by the application,
26523 // we need to make a copy and not return the reference to the value on the option.
26524 return trackBy ? angular.copy(option.viewValue) : option.viewValue;
26532 // we can't just jqLite('<option>') since jqLite is not smart enough
26533 // to create it in <select> and IE barfs otherwise.
26534 var optionTemplate = document.createElement('option'),
26535 optGroupTemplate = document.createElement('optgroup');
26538 function ngOptionsPostLink(scope, selectElement, attr, ctrls) {
26540 // if ngModel is not defined, we don't need to do anything
26541 var ngModelCtrl = ctrls[1];
26542 if (!ngModelCtrl) return;
26544 var selectCtrl = ctrls[0];
26545 var multiple = attr.multiple;
26547 // The emptyOption allows the application developer to provide their own custom "empty"
26548 // option when the viewValue does not match any of the option values.
26550 for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) {
26551 if (children[i].value === '') {
26552 emptyOption = children.eq(i);
26557 var providedEmptyOption = !!emptyOption;
26559 var unknownOption = jqLite(optionTemplate.cloneNode(false));
26560 unknownOption.val('?');
26563 var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope);
26566 var renderEmptyOption = function() {
26567 if (!providedEmptyOption) {
26568 selectElement.prepend(emptyOption);
26570 selectElement.val('');
26571 emptyOption.prop('selected', true); // needed for IE
26572 emptyOption.attr('selected', true);
26575 var removeEmptyOption = function() {
26576 if (!providedEmptyOption) {
26577 emptyOption.remove();
26582 var renderUnknownOption = function() {
26583 selectElement.prepend(unknownOption);
26584 selectElement.val('?');
26585 unknownOption.prop('selected', true); // needed for IE
26586 unknownOption.attr('selected', true);
26589 var removeUnknownOption = function() {
26590 unknownOption.remove();
26593 // Update the controller methods for multiple selectable options
26596 selectCtrl.writeValue = function writeNgOptionsValue(value) {
26597 var option = options.getOptionFromViewValue(value);
26599 if (option && !option.disabled) {
26600 if (selectElement[0].value !== option.selectValue) {
26601 removeUnknownOption();
26602 removeEmptyOption();
26604 selectElement[0].value = option.selectValue;
26605 option.element.selected = true;
26606 option.element.setAttribute('selected', 'selected');
26609 if (value === null || providedEmptyOption) {
26610 removeUnknownOption();
26611 renderEmptyOption();
26613 removeEmptyOption();
26614 renderUnknownOption();
26619 selectCtrl.readValue = function readNgOptionsValue() {
26621 var selectedOption = options.selectValueMap[selectElement.val()];
26623 if (selectedOption && !selectedOption.disabled) {
26624 removeEmptyOption();
26625 removeUnknownOption();
26626 return options.getViewValueFromOption(selectedOption);
26631 // If we are using `track by` then we must watch the tracked value on the model
26632 // since ngModel only watches for object identity change
26633 if (ngOptions.trackBy) {
26635 function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); },
26636 function() { ngModelCtrl.$render(); }
26642 ngModelCtrl.$isEmpty = function(value) {
26643 return !value || value.length === 0;
26647 selectCtrl.writeValue = function writeNgOptionsMultiple(value) {
26648 options.items.forEach(function(option) {
26649 option.element.selected = false;
26653 value.forEach(function(item) {
26654 var option = options.getOptionFromViewValue(item);
26655 if (option && !option.disabled) option.element.selected = true;
26661 selectCtrl.readValue = function readNgOptionsMultiple() {
26662 var selectedValues = selectElement.val() || [],
26665 forEach(selectedValues, function(value) {
26666 var option = options.selectValueMap[value];
26667 if (option && !option.disabled) selections.push(options.getViewValueFromOption(option));
26673 // If we are using `track by` then we must watch these tracked values on the model
26674 // since ngModel only watches for object identity change
26675 if (ngOptions.trackBy) {
26677 scope.$watchCollection(function() {
26678 if (isArray(ngModelCtrl.$viewValue)) {
26679 return ngModelCtrl.$viewValue.map(function(value) {
26680 return ngOptions.getTrackByValue(value);
26684 ngModelCtrl.$render();
26691 if (providedEmptyOption) {
26693 // we need to remove it before calling selectElement.empty() because otherwise IE will
26694 // remove the label from the element. wtf?
26695 emptyOption.remove();
26697 // compile the element since there might be bindings in it
26698 $compile(emptyOption)(scope);
26700 // remove the class, which is added automatically because we recompile the element and it
26701 // becomes the compilation root
26702 emptyOption.removeClass('ng-scope');
26704 emptyOption = jqLite(optionTemplate.cloneNode(false));
26707 // We need to do this here to ensure that the options object is defined
26708 // when we first hit it in writeNgOptionsValue
26711 // We will re-render the option elements if the option values or labels change
26712 scope.$watchCollection(ngOptions.getWatchables, updateOptions);
26714 // ------------------------------------------------------------------ //
26717 function updateOptionElement(option, element) {
26718 option.element = element;
26719 element.disabled = option.disabled;
26720 // NOTE: The label must be set before the value, otherwise IE10/11/EDGE create unresponsive
26721 // selects in certain circumstances when multiple selects are next to each other and display
26722 // the option list in listbox style, i.e. the select is [multiple], or specifies a [size].
26723 // See https://github.com/angular/angular.js/issues/11314 for more info.
26724 // This is unfortunately untestable with unit / e2e tests
26725 if (option.label !== element.label) {
26726 element.label = option.label;
26727 element.textContent = option.label;
26729 if (option.value !== element.value) element.value = option.selectValue;
26732 function addOrReuseElement(parent, current, type, templateElement) {
26734 // Check whether we can reuse the next element
26735 if (current && lowercase(current.nodeName) === type) {
26736 // The next element is the right type so reuse it
26739 // The next element is not the right type so create a new one
26740 element = templateElement.cloneNode(false);
26742 // There are no more elements so just append it to the select
26743 parent.appendChild(element);
26745 // The next element is not a group so insert the new one
26746 parent.insertBefore(element, current);
26753 function removeExcessElements(current) {
26756 next = current.nextSibling;
26757 jqLiteRemove(current);
26763 function skipEmptyAndUnknownOptions(current) {
26764 var emptyOption_ = emptyOption && emptyOption[0];
26765 var unknownOption_ = unknownOption && unknownOption[0];
26767 // We cannot rely on the extracted empty option being the same as the compiled empty option,
26768 // because the compiled empty option might have been replaced by a comment because
26769 // it had an "element" transclusion directive on it (such as ngIf)
26770 if (emptyOption_ || unknownOption_) {
26772 (current === emptyOption_ ||
26773 current === unknownOption_ ||
26774 current.nodeType === NODE_TYPE_COMMENT ||
26775 current.value === '')) {
26776 current = current.nextSibling;
26783 function updateOptions() {
26785 var previousValue = options && selectCtrl.readValue();
26787 options = ngOptions.getOptions();
26790 var currentElement = selectElement[0].firstChild;
26792 // Ensure that the empty option is always there if it was explicitly provided
26793 if (providedEmptyOption) {
26794 selectElement.prepend(emptyOption);
26797 currentElement = skipEmptyAndUnknownOptions(currentElement);
26799 options.items.forEach(function updateOption(option) {
26804 if (option.group) {
26806 // This option is to live in a group
26807 // See if we have already created this group
26808 group = groupMap[option.group];
26812 // We have not already created this group
26813 groupElement = addOrReuseElement(selectElement[0],
26817 // Move to the next element
26818 currentElement = groupElement.nextSibling;
26820 // Update the label on the group element
26821 groupElement.label = option.group;
26823 // Store it for use later
26824 group = groupMap[option.group] = {
26825 groupElement: groupElement,
26826 currentOptionElement: groupElement.firstChild
26831 // So now we have a group for this option we add the option to the group
26832 optionElement = addOrReuseElement(group.groupElement,
26833 group.currentOptionElement,
26836 updateOptionElement(option, optionElement);
26837 // Move to the next element
26838 group.currentOptionElement = optionElement.nextSibling;
26842 // This option is not in a group
26843 optionElement = addOrReuseElement(selectElement[0],
26847 updateOptionElement(option, optionElement);
26848 // Move to the next element
26849 currentElement = optionElement.nextSibling;
26854 // Now remove all excess options and group
26855 Object.keys(groupMap).forEach(function(key) {
26856 removeExcessElements(groupMap[key].currentOptionElement);
26858 removeExcessElements(currentElement);
26860 ngModelCtrl.$render();
26862 // Check to see if the value has changed due to the update to the options
26863 if (!ngModelCtrl.$isEmpty(previousValue)) {
26864 var nextValue = selectCtrl.readValue();
26865 if (ngOptions.trackBy ? !equals(previousValue, nextValue) : previousValue !== nextValue) {
26866 ngModelCtrl.$setViewValue(nextValue);
26867 ngModelCtrl.$render();
26877 require: ['select', '?ngModel'],
26879 pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) {
26880 // Deactivate the SelectController.register method to prevent
26881 // option directives from accidentally registering themselves
26882 // (and unwanted $destroy handlers etc.)
26883 ctrls[0].registerOption = noop;
26885 post: ngOptionsPostLink
26892 * @name ngPluralize
26896 * `ngPluralize` is a directive that displays messages according to en-US localization rules.
26897 * These rules are bundled with angular.js, but can be overridden
26898 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive
26899 * by specifying the mappings between
26900 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
26901 * and the strings to be displayed.
26903 * # Plural categories and explicit number rules
26905 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
26906 * in Angular's default en-US locale: "one" and "other".
26908 * While a plural category may match many numbers (for example, in en-US locale, "other" can match
26909 * any number that is not 1), an explicit number rule can only match one number. For example, the
26910 * explicit number rule for "3" matches the number 3. There are examples of plural categories
26911 * and explicit number rules throughout the rest of this documentation.
26913 * # Configuring ngPluralize
26914 * You configure ngPluralize by providing 2 attributes: `count` and `when`.
26915 * You can also provide an optional attribute, `offset`.
26917 * The value of the `count` attribute can be either a string or an {@link guide/expression
26918 * Angular expression}; these are evaluated on the current scope for its bound value.
26920 * The `when` attribute specifies the mappings between plural categories and the actual
26921 * string to be displayed. The value of the attribute should be a JSON object.
26923 * The following example shows how to configure ngPluralize:
26926 * <ng-pluralize count="personCount"
26927 when="{'0': 'Nobody is viewing.',
26928 * 'one': '1 person is viewing.',
26929 * 'other': '{} people are viewing.'}">
26933 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not
26934 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing"
26935 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for
26936 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can
26937 * show "a dozen people are viewing".
26939 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted
26940 * into pluralized strings. In the previous example, Angular will replace `{}` with
26941 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
26942 * for <span ng-non-bindable>{{numberExpression}}</span>.
26944 * If no rule is defined for a category, then an empty string is displayed and a warning is generated.
26945 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`.
26947 * # Configuring ngPluralize with offset
26948 * The `offset` attribute allows further customization of pluralized text, which can result in
26949 * a better user experience. For example, instead of the message "4 people are viewing this document",
26950 * you might display "John, Kate and 2 others are viewing this document".
26951 * The offset attribute allows you to offset a number by any desired value.
26952 * Let's take a look at an example:
26955 * <ng-pluralize count="personCount" offset=2
26956 * when="{'0': 'Nobody is viewing.',
26957 * '1': '{{person1}} is viewing.',
26958 * '2': '{{person1}} and {{person2}} are viewing.',
26959 * 'one': '{{person1}}, {{person2}} and one other person are viewing.',
26960 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
26964 * Notice that we are still using two plural categories(one, other), but we added
26965 * three explicit number rules 0, 1 and 2.
26966 * When one person, perhaps John, views the document, "John is viewing" will be shown.
26967 * When three people view the document, no explicit number rule is found, so
26968 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category.
26969 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing"
26972 * Note that when you specify offsets, you must provide explicit number rules for
26973 * numbers from 0 up to and including the offset. If you use an offset of 3, for example,
26974 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for
26975 * plural categories "one" and "other".
26977 * @param {string|expression} count The variable to be bound to.
26978 * @param {string} when The mapping between plural category to its corresponding strings.
26979 * @param {number=} offset Offset to deduct from the total number.
26982 <example module="pluralizeExample">
26983 <file name="index.html">
26985 angular.module('pluralizeExample', [])
26986 .controller('ExampleController', ['$scope', function($scope) {
26987 $scope.person1 = 'Igor';
26988 $scope.person2 = 'Misko';
26989 $scope.personCount = 1;
26992 <div ng-controller="ExampleController">
26993 <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/>
26994 <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/>
26995 <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/>
26997 <!--- Example with simple pluralization rules for en locale --->
26999 <ng-pluralize count="personCount"
27000 when="{'0': 'Nobody is viewing.',
27001 'one': '1 person is viewing.',
27002 'other': '{} people are viewing.'}">
27003 </ng-pluralize><br>
27005 <!--- Example with offset --->
27007 <ng-pluralize count="personCount" offset=2
27008 when="{'0': 'Nobody is viewing.',
27009 '1': '{{person1}} is viewing.',
27010 '2': '{{person1}} and {{person2}} are viewing.',
27011 'one': '{{person1}}, {{person2}} and one other person are viewing.',
27012 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
27016 <file name="protractor.js" type="protractor">
27017 it('should show correct pluralized string', function() {
27018 var withoutOffset = element.all(by.css('ng-pluralize')).get(0);
27019 var withOffset = element.all(by.css('ng-pluralize')).get(1);
27020 var countInput = element(by.model('personCount'));
27022 expect(withoutOffset.getText()).toEqual('1 person is viewing.');
27023 expect(withOffset.getText()).toEqual('Igor is viewing.');
27025 countInput.clear();
27026 countInput.sendKeys('0');
27028 expect(withoutOffset.getText()).toEqual('Nobody is viewing.');
27029 expect(withOffset.getText()).toEqual('Nobody is viewing.');
27031 countInput.clear();
27032 countInput.sendKeys('2');
27034 expect(withoutOffset.getText()).toEqual('2 people are viewing.');
27035 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.');
27037 countInput.clear();
27038 countInput.sendKeys('3');
27040 expect(withoutOffset.getText()).toEqual('3 people are viewing.');
27041 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.');
27043 countInput.clear();
27044 countInput.sendKeys('4');
27046 expect(withoutOffset.getText()).toEqual('4 people are viewing.');
27047 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.');
27049 it('should show data-bound names', function() {
27050 var withOffset = element.all(by.css('ng-pluralize')).get(1);
27051 var personCount = element(by.model('personCount'));
27052 var person1 = element(by.model('person1'));
27053 var person2 = element(by.model('person2'));
27054 personCount.clear();
27055 personCount.sendKeys('4');
27057 person1.sendKeys('Di');
27059 person2.sendKeys('Vojta');
27060 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.');
27065 var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) {
27067 IS_WHEN = /^when(Minus)?(.+)$/;
27070 link: function(scope, element, attr) {
27071 var numberExp = attr.count,
27072 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs
27073 offset = attr.offset || 0,
27074 whens = scope.$eval(whenExp) || {},
27076 startSymbol = $interpolate.startSymbol(),
27077 endSymbol = $interpolate.endSymbol(),
27078 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol,
27079 watchRemover = angular.noop,
27082 forEach(attr, function(expression, attributeName) {
27083 var tmpMatch = IS_WHEN.exec(attributeName);
27085 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]);
27086 whens[whenKey] = element.attr(attr.$attr[attributeName]);
27089 forEach(whens, function(expression, key) {
27090 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement));
27094 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) {
27095 var count = parseFloat(newVal);
27096 var countIsNaN = isNaN(count);
27098 if (!countIsNaN && !(count in whens)) {
27099 // If an explicit number rule such as 1, 2, 3... is defined, just use it.
27100 // Otherwise, check it against pluralization rules in $locale service.
27101 count = $locale.pluralCat(count - offset);
27104 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch.
27105 // In JS `NaN !== NaN`, so we have to exlicitly check.
27106 if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) {
27108 var whenExpFn = whensExpFns[count];
27109 if (isUndefined(whenExpFn)) {
27110 if (newVal != null) {
27111 $log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp);
27113 watchRemover = noop;
27114 updateElementText();
27116 watchRemover = scope.$watch(whenExpFn, updateElementText);
27122 function updateElementText(newText) {
27123 element.text(newText || '');
27135 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template
27136 * instance gets its own scope, where the given loop variable is set to the current collection item,
27137 * and `$index` is set to the item index or key.
27139 * Special properties are exposed on the local scope of each template instance, including:
27141 * | Variable | Type | Details |
27142 * |-----------|-----------------|-----------------------------------------------------------------------------|
27143 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) |
27144 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. |
27145 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. |
27146 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. |
27147 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). |
27148 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). |
27150 * <div class="alert alert-info">
27151 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
27152 * This may be useful when, for instance, nesting ngRepeats.
27156 * # Iterating over object properties
27158 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following
27162 * <div ng-repeat="(key, value) in myObj"> ... </div>
27165 * You need to be aware that the JavaScript specification does not define the order of keys
27166 * returned for an object. (To mitigate this in Angular 1.3 the `ngRepeat` directive
27167 * used to sort the keys alphabetically.)
27169 * Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser
27170 * when running `for key in myObj`. It seems that browsers generally follow the strategy of providing
27171 * keys in the order in which they were defined, although there are exceptions when keys are deleted
27172 * 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).
27174 * If this is not desired, the recommended workaround is to convert your object into an array
27175 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
27176 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter)
27177 * or implement a `$watch` on the object yourself.
27180 * # Tracking and Duplicates
27182 * `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in
27183 * the collection. When a change happens, ngRepeat then makes the corresponding changes to the DOM:
27185 * * When an item is added, a new instance of the template is added to the DOM.
27186 * * When an item is removed, its template instance is removed from the DOM.
27187 * * When items are reordered, their respective templates are reordered in the DOM.
27189 * To minimize creation of DOM elements, `ngRepeat` uses a function
27190 * to "keep track" of all items in the collection and their corresponding DOM elements.
27191 * For example, if an item is added to the collection, ngRepeat will know that all other items
27192 * already have DOM elements, and will not re-render them.
27194 * The default tracking function (which tracks items by their identity) does not allow
27195 * duplicate items in arrays. This is because when there are duplicates, it is not possible
27196 * to maintain a one-to-one mapping between collection items and DOM elements.
27198 * If you do need to repeat duplicate items, you can substitute the default tracking behavior
27199 * with your own using the `track by` expression.
27201 * For example, you may track items by the index of each item in the collection, using the
27202 * special scope property `$index`:
27204 * <div ng-repeat="n in [42, 42, 43, 43] track by $index">
27209 * You may also use arbitrary expressions in `track by`, including references to custom functions
27212 * <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)">
27217 * <div class="alert alert-success">
27218 * If you are working with objects that have an identifier property, you should track
27219 * by the identifier instead of the whole object. Should you reload your data later, `ngRepeat`
27220 * will not have to rebuild the DOM elements for items it has already rendered, even if the
27221 * JavaScript objects in the collection have been substituted for new ones. For large collections,
27222 * this signifincantly improves rendering performance. If you don't have a unique identifier,
27223 * `track by $index` can also provide a performance boost.
27226 * <div ng-repeat="model in collection track by model.id">
27231 * When no `track by` expression is provided, it is equivalent to tracking by the built-in
27232 * `$id` function, which tracks items by their identity:
27234 * <div ng-repeat="obj in collection track by $id(obj)">
27239 * <div class="alert alert-warning">
27240 * **Note:** `track by` must always be the last expression:
27243 * <div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id">
27248 * # Special repeat start and end points
27249 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
27250 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively.
27251 * 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)
27252 * up to and including the ending HTML tag where **ng-repeat-end** is placed.
27254 * The example below makes use of this feature:
27256 * <header ng-repeat-start="item in items">
27257 * Header {{ item }}
27259 * <div class="body">
27262 * <footer ng-repeat-end>
27263 * Footer {{ item }}
27267 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to:
27272 * <div class="body">
27281 * <div class="body">
27289 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such
27290 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**).
27293 * **.enter** - when a new item is added to the list or when an item is revealed after a filter
27295 * **.leave** - when an item is removed from the list or when an item is filtered out
27297 * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered
27302 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These
27303 * formats are currently supported:
27305 * * `variable in expression` – where variable is the user defined loop variable and `expression`
27306 * is a scope expression giving the collection to enumerate.
27308 * For example: `album in artist.albums`.
27310 * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers,
27311 * and `expression` is the scope expression giving the collection to enumerate.
27313 * For example: `(name, age) in {'adam':10, 'amalie':12}`.
27315 * * `variable in expression track by tracking_expression` – You can also provide an optional tracking expression
27316 * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression
27317 * is specified, ng-repeat associates elements by identity. It is an error to have
27318 * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are
27319 * mapped to the same DOM element, which is not possible.)
27321 * Note that the tracking expression must come last, after any filters, and the alias expression.
27323 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements
27324 * will be associated by item identity in the array.
27326 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique
27327 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements
27328 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM
27329 * element in the same way in the DOM.
27331 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this
27332 * case the object identity does not matter. Two objects are considered equivalent as long as their `id`
27333 * property is same.
27335 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter
27336 * to items in conjunction with a tracking expression.
27338 * * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the
27339 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message
27340 * when a filter is active on the repeater, but the filtered result set is empty.
27342 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after
27343 * the items have been processed through the filter.
27345 * 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
27346 * (and not as operator, inside an expression).
27348 * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` .
27351 * This example initializes the scope to a list of names and
27352 * then uses `ngRepeat` to display every person:
27353 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27354 <file name="index.html">
27355 <div ng-init="friends = [
27356 {name:'John', age:25, gender:'boy'},
27357 {name:'Jessie', age:30, gender:'girl'},
27358 {name:'Johanna', age:28, gender:'girl'},
27359 {name:'Joy', age:15, gender:'girl'},
27360 {name:'Mary', age:28, gender:'girl'},
27361 {name:'Peter', age:95, gender:'boy'},
27362 {name:'Sebastian', age:50, gender:'boy'},
27363 {name:'Erika', age:27, gender:'girl'},
27364 {name:'Patrick', age:40, gender:'boy'},
27365 {name:'Samantha', age:60, gender:'girl'}
27367 I have {{friends.length}} friends. They are:
27368 <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" />
27369 <ul class="example-animate-container">
27370 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results">
27371 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
27373 <li class="animate-repeat" ng-if="results.length == 0">
27374 <strong>No results found...</strong>
27379 <file name="animations.css">
27380 .example-animate-container {
27382 border:1px solid black;
27391 box-sizing:border-box;
27394 .animate-repeat.ng-move,
27395 .animate-repeat.ng-enter,
27396 .animate-repeat.ng-leave {
27397 transition:all linear 0.5s;
27400 .animate-repeat.ng-leave.ng-leave-active,
27401 .animate-repeat.ng-move,
27402 .animate-repeat.ng-enter {
27407 .animate-repeat.ng-leave,
27408 .animate-repeat.ng-move.ng-move-active,
27409 .animate-repeat.ng-enter.ng-enter-active {
27414 <file name="protractor.js" type="protractor">
27415 var friends = element.all(by.repeater('friend in friends'));
27417 it('should render initial data set', function() {
27418 expect(friends.count()).toBe(10);
27419 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.');
27420 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.');
27421 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.');
27422 expect(element(by.binding('friends.length')).getText())
27423 .toMatch("I have 10 friends. They are:");
27426 it('should update repeater when filter predicate changes', function() {
27427 expect(friends.count()).toBe(10);
27429 element(by.model('q')).sendKeys('ma');
27431 expect(friends.count()).toBe(2);
27432 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.');
27433 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.');
27438 var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
27439 var NG_REMOVED = '$$NG_REMOVED';
27440 var ngRepeatMinErr = minErr('ngRepeat');
27442 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) {
27443 // TODO(perf): generate setters to shave off ~40ms or 1-1.5%
27444 scope[valueIdentifier] = value;
27445 if (keyIdentifier) scope[keyIdentifier] = key;
27446 scope.$index = index;
27447 scope.$first = (index === 0);
27448 scope.$last = (index === (arrayLength - 1));
27449 scope.$middle = !(scope.$first || scope.$last);
27450 // jshint bitwise: false
27451 scope.$odd = !(scope.$even = (index&1) === 0);
27452 // jshint bitwise: true
27455 var getBlockStart = function(block) {
27456 return block.clone[0];
27459 var getBlockEnd = function(block) {
27460 return block.clone[block.clone.length - 1];
27466 multiElement: true,
27467 transclude: 'element',
27471 compile: function ngRepeatCompile($element, $attr) {
27472 var expression = $attr.ngRepeat;
27473 var ngRepeatEndComment = document.createComment(' end ngRepeat: ' + expression + ' ');
27475 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*$/);
27478 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.",
27482 var lhs = match[1];
27483 var rhs = match[2];
27484 var aliasAs = match[3];
27485 var trackByExp = match[4];
27487 match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);
27490 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.",
27493 var valueIdentifier = match[3] || match[1];
27494 var keyIdentifier = match[2];
27496 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) ||
27497 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) {
27498 throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.",
27502 var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn;
27503 var hashFnLocals = {$id: hashKey};
27506 trackByExpGetter = $parse(trackByExp);
27508 trackByIdArrayFn = function(key, value) {
27509 return hashKey(value);
27511 trackByIdObjFn = function(key) {
27516 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) {
27518 if (trackByExpGetter) {
27519 trackByIdExpFn = function(key, value, index) {
27520 // assign key, value, and $index to the locals so that they can be used in hash functions
27521 if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
27522 hashFnLocals[valueIdentifier] = value;
27523 hashFnLocals.$index = index;
27524 return trackByExpGetter($scope, hashFnLocals);
27528 // Store a list of elements from previous run. This is a hash where key is the item from the
27529 // iterator, and the value is objects with following properties.
27530 // - scope: bound scope
27531 // - element: previous element.
27532 // - index: position
27534 // We are using no-proto object so that we don't need to guard against inherited props via
27536 var lastBlockMap = createMap();
27539 $scope.$watchCollection(rhs, function ngRepeatAction(collection) {
27541 previousNode = $element[0], // node that cloned nodes should be inserted after
27542 // initialized to the comment node anchor
27544 // Same as lastBlockMap but it has the current state. It will become the
27545 // lastBlockMap on the next iteration.
27546 nextBlockMap = createMap(),
27548 key, value, // key/value of iteration
27552 block, // last object information {scope, element, id}
27557 $scope[aliasAs] = collection;
27560 if (isArrayLike(collection)) {
27561 collectionKeys = collection;
27562 trackByIdFn = trackByIdExpFn || trackByIdArrayFn;
27564 trackByIdFn = trackByIdExpFn || trackByIdObjFn;
27565 // if object, extract keys, in enumeration order, unsorted
27566 collectionKeys = [];
27567 for (var itemKey in collection) {
27568 if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') {
27569 collectionKeys.push(itemKey);
27574 collectionLength = collectionKeys.length;
27575 nextBlockOrder = new Array(collectionLength);
27577 // locate existing items
27578 for (index = 0; index < collectionLength; index++) {
27579 key = (collection === collectionKeys) ? index : collectionKeys[index];
27580 value = collection[key];
27581 trackById = trackByIdFn(key, value, index);
27582 if (lastBlockMap[trackById]) {
27583 // found previously seen block
27584 block = lastBlockMap[trackById];
27585 delete lastBlockMap[trackById];
27586 nextBlockMap[trackById] = block;
27587 nextBlockOrder[index] = block;
27588 } else if (nextBlockMap[trackById]) {
27589 // if collision detected. restore lastBlockMap and throw an error
27590 forEach(nextBlockOrder, function(block) {
27591 if (block && block.scope) lastBlockMap[block.id] = block;
27593 throw ngRepeatMinErr('dupes',
27594 "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}",
27595 expression, trackById, value);
27597 // new never before seen block
27598 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined};
27599 nextBlockMap[trackById] = true;
27603 // remove leftover items
27604 for (var blockKey in lastBlockMap) {
27605 block = lastBlockMap[blockKey];
27606 elementsToRemove = getBlockNodes(block.clone);
27607 $animate.leave(elementsToRemove);
27608 if (elementsToRemove[0].parentNode) {
27609 // if the element was not removed yet because of pending animation, mark it as deleted
27610 // so that we can ignore it later
27611 for (index = 0, length = elementsToRemove.length; index < length; index++) {
27612 elementsToRemove[index][NG_REMOVED] = true;
27615 block.scope.$destroy();
27618 // we are not using forEach for perf reasons (trying to avoid #call)
27619 for (index = 0; index < collectionLength; index++) {
27620 key = (collection === collectionKeys) ? index : collectionKeys[index];
27621 value = collection[key];
27622 block = nextBlockOrder[index];
27625 // if we have already seen this object, then we need to reuse the
27626 // associated scope/element
27628 nextNode = previousNode;
27630 // skip nodes that are already pending removal via leave animation
27632 nextNode = nextNode.nextSibling;
27633 } while (nextNode && nextNode[NG_REMOVED]);
27635 if (getBlockStart(block) != nextNode) {
27636 // existing item which got moved
27637 $animate.move(getBlockNodes(block.clone), null, jqLite(previousNode));
27639 previousNode = getBlockEnd(block);
27640 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
27642 // new item which we don't know about
27643 $transclude(function ngRepeatTransclude(clone, scope) {
27644 block.scope = scope;
27645 // http://jsperf.com/clone-vs-createcomment
27646 var endNode = ngRepeatEndComment.cloneNode(false);
27647 clone[clone.length++] = endNode;
27649 // TODO(perf): support naked previousNode in `enter` to avoid creation of jqLite wrapper?
27650 $animate.enter(clone, null, jqLite(previousNode));
27651 previousNode = endNode;
27652 // Note: We only need the first/last node of the cloned nodes.
27653 // However, we need to keep the reference to the jqlite wrapper as it might be changed later
27654 // by a directive with templateUrl when its template arrives.
27655 block.clone = clone;
27656 nextBlockMap[block.id] = block;
27657 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
27661 lastBlockMap = nextBlockMap;
27668 var NG_HIDE_CLASS = 'ng-hide';
27669 var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
27676 * The `ngShow` directive shows or hides the given HTML element based on the expression
27677 * provided to the `ngShow` attribute. The element is shown or hidden by removing or adding
27678 * the `.ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined
27679 * in AngularJS and sets the display style to none (using an !important flag).
27680 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
27683 * <!-- when $scope.myValue is truthy (element is visible) -->
27684 * <div ng-show="myValue"></div>
27686 * <!-- when $scope.myValue is falsy (element is hidden) -->
27687 * <div ng-show="myValue" class="ng-hide"></div>
27690 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added to the class
27691 * attribute on the element causing it to become hidden. When truthy, the `.ng-hide` CSS class is removed
27692 * from the element causing the element not to appear hidden.
27694 * ## Why is !important used?
27696 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector
27697 * can be easily overridden by heavier selectors. For example, something as simple
27698 * as changing the display style on a HTML list item would make hidden elements appear visible.
27699 * This also becomes a bigger issue when dealing with CSS frameworks.
27701 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector
27702 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the
27703 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code.
27705 * ### Overriding `.ng-hide`
27707 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
27708 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
27709 * class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope
27710 * with extra animation classes that can be added.
27713 * .ng-hide:not(.ng-hide-animate) {
27714 * /* this is just another form of hiding an element */
27715 * display: block!important;
27716 * position: absolute;
27722 * By default you don't need to override in CSS anything and the animations will work around the display style.
27724 * ## A note about animations with `ngShow`
27726 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
27727 * is true and false. This system works like the animation system present with ngClass except that
27728 * you must also include the !important flag to override the display property
27729 * so that you can perform an animation when the element is hidden during the time of the animation.
27733 * //a working example can be found at the bottom of this page
27735 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
27736 * /* this is required as of 1.3x to properly
27737 * apply all styling in a show/hide animation */
27738 * transition: 0s linear all;
27741 * .my-element.ng-hide-add-active,
27742 * .my-element.ng-hide-remove-active {
27743 * /* the transition is defined in the active class */
27744 * transition: 1s linear all;
27747 * .my-element.ng-hide-add { ... }
27748 * .my-element.ng-hide-add.ng-hide-add-active { ... }
27749 * .my-element.ng-hide-remove { ... }
27750 * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
27753 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display
27754 * property to block during animation states--ngAnimate will handle the style toggling automatically for you.
27757 * addClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a truthy value and the just before contents are set to visible
27758 * removeClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden
27761 * @param {expression} ngShow If the {@link guide/expression expression} is truthy
27762 * then the element is shown or hidden respectively.
27765 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27766 <file name="index.html">
27767 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br/>
27770 <div class="check-element animate-show" ng-show="checked">
27771 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked.
27776 <div class="check-element animate-show" ng-hide="checked">
27777 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked.
27781 <file name="glyphicons.css">
27782 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
27784 <file name="animations.css">
27789 border: 1px solid black;
27793 .animate-show.ng-hide-add, .animate-show.ng-hide-remove {
27794 transition: all linear 0.5s;
27797 .animate-show.ng-hide {
27805 border: 1px solid black;
27809 <file name="protractor.js" type="protractor">
27810 var thumbsUp = element(by.css('span.glyphicon-thumbs-up'));
27811 var thumbsDown = element(by.css('span.glyphicon-thumbs-down'));
27813 it('should check ng-show / ng-hide', function() {
27814 expect(thumbsUp.isDisplayed()).toBeFalsy();
27815 expect(thumbsDown.isDisplayed()).toBeTruthy();
27817 element(by.model('checked')).click();
27819 expect(thumbsUp.isDisplayed()).toBeTruthy();
27820 expect(thumbsDown.isDisplayed()).toBeFalsy();
27825 var ngShowDirective = ['$animate', function($animate) {
27828 multiElement: true,
27829 link: function(scope, element, attr) {
27830 scope.$watch(attr.ngShow, function ngShowWatchAction(value) {
27831 // we're adding a temporary, animation-specific class for ng-hide since this way
27832 // we can control when the element is actually displayed on screen without having
27833 // to have a global/greedy CSS selector that breaks when other animations are run.
27834 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845
27835 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, {
27836 tempClasses: NG_HIDE_IN_PROGRESS_CLASS
27850 * The `ngHide` directive shows or hides the given HTML element based on the expression
27851 * provided to the `ngHide` attribute. The element is shown or hidden by removing or adding
27852 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined
27853 * in AngularJS and sets the display style to none (using an !important flag).
27854 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
27857 * <!-- when $scope.myValue is truthy (element is hidden) -->
27858 * <div ng-hide="myValue" class="ng-hide"></div>
27860 * <!-- when $scope.myValue is falsy (element is visible) -->
27861 * <div ng-hide="myValue"></div>
27864 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added to the class
27865 * attribute on the element causing it to become hidden. When falsy, the `.ng-hide` CSS class is removed
27866 * from the element causing the element not to appear hidden.
27868 * ## Why is !important used?
27870 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector
27871 * can be easily overridden by heavier selectors. For example, something as simple
27872 * as changing the display style on a HTML list item would make hidden elements appear visible.
27873 * This also becomes a bigger issue when dealing with CSS frameworks.
27875 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector
27876 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the
27877 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code.
27879 * ### Overriding `.ng-hide`
27881 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
27882 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
27887 * /* this is just another form of hiding an element */
27888 * display: block!important;
27889 * position: absolute;
27895 * By default you don't need to override in CSS anything and the animations will work around the display style.
27897 * ## A note about animations with `ngHide`
27899 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
27900 * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide`
27901 * CSS class is added and removed for you instead of your own CSS class.
27905 * //a working example can be found at the bottom of this page
27907 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
27908 * transition: 0.5s linear all;
27911 * .my-element.ng-hide-add { ... }
27912 * .my-element.ng-hide-add.ng-hide-add-active { ... }
27913 * .my-element.ng-hide-remove { ... }
27914 * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
27917 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display
27918 * property to block during animation states--ngAnimate will handle the style toggling automatically for you.
27921 * removeClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden
27922 * addClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a non truthy value and just before the contents are set to visible
27925 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then
27926 * the element is shown or hidden respectively.
27929 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27930 <file name="index.html">
27931 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br/>
27934 <div class="check-element animate-hide" ng-show="checked">
27935 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked.
27940 <div class="check-element animate-hide" ng-hide="checked">
27941 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked.
27945 <file name="glyphicons.css">
27946 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
27948 <file name="animations.css">
27950 transition: all linear 0.5s;
27954 border: 1px solid black;
27958 .animate-hide.ng-hide {
27966 border: 1px solid black;
27970 <file name="protractor.js" type="protractor">
27971 var thumbsUp = element(by.css('span.glyphicon-thumbs-up'));
27972 var thumbsDown = element(by.css('span.glyphicon-thumbs-down'));
27974 it('should check ng-show / ng-hide', function() {
27975 expect(thumbsUp.isDisplayed()).toBeFalsy();
27976 expect(thumbsDown.isDisplayed()).toBeTruthy();
27978 element(by.model('checked')).click();
27980 expect(thumbsUp.isDisplayed()).toBeTruthy();
27981 expect(thumbsDown.isDisplayed()).toBeFalsy();
27986 var ngHideDirective = ['$animate', function($animate) {
27989 multiElement: true,
27990 link: function(scope, element, attr) {
27991 scope.$watch(attr.ngHide, function ngHideWatchAction(value) {
27992 // The comment inside of the ngShowDirective explains why we add and
27993 // remove a temporary class for the show/hide animation
27994 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, {
27995 tempClasses: NG_HIDE_IN_PROGRESS_CLASS
28008 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally.
28011 * @param {expression} ngStyle
28013 * {@link guide/expression Expression} which evals to an
28014 * object whose keys are CSS style names and values are corresponding values for those CSS
28017 * Since some CSS style names are not valid keys for an object, they must be quoted.
28018 * See the 'background-color' style in the example below.
28022 <file name="index.html">
28023 <input type="button" value="set color" ng-click="myStyle={color:'red'}">
28024 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}">
28025 <input type="button" value="clear" ng-click="myStyle={}">
28027 <span ng-style="myStyle">Sample Text</span>
28028 <pre>myStyle={{myStyle}}</pre>
28030 <file name="style.css">
28035 <file name="protractor.js" type="protractor">
28036 var colorSpan = element(by.css('span'));
28038 it('should check ng-style', function() {
28039 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
28040 element(by.css('input[value=\'set color\']')).click();
28041 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)');
28042 element(by.css('input[value=clear]')).click();
28043 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
28048 var ngStyleDirective = ngDirective(function(scope, element, attr) {
28049 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
28050 if (oldStyles && (newStyles !== oldStyles)) {
28051 forEach(oldStyles, function(val, style) { element.css(style, '');});
28053 if (newStyles) element.css(newStyles);
28063 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression.
28064 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location
28065 * as specified in the template.
28067 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it
28068 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element
28069 * matches the value obtained from the evaluated expression. In other words, you define a container element
28070 * (where you place the directive), place an expression on the **`on="..."` attribute**
28071 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place
28072 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on
28073 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default
28074 * attribute is displayed.
28076 * <div class="alert alert-info">
28077 * Be aware that the attribute values to match against cannot be expressions. They are interpreted
28078 * as literal string values to match against.
28079 * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the
28080 * value of the expression `$scope.someVal`.
28084 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container
28085 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM
28090 * <ANY ng-switch="expression">
28091 * <ANY ng-switch-when="matchValue1">...</ANY>
28092 * <ANY ng-switch-when="matchValue2">...</ANY>
28093 * <ANY ng-switch-default>...</ANY>
28100 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>.
28101 * On child elements add:
28103 * * `ngSwitchWhen`: the case statement to match against. If match then this
28104 * case will be displayed. If the same match appears multiple times, all the
28105 * elements will be displayed.
28106 * * `ngSwitchDefault`: the default case when no other case match. If there
28107 * are multiple default cases, all of them will be displayed when no other
28112 <example module="switchExample" deps="angular-animate.js" animations="true">
28113 <file name="index.html">
28114 <div ng-controller="ExampleController">
28115 <select ng-model="selection" ng-options="item for item in items">
28117 <code>selection={{selection}}</code>
28119 <div class="animate-switch-container"
28120 ng-switch on="selection">
28121 <div class="animate-switch" ng-switch-when="settings">Settings Div</div>
28122 <div class="animate-switch" ng-switch-when="home">Home Span</div>
28123 <div class="animate-switch" ng-switch-default>default</div>
28127 <file name="script.js">
28128 angular.module('switchExample', ['ngAnimate'])
28129 .controller('ExampleController', ['$scope', function($scope) {
28130 $scope.items = ['settings', 'home', 'other'];
28131 $scope.selection = $scope.items[0];
28134 <file name="animations.css">
28135 .animate-switch-container {
28138 border:1px solid black;
28147 .animate-switch.ng-animate {
28148 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
28157 .animate-switch.ng-leave.ng-leave-active,
28158 .animate-switch.ng-enter {
28161 .animate-switch.ng-leave,
28162 .animate-switch.ng-enter.ng-enter-active {
28166 <file name="protractor.js" type="protractor">
28167 var switchElem = element(by.css('[ng-switch]'));
28168 var select = element(by.model('selection'));
28170 it('should start in settings', function() {
28171 expect(switchElem.getText()).toMatch(/Settings Div/);
28173 it('should change to home', function() {
28174 select.all(by.css('option')).get(1).click();
28175 expect(switchElem.getText()).toMatch(/Home Span/);
28177 it('should select default', function() {
28178 select.all(by.css('option')).get(2).click();
28179 expect(switchElem.getText()).toMatch(/default/);
28184 var ngSwitchDirective = ['$animate', function($animate) {
28186 require: 'ngSwitch',
28188 // asks for $scope to fool the BC controller module
28189 controller: ['$scope', function ngSwitchController() {
28192 link: function(scope, element, attr, ngSwitchController) {
28193 var watchExpr = attr.ngSwitch || attr.on,
28194 selectedTranscludes = [],
28195 selectedElements = [],
28196 previousLeaveAnimations = [],
28197 selectedScopes = [];
28199 var spliceFactory = function(array, index) {
28200 return function() { array.splice(index, 1); };
28203 scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
28205 for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) {
28206 $animate.cancel(previousLeaveAnimations[i]);
28208 previousLeaveAnimations.length = 0;
28210 for (i = 0, ii = selectedScopes.length; i < ii; ++i) {
28211 var selected = getBlockNodes(selectedElements[i].clone);
28212 selectedScopes[i].$destroy();
28213 var promise = previousLeaveAnimations[i] = $animate.leave(selected);
28214 promise.then(spliceFactory(previousLeaveAnimations, i));
28217 selectedElements.length = 0;
28218 selectedScopes.length = 0;
28220 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) {
28221 forEach(selectedTranscludes, function(selectedTransclude) {
28222 selectedTransclude.transclude(function(caseElement, selectedScope) {
28223 selectedScopes.push(selectedScope);
28224 var anchor = selectedTransclude.element;
28225 caseElement[caseElement.length++] = document.createComment(' end ngSwitchWhen: ');
28226 var block = { clone: caseElement };
28228 selectedElements.push(block);
28229 $animate.enter(caseElement, anchor.parent(), anchor);
28238 var ngSwitchWhenDirective = ngDirective({
28239 transclude: 'element',
28241 require: '^ngSwitch',
28242 multiElement: true,
28243 link: function(scope, element, attrs, ctrl, $transclude) {
28244 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
28245 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element });
28249 var ngSwitchDefaultDirective = ngDirective({
28250 transclude: 'element',
28252 require: '^ngSwitch',
28253 multiElement: true,
28254 link: function(scope, element, attr, ctrl, $transclude) {
28255 ctrl.cases['?'] = (ctrl.cases['?'] || []);
28256 ctrl.cases['?'].push({ transclude: $transclude, element: element });
28262 * @name ngTransclude
28266 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.
28268 * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted.
28273 <example module="transcludeExample">
28274 <file name="index.html">
28276 angular.module('transcludeExample', [])
28277 .directive('pane', function(){
28281 scope: { title:'@' },
28282 template: '<div style="border: 1px solid black;">' +
28283 '<div style="background-color: gray">{{title}}</div>' +
28284 '<ng-transclude></ng-transclude>' +
28288 .controller('ExampleController', ['$scope', function($scope) {
28289 $scope.title = 'Lorem Ipsum';
28290 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
28293 <div ng-controller="ExampleController">
28294 <input ng-model="title" aria-label="title"> <br/>
28295 <textarea ng-model="text" aria-label="text"></textarea> <br/>
28296 <pane title="{{title}}">{{text}}</pane>
28299 <file name="protractor.js" type="protractor">
28300 it('should have transcluded', function() {
28301 var titleElement = element(by.model('title'));
28302 titleElement.clear();
28303 titleElement.sendKeys('TITLE');
28304 var textElement = element(by.model('text'));
28305 textElement.clear();
28306 textElement.sendKeys('TEXT');
28307 expect(element(by.binding('title')).getText()).toEqual('TITLE');
28308 expect(element(by.binding('text')).getText()).toEqual('TEXT');
28314 var ngTranscludeDirective = ngDirective({
28316 link: function($scope, $element, $attrs, controller, $transclude) {
28317 if (!$transclude) {
28318 throw minErr('ngTransclude')('orphan',
28319 'Illegal use of ngTransclude directive in the template! ' +
28320 'No parent directive that requires a transclusion found. ' +
28322 startingTag($element));
28325 $transclude(function(clone) {
28327 $element.append(clone);
28338 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the
28339 * template can be used by {@link ng.directive:ngInclude `ngInclude`},
28340 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the
28341 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be
28342 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`.
28344 * @param {string} type Must be set to `'text/ng-template'`.
28345 * @param {string} id Cache name of the template.
28349 <file name="index.html">
28350 <script type="text/ng-template" id="/tpl.html">
28351 Content of the template.
28354 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a>
28355 <div id="tpl-content" ng-include src="currentTpl"></div>
28357 <file name="protractor.js" type="protractor">
28358 it('should load template defined inside script tag', function() {
28359 element(by.css('#tpl-link')).click();
28360 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/);
28365 var scriptDirective = ['$templateCache', function($templateCache) {
28369 compile: function(element, attr) {
28370 if (attr.type == 'text/ng-template') {
28371 var templateUrl = attr.id,
28372 text = element[0].text;
28374 $templateCache.put(templateUrl, text);
28380 var noopNgModelController = { $setViewValue: noop, $render: noop };
28382 function chromeHack(optionElement) {
28383 // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459
28384 // Adding an <option selected="selected"> element to a <select required="required"> should
28385 // automatically select the new element
28386 if (optionElement[0].hasAttribute('selected')) {
28387 optionElement[0].selected = true;
28393 * @name select.SelectController
28395 * The controller for the `<select>` directive. This provides support for reading
28396 * and writing the selected value(s) of the control and also coordinates dynamically
28397 * added `<option>` elements, perhaps by an `ngRepeat` directive.
28399 var SelectController =
28400 ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
28403 optionsMap = new HashMap();
28405 // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors
28406 self.ngModelCtrl = noopNgModelController;
28408 // The "unknown" option is one that is prepended to the list if the viewValue
28409 // does not match any of the options. When it is rendered the value of the unknown
28410 // option is '? XXX ?' where XXX is the hashKey of the value that is not known.
28412 // We can't just jqLite('<option>') since jqLite is not smart enough
28413 // to create it in <select> and IE barfs otherwise.
28414 self.unknownOption = jqLite(document.createElement('option'));
28415 self.renderUnknownOption = function(val) {
28416 var unknownVal = '? ' + hashKey(val) + ' ?';
28417 self.unknownOption.val(unknownVal);
28418 $element.prepend(self.unknownOption);
28419 $element.val(unknownVal);
28422 $scope.$on('$destroy', function() {
28423 // disable unknown option so that we don't do work when the whole select is being destroyed
28424 self.renderUnknownOption = noop;
28427 self.removeUnknownOption = function() {
28428 if (self.unknownOption.parent()) self.unknownOption.remove();
28432 // Read the value of the select control, the implementation of this changes depending
28433 // upon whether the select can have multiple values and whether ngOptions is at work.
28434 self.readValue = function readSingleValue() {
28435 self.removeUnknownOption();
28436 return $element.val();
28440 // Write the value to the select control, the implementation of this changes depending
28441 // upon whether the select can have multiple values and whether ngOptions is at work.
28442 self.writeValue = function writeSingleValue(value) {
28443 if (self.hasOption(value)) {
28444 self.removeUnknownOption();
28445 $element.val(value);
28446 if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy
28448 if (value == null && self.emptyOption) {
28449 self.removeUnknownOption();
28452 self.renderUnknownOption(value);
28458 // Tell the select control that an option, with the given value, has been added
28459 self.addOption = function(value, element) {
28460 assertNotHasOwnProperty(value, '"option value"');
28461 if (value === '') {
28462 self.emptyOption = element;
28464 var count = optionsMap.get(value) || 0;
28465 optionsMap.put(value, count + 1);
28466 self.ngModelCtrl.$render();
28467 chromeHack(element);
28470 // Tell the select control that an option, with the given value, has been removed
28471 self.removeOption = function(value) {
28472 var count = optionsMap.get(value);
28475 optionsMap.remove(value);
28476 if (value === '') {
28477 self.emptyOption = undefined;
28480 optionsMap.put(value, count - 1);
28485 // Check whether the select control has an option matching the given value
28486 self.hasOption = function(value) {
28487 return !!optionsMap.get(value);
28491 self.registerOption = function(optionScope, optionElement, optionAttrs, interpolateValueFn, interpolateTextFn) {
28493 if (interpolateValueFn) {
28494 // The value attribute is interpolated
28496 optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {
28497 if (isDefined(oldVal)) {
28498 self.removeOption(oldVal);
28501 self.addOption(newVal, optionElement);
28503 } else if (interpolateTextFn) {
28504 // The text content is interpolated
28505 optionScope.$watch(interpolateTextFn, function interpolateWatchAction(newVal, oldVal) {
28506 optionAttrs.$set('value', newVal);
28507 if (oldVal !== newVal) {
28508 self.removeOption(oldVal);
28510 self.addOption(newVal, optionElement);
28513 // The value attribute is static
28514 self.addOption(optionAttrs.value, optionElement);
28517 optionElement.on('$destroy', function() {
28518 self.removeOption(optionAttrs.value);
28519 self.ngModelCtrl.$render();
28530 * HTML `SELECT` element with angular data-binding.
28532 * The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding
28533 * between the scope and the `<select>` control (including setting default values).
28534 * Ìt also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or
28535 * {@link ngOptions `ngOptions`} directives.
28537 * When an item in the `<select>` menu is selected, the value of the selected option will be bound
28538 * to the model identified by the `ngModel` directive. With static or repeated options, this is
28539 * the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing.
28540 * If you want dynamic value attributes, you can use interpolation inside the value attribute.
28542 * <div class="alert alert-warning">
28543 * Note that the value of a `select` directive used without `ngOptions` is always a string.
28544 * When the model needs to be bound to a non-string value, you must either explictly convert it
28545 * using a directive (see example below) or use `ngOptions` to specify the set of options.
28546 * This is because an option element can only be bound to string values at present.
28549 * If the viewValue of `ngModel` does not match any of the options, then the control
28550 * will automatically add an "unknown" option, which it then removes when the mismatch is resolved.
28552 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
28553 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
28554 * option. See example below for demonstration.
28556 * <div class="alert alert-info">
28557 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions
28558 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits, such as
28559 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
28560 * comprehension expression, and additionally in reducing memory and increasing speed by not creating
28561 * a new scope for each repeated instance.
28565 * @param {string} ngModel Assignable angular expression to data-bind to.
28566 * @param {string=} name Property name of the form under which the control is published.
28567 * @param {string=} multiple Allows multiple options to be selected. The selected values will be
28568 * bound to the model as an array.
28569 * @param {string=} required Sets `required` validation error key if the value is not entered.
28570 * @param {string=} ngRequired Adds required attribute and required validation constraint to
28571 * the element when the ngRequired expression evaluates to true. Use ngRequired instead of required
28572 * when you want to data-bind to the required attribute.
28573 * @param {string=} ngChange Angular expression to be executed when selected option(s) changes due to user
28574 * interaction with the select element.
28575 * @param {string=} ngOptions sets the options that the select is populated with and defines what is
28576 * set on the model on selection. See {@link ngOptions `ngOptions`}.
28579 * ### Simple `select` elements with static options
28581 * <example name="static-select" module="staticSelect">
28582 * <file name="index.html">
28583 * <div ng-controller="ExampleController">
28584 * <form name="myForm">
28585 * <label for="singleSelect"> Single select: </label><br>
28586 * <select name="singleSelect" ng-model="data.singleSelect">
28587 * <option value="option-1">Option 1</option>
28588 * <option value="option-2">Option 2</option>
28591 * <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br>
28592 * <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect">
28593 * <option value="">---Please select---</option> <!-- not selected / blank option -->
28594 * <option value="{{data.option1}}">Option 1</option> <!-- interpolation -->
28595 * <option value="option-2">Option 2</option>
28597 * <button ng-click="forceUnknownOption()">Force unknown option</button><br>
28598 * <tt>singleSelect = {{data.singleSelect}}</tt>
28601 * <label for="multipleSelect"> Multiple select: </label><br>
28602 * <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple>
28603 * <option value="option-1">Option 1</option>
28604 * <option value="option-2">Option 2</option>
28605 * <option value="option-3">Option 3</option>
28607 * <tt>multipleSelect = {{data.multipleSelect}}</tt><br/>
28611 * <file name="app.js">
28612 * angular.module('staticSelect', [])
28613 * .controller('ExampleController', ['$scope', function($scope) {
28615 * singleSelect: null,
28616 * multipleSelect: [],
28617 * option1: 'option-1',
28620 * $scope.forceUnknownOption = function() {
28621 * $scope.data.singleSelect = 'nonsense';
28627 * ### Using `ngRepeat` to generate `select` options
28628 * <example name="ngrepeat-select" module="ngrepeatSelect">
28629 * <file name="index.html">
28630 * <div ng-controller="ExampleController">
28631 * <form name="myForm">
28632 * <label for="repeatSelect"> Repeat select: </label>
28633 * <select name="repeatSelect" id="repeatSelect" ng-model="data.repeatSelect">
28634 * <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option>
28638 * <tt>repeatSelect = {{data.repeatSelect}}</tt><br/>
28641 * <file name="app.js">
28642 * angular.module('ngrepeatSelect', [])
28643 * .controller('ExampleController', ['$scope', function($scope) {
28645 * repeatSelect: null,
28646 * availableOptions: [
28647 * {id: '1', name: 'Option A'},
28648 * {id: '2', name: 'Option B'},
28649 * {id: '3', name: 'Option C'}
28657 * ### Using `select` with `ngOptions` and setting a default value
28658 * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples.
28660 * <example name="select-with-default-values" module="defaultValueSelect">
28661 * <file name="index.html">
28662 * <div ng-controller="ExampleController">
28663 * <form name="myForm">
28664 * <label for="mySelect">Make a choice:</label>
28665 * <select name="mySelect" id="mySelect"
28666 * ng-options="option.name for option in data.availableOptions track by option.id"
28667 * ng-model="data.selectedOption"></select>
28670 * <tt>option = {{data.selectedOption}}</tt><br/>
28673 * <file name="app.js">
28674 * angular.module('defaultValueSelect', [])
28675 * .controller('ExampleController', ['$scope', function($scope) {
28677 * availableOptions: [
28678 * {id: '1', name: 'Option A'},
28679 * {id: '2', name: 'Option B'},
28680 * {id: '3', name: 'Option C'}
28682 * selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui
28689 * ### Binding `select` to a non-string value via `ngModel` parsing / formatting
28691 * <example name="select-with-non-string-options" module="nonStringSelect">
28692 * <file name="index.html">
28693 * <select ng-model="model.id" convert-to-number>
28694 * <option value="0">Zero</option>
28695 * <option value="1">One</option>
28696 * <option value="2">Two</option>
28700 * <file name="app.js">
28701 * angular.module('nonStringSelect', [])
28702 * .run(function($rootScope) {
28703 * $rootScope.model = { id: 2 };
28705 * .directive('convertToNumber', function() {
28707 * require: 'ngModel',
28708 * link: function(scope, element, attrs, ngModel) {
28709 * ngModel.$parsers.push(function(val) {
28710 * return parseInt(val, 10);
28712 * ngModel.$formatters.push(function(val) {
28719 * <file name="protractor.js" type="protractor">
28720 * it('should initialize to model', function() {
28721 * var select = element(by.css('select'));
28722 * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two');
28728 var selectDirective = function() {
28732 require: ['select', '?ngModel'],
28733 controller: SelectController,
28740 function selectPreLink(scope, element, attr, ctrls) {
28742 // if ngModel is not defined, we don't need to do anything
28743 var ngModelCtrl = ctrls[1];
28744 if (!ngModelCtrl) return;
28746 var selectCtrl = ctrls[0];
28748 selectCtrl.ngModelCtrl = ngModelCtrl;
28750 // We delegate rendering to the `writeValue` method, which can be changed
28751 // if the select can have multiple selected values or if the options are being
28752 // generated by `ngOptions`
28753 ngModelCtrl.$render = function() {
28754 selectCtrl.writeValue(ngModelCtrl.$viewValue);
28757 // When the selected item(s) changes we delegate getting the value of the select control
28758 // to the `readValue` method, which can be changed if the select can have multiple
28759 // selected values or if the options are being generated by `ngOptions`
28760 element.on('change', function() {
28761 scope.$apply(function() {
28762 ngModelCtrl.$setViewValue(selectCtrl.readValue());
28766 // If the select allows multiple values then we need to modify how we read and write
28767 // values from and to the control; also what it means for the value to be empty and
28768 // we have to add an extra watch since ngModel doesn't work well with arrays - it
28769 // doesn't trigger rendering if only an item in the array changes.
28770 if (attr.multiple) {
28772 // Read value now needs to check each option to see if it is selected
28773 selectCtrl.readValue = function readMultipleValue() {
28775 forEach(element.find('option'), function(option) {
28776 if (option.selected) {
28777 array.push(option.value);
28783 // Write value now needs to set the selected property of each matching option
28784 selectCtrl.writeValue = function writeMultipleValue(value) {
28785 var items = new HashMap(value);
28786 forEach(element.find('option'), function(option) {
28787 option.selected = isDefined(items.get(option.value));
28791 // we have to do it on each watch since ngModel watches reference, but
28792 // we need to work of an array, so we need to see if anything was inserted/removed
28793 var lastView, lastViewRef = NaN;
28794 scope.$watch(function selectMultipleWatch() {
28795 if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) {
28796 lastView = shallowCopy(ngModelCtrl.$viewValue);
28797 ngModelCtrl.$render();
28799 lastViewRef = ngModelCtrl.$viewValue;
28802 // If we are a multiple select then value is now a collection
28803 // so the meaning of $isEmpty changes
28804 ngModelCtrl.$isEmpty = function(value) {
28805 return !value || value.length === 0;
28813 // The option directive is purely designed to communicate the existence (or lack of)
28814 // of dynamically created (and destroyed) option elements to their containing select
28815 // directive via its controller.
28816 var optionDirective = ['$interpolate', function($interpolate) {
28820 compile: function(element, attr) {
28822 if (isDefined(attr.value)) {
28823 // If the value attribute is defined, check if it contains an interpolation
28824 var interpolateValueFn = $interpolate(attr.value, true);
28826 // If the value attribute is not defined then we fall back to the
28827 // text content of the option element, which may be interpolated
28828 var interpolateTextFn = $interpolate(element.text(), true);
28829 if (!interpolateTextFn) {
28830 attr.$set('value', element.text());
28834 return function(scope, element, attr) {
28836 // This is an optimization over using ^^ since we don't want to have to search
28837 // all the way to the root of the DOM for every single option element
28838 var selectCtrlName = '$selectController',
28839 parent = element.parent(),
28840 selectCtrl = parent.data(selectCtrlName) ||
28841 parent.parent().data(selectCtrlName); // in case we are in optgroup
28844 selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn);
28851 var styleDirective = valueFn({
28856 var requiredDirective = function() {
28859 require: '?ngModel',
28860 link: function(scope, elm, attr, ctrl) {
28862 attr.required = true; // force truthy in case we are on non input element
28864 ctrl.$validators.required = function(modelValue, viewValue) {
28865 return !attr.required || !ctrl.$isEmpty(viewValue);
28868 attr.$observe('required', function() {
28876 var patternDirective = function() {
28879 require: '?ngModel',
28880 link: function(scope, elm, attr, ctrl) {
28883 var regexp, patternExp = attr.ngPattern || attr.pattern;
28884 attr.$observe('pattern', function(regex) {
28885 if (isString(regex) && regex.length > 0) {
28886 regex = new RegExp('^' + regex + '$');
28889 if (regex && !regex.test) {
28890 throw minErr('ngPattern')('noregexp',
28891 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
28892 regex, startingTag(elm));
28895 regexp = regex || undefined;
28899 ctrl.$validators.pattern = function(modelValue, viewValue) {
28900 // HTML5 pattern constraint validates the input value, so we validate the viewValue
28901 return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue);
28908 var maxlengthDirective = function() {
28911 require: '?ngModel',
28912 link: function(scope, elm, attr, ctrl) {
28915 var maxlength = -1;
28916 attr.$observe('maxlength', function(value) {
28917 var intVal = toInt(value);
28918 maxlength = isNaN(intVal) ? -1 : intVal;
28921 ctrl.$validators.maxlength = function(modelValue, viewValue) {
28922 return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength);
28928 var minlengthDirective = function() {
28931 require: '?ngModel',
28932 link: function(scope, elm, attr, ctrl) {
28936 attr.$observe('minlength', function(value) {
28937 minlength = toInt(value) || 0;
28940 ctrl.$validators.minlength = function(modelValue, viewValue) {
28941 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
28947 if (window.angular.bootstrap) {
28948 //AngularJS is already loaded, so we can return here...
28949 console.log('WARNING: Tried to load angular more than once.');
28953 //try to bind to jquery now so that one can write jqLite(document).ready()
28954 //but we will rebind on bootstrap again.
28957 publishExternalAPI(angular);
28959 angular.module("ngLocale", [], ["$provide", function($provide) {
28960 var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"};
28961 function getDecimals(n) {
28963 var i = n.indexOf('.');
28964 return (i == -1) ? 0 : n.length - i - 1;
28967 function getVF(n, opt_precision) {
28968 var v = opt_precision;
28970 if (undefined === v) {
28971 v = Math.min(getDecimals(n), 3);
28974 var base = Math.pow(10, v);
28975 var f = ((n * base) | 0) % base;
28976 return {v: v, f: f};
28979 $provide.value("$locale", {
28980 "DATETIME_FORMATS": {
29002 "FIRSTDAYOFWEEK": 6,
29044 "fullDate": "EEEE, MMMM d, y",
29045 "longDate": "MMMM d, y",
29046 "medium": "MMM d, y h:mm:ss a",
29047 "mediumDate": "MMM d, y",
29048 "mediumTime": "h:mm:ss a",
29049 "short": "M/d/yy h:mm a",
29050 "shortDate": "M/d/yy",
29051 "shortTime": "h:mm a"
29053 "NUMBER_FORMATS": {
29054 "CURRENCY_SYM": "$",
29055 "DECIMAL_SEP": ".",
29075 "negPre": "-\u00a4",
29077 "posPre": "\u00a4",
29083 "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;}
29087 jqLite(document).ready(function() {
29088 angularInit(document, bootstrap);
29091 })(window, document);
29093 !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>');
29097 /***/ function(module, exports) {
29100 * State-based routing for AngularJS
29102 * @link http://angular-ui.github.com/
29103 * @license MIT License, http://www.opensource.org/licenses/MIT
29106 /* commonjs package manager support (eg componentjs) */
29107 if (typeof module !== "undefined" && typeof exports !== "undefined" && module.exports === exports){
29108 module.exports = 'ui.router';
29111 (function (window, angular, undefined) {
29112 /*jshint globalstrict:true*/
29113 /*global angular:false*/
29116 var isDefined = angular.isDefined,
29117 isFunction = angular.isFunction,
29118 isString = angular.isString,
29119 isObject = angular.isObject,
29120 isArray = angular.isArray,
29121 forEach = angular.forEach,
29122 extend = angular.extend,
29123 copy = angular.copy;
29125 function inherit(parent, extra) {
29126 return extend(new (extend(function() {}, { prototype: parent }))(), extra);
29129 function merge(dst) {
29130 forEach(arguments, function(obj) {
29132 forEach(obj, function(value, key) {
29133 if (!dst.hasOwnProperty(key)) dst[key] = value;
29141 * Finds the common ancestor path between two states.
29143 * @param {Object} first The first state.
29144 * @param {Object} second The second state.
29145 * @return {Array} Returns an array of state names in descending order, not including the root.
29147 function ancestors(first, second) {
29150 for (var n in first.path) {
29151 if (first.path[n] !== second.path[n]) break;
29152 path.push(first.path[n]);
29158 * IE8-safe wrapper for `Object.keys()`.
29160 * @param {Object} object A JavaScript object.
29161 * @return {Array} Returns the keys of the object as an array.
29163 function objectKeys(object) {
29165 return Object.keys(object);
29169 forEach(object, function(val, key) {
29176 * IE8-safe wrapper for `Array.prototype.indexOf()`.
29178 * @param {Array} array A JavaScript array.
29179 * @param {*} value A value to search the array for.
29180 * @return {Number} Returns the array index value of `value`, or `-1` if not present.
29182 function indexOf(array, value) {
29183 if (Array.prototype.indexOf) {
29184 return array.indexOf(value, Number(arguments[2]) || 0);
29186 var len = array.length >>> 0, from = Number(arguments[2]) || 0;
29187 from = (from < 0) ? Math.ceil(from) : Math.floor(from);
29189 if (from < 0) from += len;
29191 for (; from < len; from++) {
29192 if (from in array && array[from] === value) return from;
29198 * Merges a set of parameters with all parameters inherited between the common parents of the
29199 * current state and a given destination state.
29201 * @param {Object} currentParams The value of the current state parameters ($stateParams).
29202 * @param {Object} newParams The set of parameters which will be composited with inherited params.
29203 * @param {Object} $current Internal definition of object representing the current state.
29204 * @param {Object} $to Internal definition of object representing state to transition to.
29206 function inheritParams(currentParams, newParams, $current, $to) {
29207 var parents = ancestors($current, $to), parentParams, inherited = {}, inheritList = [];
29209 for (var i in parents) {
29210 if (!parents[i].params) continue;
29211 parentParams = objectKeys(parents[i].params);
29212 if (!parentParams.length) continue;
29214 for (var j in parentParams) {
29215 if (indexOf(inheritList, parentParams[j]) >= 0) continue;
29216 inheritList.push(parentParams[j]);
29217 inherited[parentParams[j]] = currentParams[parentParams[j]];
29220 return extend({}, inherited, newParams);
29224 * Performs a non-strict comparison of the subset of two objects, defined by a list of keys.
29226 * @param {Object} a The first object.
29227 * @param {Object} b The second object.
29228 * @param {Array} keys The list of keys within each object to compare. If the list is empty or not specified,
29229 * it defaults to the list of keys in `a`.
29230 * @return {Boolean} Returns `true` if the keys match, otherwise `false`.
29232 function equalForKeys(a, b, keys) {
29235 for (var n in a) keys.push(n); // Used instead of Object.keys() for IE8 compatibility
29238 for (var i=0; i<keys.length; i++) {
29240 if (a[k] != b[k]) return false; // Not '===', values aren't necessarily normalized
29246 * Returns the subset of an object, based on a list of keys.
29248 * @param {Array} keys
29249 * @param {Object} values
29250 * @return {Boolean} Returns a subset of `values`.
29252 function filterByKeys(keys, values) {
29255 forEach(keys, function (name) {
29256 filtered[name] = values[name];
29262 // when you know that your index values will be unique, or you want last-one-in to win
29263 function indexBy(array, propName) {
29265 forEach(array, function(item) {
29266 result[item[propName]] = item;
29271 // extracted from underscore.js
29272 // Return a copy of the object only containing the whitelisted properties.
29273 function pick(obj) {
29275 var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1));
29276 forEach(keys, function(key) {
29277 if (key in obj) copy[key] = obj[key];
29282 // extracted from underscore.js
29283 // Return a copy of the object omitting the blacklisted properties.
29284 function omit(obj) {
29286 var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1));
29287 for (var key in obj) {
29288 if (indexOf(keys, key) == -1) copy[key] = obj[key];
29293 function pluck(collection, key) {
29294 var result = isArray(collection) ? [] : {};
29296 forEach(collection, function(val, i) {
29297 result[i] = isFunction(key) ? key(val) : val[key];
29302 function filter(collection, callback) {
29303 var array = isArray(collection);
29304 var result = array ? [] : {};
29305 forEach(collection, function(val, i) {
29306 if (callback(val, i)) {
29307 result[array ? result.length : i] = val;
29313 function map(collection, callback) {
29314 var result = isArray(collection) ? [] : {};
29316 forEach(collection, function(val, i) {
29317 result[i] = callback(val, i);
29324 * @name ui.router.util
29327 * # ui.router.util sub-module
29329 * This module is a dependency of other sub-modules. Do not include this module as a dependency
29330 * in your angular app (use {@link ui.router} module instead).
29333 angular.module('ui.router.util', ['ng']);
29337 * @name ui.router.router
29339 * @requires ui.router.util
29342 * # ui.router.router sub-module
29344 * This module is a dependency of other sub-modules. Do not include this module as a dependency
29345 * in your angular app (use {@link ui.router} module instead).
29347 angular.module('ui.router.router', ['ui.router.util']);
29351 * @name ui.router.state
29353 * @requires ui.router.router
29354 * @requires ui.router.util
29357 * # ui.router.state sub-module
29359 * This module is a dependency of the main ui.router module. Do not include this module as a dependency
29360 * in your angular app (use {@link ui.router} module instead).
29363 angular.module('ui.router.state', ['ui.router.router', 'ui.router.util']);
29369 * @requires ui.router.state
29374 * ## The main module for ui.router
29375 * There are several sub-modules included with the ui.router module, however only this module is needed
29376 * as a dependency within your angular app. The other modules are for organization purposes.
29379 * * ui.router - the main "umbrella" module
29380 * * ui.router.router -
29382 * *You'll need to include **only** this module as the dependency within your angular app.*
29386 * <html ng-app="myApp">
29388 * <script src="js/angular.js"></script>
29389 * <!-- Include the ui-router script -->
29390 * <script src="js/angular-ui-router.min.js"></script>
29392 * // ...and add 'ui.router' as a dependency
29393 * var myApp = angular.module('myApp', ['ui.router']);
29401 angular.module('ui.router', ['ui.router.state']);
29403 angular.module('ui.router.compat', ['ui.router']);
29407 * @name ui.router.util.$resolve
29410 * @requires $injector
29413 * Manages resolution of (acyclic) graphs of promises.
29415 $Resolve.$inject = ['$q', '$injector'];
29416 function $Resolve( $q, $injector) {
29418 var VISIT_IN_PROGRESS = 1,
29421 NO_DEPENDENCIES = [],
29422 NO_LOCALS = NOTHING,
29423 NO_PARENT = extend($q.when(NOTHING), { $$promises: NOTHING, $$values: NOTHING });
29428 * @name ui.router.util.$resolve#study
29429 * @methodOf ui.router.util.$resolve
29432 * Studies a set of invocables that are likely to be used multiple times.
29434 * $resolve.study(invocables)(locals, parent, self)
29438 * $resolve.resolve(invocables, locals, parent, self)
29440 * but the former is more efficient (in fact `resolve` just calls `study`
29443 * @param {object} invocables Invocable objects
29444 * @return {function} a function to pass in locals, parent and self
29446 this.study = function (invocables) {
29447 if (!isObject(invocables)) throw new Error("'invocables' must be an object");
29448 var invocableKeys = objectKeys(invocables || {});
29450 // Perform a topological sort of invocables to build an ordered plan
29451 var plan = [], cycle = [], visited = {};
29452 function visit(value, key) {
29453 if (visited[key] === VISIT_DONE) return;
29456 if (visited[key] === VISIT_IN_PROGRESS) {
29457 cycle.splice(0, indexOf(cycle, key));
29458 throw new Error("Cyclic dependency: " + cycle.join(" -> "));
29460 visited[key] = VISIT_IN_PROGRESS;
29462 if (isString(value)) {
29463 plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES);
29465 var params = $injector.annotate(value);
29466 forEach(params, function (param) {
29467 if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param);
29469 plan.push(key, value, params);
29473 visited[key] = VISIT_DONE;
29475 forEach(invocables, visit);
29476 invocables = cycle = visited = null; // plan is all that's required
29478 function isResolve(value) {
29479 return isObject(value) && value.then && value.$$promises;
29482 return function (locals, parent, self) {
29483 if (isResolve(locals) && self === undefined) {
29484 self = parent; parent = locals; locals = null;
29486 if (!locals) locals = NO_LOCALS;
29487 else if (!isObject(locals)) {
29488 throw new Error("'locals' must be an object");
29490 if (!parent) parent = NO_PARENT;
29491 else if (!isResolve(parent)) {
29492 throw new Error("'parent' must be a promise returned by $resolve.resolve()");
29495 // To complete the overall resolution, we have to wait for the parent
29496 // promise and for the promise for each invokable in our plan.
29497 var resolution = $q.defer(),
29498 result = resolution.promise,
29499 promises = result.$$promises = {},
29500 values = extend({}, locals),
29501 wait = 1 + plan.length/3,
29505 // Merge parent values we haven't got yet and publish our own $$values
29507 if (!merged) merge(values, parent.$$values);
29508 result.$$values = values;
29509 result.$$promises = result.$$promises || true; // keep for isResolve()
29510 delete result.$$inheritedValues;
29511 resolution.resolve(values);
29515 function fail(reason) {
29516 result.$$failure = reason;
29517 resolution.reject(reason);
29520 // Short-circuit if parent has already failed
29521 if (isDefined(parent.$$failure)) {
29522 fail(parent.$$failure);
29526 if (parent.$$inheritedValues) {
29527 merge(values, omit(parent.$$inheritedValues, invocableKeys));
29530 // Merge parent values if the parent has already resolved, or merge
29531 // parent promises and wait if the parent resolve is still in progress.
29532 extend(promises, parent.$$promises);
29533 if (parent.$$values) {
29534 merged = merge(values, omit(parent.$$values, invocableKeys));
29535 result.$$inheritedValues = omit(parent.$$values, invocableKeys);
29538 if (parent.$$inheritedValues) {
29539 result.$$inheritedValues = omit(parent.$$inheritedValues, invocableKeys);
29541 parent.then(done, fail);
29544 // Process each invocable in the plan, but ignore any where a local of the same name exists.
29545 for (var i=0, ii=plan.length; i<ii; i+=3) {
29546 if (locals.hasOwnProperty(plan[i])) done();
29547 else invoke(plan[i], plan[i+1], plan[i+2]);
29550 function invoke(key, invocable, params) {
29551 // Create a deferred for this invocation. Failures will propagate to the resolution as well.
29552 var invocation = $q.defer(), waitParams = 0;
29553 function onfailure(reason) {
29554 invocation.reject(reason);
29557 // Wait for any parameter that we have a promise for (either from parent or from this
29558 // resolve; in that case study() will have made sure it's ordered before us in the plan).
29559 forEach(params, function (dep) {
29560 if (promises.hasOwnProperty(dep) && !locals.hasOwnProperty(dep)) {
29562 promises[dep].then(function (result) {
29563 values[dep] = result;
29564 if (!(--waitParams)) proceed();
29568 if (!waitParams) proceed();
29569 function proceed() {
29570 if (isDefined(result.$$failure)) return;
29572 invocation.resolve($injector.invoke(invocable, self, values));
29573 invocation.promise.then(function (result) {
29574 values[key] = result;
29581 // Publish promise synchronously; invocations further down in the plan may depend on it.
29582 promises[key] = invocation.promise;
29591 * @name ui.router.util.$resolve#resolve
29592 * @methodOf ui.router.util.$resolve
29595 * Resolves a set of invocables. An invocable is a function to be invoked via
29596 * `$injector.invoke()`, and can have an arbitrary number of dependencies.
29597 * An invocable can either return a value directly,
29598 * or a `$q` promise. If a promise is returned it will be resolved and the
29599 * resulting value will be used instead. Dependencies of invocables are resolved
29600 * (in this order of precedence)
29602 * - from the specified `locals`
29603 * - from another invocable that is part of this `$resolve` call
29604 * - from an invocable that is inherited from a `parent` call to `$resolve`
29606 * - from any ancestor `$resolve` of that parent).
29608 * The return value of `$resolve` is a promise for an object that contains
29609 * (in this order of precedence)
29611 * - any `locals` (if specified)
29612 * - the resolved return values of all injectables
29613 * - any values inherited from a `parent` call to `$resolve` (if specified)
29615 * The promise will resolve after the `parent` promise (if any) and all promises
29616 * returned by injectables have been resolved. If any invocable
29617 * (or `$injector.invoke`) throws an exception, or if a promise returned by an
29618 * invocable is rejected, the `$resolve` promise is immediately rejected with the
29619 * same error. A rejection of a `parent` promise (if specified) will likewise be
29620 * propagated immediately. Once the `$resolve` promise has been rejected, no
29621 * further invocables will be called.
29623 * Cyclic dependencies between invocables are not permitted and will caues `$resolve`
29624 * to throw an error. As a special case, an injectable can depend on a parameter
29625 * with the same name as the injectable, which will be fulfilled from the `parent`
29626 * injectable of the same name. This allows inherited values to be decorated.
29627 * Note that in this case any other injectable in the same `$resolve` with the same
29628 * dependency would see the decorated value, not the inherited value.
29630 * Note that missing dependencies -- unlike cyclic dependencies -- will cause an
29631 * (asynchronous) rejection of the `$resolve` promise rather than a (synchronous)
29634 * Invocables are invoked eagerly as soon as all dependencies are available.
29635 * This is true even for dependencies inherited from a `parent` call to `$resolve`.
29637 * As a special case, an invocable can be a string, in which case it is taken to
29638 * be a service name to be passed to `$injector.get()`. This is supported primarily
29639 * for backwards-compatibility with the `resolve` property of `$routeProvider`
29642 * @param {object} invocables functions to invoke or
29643 * `$injector` services to fetch.
29644 * @param {object} locals values to make available to the injectables
29645 * @param {object} parent a promise returned by another call to `$resolve`.
29646 * @param {object} self the `this` for the invoked methods
29647 * @return {object} Promise for an object that contains the resolved return value
29648 * of all invocables, as well as any inherited and local values.
29650 this.resolve = function (invocables, locals, parent, self) {
29651 return this.study(invocables)(locals, parent, self);
29655 angular.module('ui.router.util').service('$resolve', $Resolve);
29660 * @name ui.router.util.$templateFactory
29663 * @requires $templateCache
29664 * @requires $injector
29667 * Service. Manages loading of templates.
29669 $TemplateFactory.$inject = ['$http', '$templateCache', '$injector'];
29670 function $TemplateFactory( $http, $templateCache, $injector) {
29674 * @name ui.router.util.$templateFactory#fromConfig
29675 * @methodOf ui.router.util.$templateFactory
29678 * Creates a template from a configuration object.
29680 * @param {object} config Configuration object for which to load a template.
29681 * The following properties are search in the specified order, and the first one
29682 * that is defined is used to create the template:
29684 * @param {string|object} config.template html string template or function to
29685 * load via {@link ui.router.util.$templateFactory#fromString fromString}.
29686 * @param {string|object} config.templateUrl url to load or a function returning
29687 * the url to load via {@link ui.router.util.$templateFactory#fromUrl fromUrl}.
29688 * @param {Function} config.templateProvider function to invoke via
29689 * {@link ui.router.util.$templateFactory#fromProvider fromProvider}.
29690 * @param {object} params Parameters to pass to the template function.
29691 * @param {object} locals Locals to pass to `invoke` if the template is loaded
29692 * via a `templateProvider`. Defaults to `{ params: params }`.
29694 * @return {string|object} The template html as a string, or a promise for
29695 * that string,or `null` if no template is configured.
29697 this.fromConfig = function (config, params, locals) {
29699 isDefined(config.template) ? this.fromString(config.template, params) :
29700 isDefined(config.templateUrl) ? this.fromUrl(config.templateUrl, params) :
29701 isDefined(config.templateProvider) ? this.fromProvider(config.templateProvider, params, locals) :
29708 * @name ui.router.util.$templateFactory#fromString
29709 * @methodOf ui.router.util.$templateFactory
29712 * Creates a template from a string or a function returning a string.
29714 * @param {string|object} template html template as a string or function that
29715 * returns an html template as a string.
29716 * @param {object} params Parameters to pass to the template function.
29718 * @return {string|object} The template html as a string, or a promise for that
29721 this.fromString = function (template, params) {
29722 return isFunction(template) ? template(params) : template;
29727 * @name ui.router.util.$templateFactory#fromUrl
29728 * @methodOf ui.router.util.$templateFactory
29731 * Loads a template from the a URL via `$http` and `$templateCache`.
29733 * @param {string|Function} url url of the template to load, or a function
29734 * that returns a url.
29735 * @param {Object} params Parameters to pass to the url function.
29736 * @return {string|Promise.<string>} The template html as a string, or a promise
29739 this.fromUrl = function (url, params) {
29740 if (isFunction(url)) url = url(params);
29741 if (url == null) return null;
29743 .get(url, { cache: $templateCache, headers: { Accept: 'text/html' }})
29744 .then(function(response) { return response.data; });
29749 * @name ui.router.util.$templateFactory#fromProvider
29750 * @methodOf ui.router.util.$templateFactory
29753 * Creates a template by invoking an injectable provider function.
29755 * @param {Function} provider Function to invoke via `$injector.invoke`
29756 * @param {Object} params Parameters for the template.
29757 * @param {Object} locals Locals to pass to `invoke`. Defaults to
29758 * `{ params: params }`.
29759 * @return {string|Promise.<string>} The template html as a string, or a promise
29762 this.fromProvider = function (provider, params, locals) {
29763 return $injector.invoke(provider, null, locals || { params: params });
29767 angular.module('ui.router.util').service('$templateFactory', $TemplateFactory);
29769 var $$UMFP; // reference to $UrlMatcherFactoryProvider
29773 * @name ui.router.util.type:UrlMatcher
29776 * Matches URLs against patterns and extracts named parameters from the path or the search
29777 * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list
29778 * of search parameters. Multiple search parameter names are separated by '&'. Search parameters
29779 * do not influence whether or not a URL is matched, but their values are passed through into
29780 * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}.
29782 * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace
29783 * syntax, which optionally allows a regular expression for the parameter to be specified:
29785 * * `':'` name - colon placeholder
29786 * * `'*'` name - catch-all placeholder
29787 * * `'{' name '}'` - curly placeholder
29788 * * `'{' name ':' regexp|type '}'` - curly placeholder with regexp or type name. Should the
29789 * regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash.
29791 * Parameter names may contain only word characters (latin letters, digits, and underscore) and
29792 * must be unique within the pattern (across both path and search parameters). For colon
29793 * placeholders or curly placeholders without an explicit regexp, a path parameter matches any
29794 * number of characters other than '/'. For catch-all placeholders the path parameter matches
29795 * any number of characters.
29799 * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for
29800 * trailing slashes, and patterns have to match the entire path, not just a prefix.
29801 * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or
29802 * '/user/bob/details'. The second path segment will be captured as the parameter 'id'.
29803 * * `'/user/{id}'` - Same as the previous example, but using curly brace syntax.
29804 * * `'/user/{id:[^/]*}'` - Same as the previous example.
29805 * * `'/user/{id:[0-9a-fA-F]{1,8}}'` - Similar to the previous example, but only matches if the id
29806 * parameter consists of 1 to 8 hex digits.
29807 * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the
29808 * path into the parameter 'path'.
29809 * * `'/files/*path'` - ditto.
29810 * * `'/calendar/{start:date}'` - Matches "/calendar/2014-11-12" (because the pattern defined
29811 * in the built-in `date` Type matches `2014-11-12`) and provides a Date object in $stateParams.start
29813 * @param {string} pattern The pattern to compile into a matcher.
29814 * @param {Object} config A configuration object hash:
29815 * @param {Object=} parentMatcher Used to concatenate the pattern/config onto
29816 * an existing UrlMatcher
29818 * * `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`.
29819 * * `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`.
29821 * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any
29822 * URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns
29823 * non-null) will start with this prefix.
29825 * @property {string} source The pattern that was passed into the constructor
29827 * @property {string} sourcePath The path portion of the source property
29829 * @property {string} sourceSearch The search portion of the source property
29831 * @property {string} regex The constructed regex that will be used to match against the url when
29832 * it is time to determine which url will match.
29834 * @returns {Object} New `UrlMatcher` object
29836 function UrlMatcher(pattern, config, parentMatcher) {
29837 config = extend({ params: {} }, isObject(config) ? config : {});
29839 // Find all placeholders and create a compiled pattern, using either classic or curly syntax:
29843 // '{' name ':' regexp '}'
29844 // The regular expression is somewhat complicated due to the need to allow curly braces
29845 // inside the regular expression. The placeholder regexp breaks down as follows:
29846 // ([:*])([\w\[\]]+) - classic placeholder ($1 / $2) (search version has - for snake-case)
29847 // \{([\w\[\]]+)(?:\:( ... ))?\} - curly brace placeholder ($3) with optional regexp/type ... ($4) (search version has - for snake-case
29848 // (?: ... | ... | ... )+ - the regexp consists of any number of atoms, an atom being either
29849 // [^{}\\]+ - anything other than curly braces or backslash
29850 // \\. - a backslash escape
29851 // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms
29852 var placeholder = /([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
29853 searchPlaceholder = /([:]?)([\w\[\]-]+)|\{([\w\[\]-]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
29854 compiled = '^', last = 0, m,
29855 segments = this.segments = [],
29856 parentParams = parentMatcher ? parentMatcher.params : {},
29857 params = this.params = parentMatcher ? parentMatcher.params.$$new() : new $$UMFP.ParamSet(),
29860 function addParameter(id, type, config, location) {
29861 paramNames.push(id);
29862 if (parentParams[id]) return parentParams[id];
29863 if (!/^\w+(-+\w+)*(?:\[\])?$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'");
29864 if (params[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'");
29865 params[id] = new $$UMFP.Param(id, type, config, location);
29869 function quoteRegExp(string, pattern, squash, optional) {
29870 var surroundPattern = ['',''], result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&");
29871 if (!pattern) return result;
29873 case false: surroundPattern = ['(', ')' + (optional ? "?" : "")]; break;
29874 case true: surroundPattern = ['?(', ')?']; break;
29875 default: surroundPattern = ['(' + squash + "|", ')?']; break;
29877 return result + surroundPattern[0] + pattern + surroundPattern[1];
29880 this.source = pattern;
29882 // Split into static segments separated by path parameter placeholders.
29883 // The number of segments is always 1 more than the number of parameters.
29884 function matchDetails(m, isSearch) {
29885 var id, regexp, segment, type, cfg, arrayMode;
29886 id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null
29887 cfg = config.params[id];
29888 segment = pattern.substring(last, m.index);
29889 regexp = isSearch ? m[4] : m[4] || (m[1] == '*' ? '.*' : null);
29890 type = $$UMFP.type(regexp || "string") || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp, config.caseInsensitive ? 'i' : undefined) });
29892 id: id, regexp: regexp, segment: segment, type: type, cfg: cfg
29896 var p, param, segment;
29897 while ((m = placeholder.exec(pattern))) {
29898 p = matchDetails(m, false);
29899 if (p.segment.indexOf('?') >= 0) break; // we're into the search part
29901 param = addParameter(p.id, p.type, p.cfg, "path");
29902 compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash, param.isOptional);
29903 segments.push(p.segment);
29904 last = placeholder.lastIndex;
29906 segment = pattern.substring(last);
29908 // Find any search parameter names and remove them from the last segment
29909 var i = segment.indexOf('?');
29912 var search = this.sourceSearch = segment.substring(i);
29913 segment = segment.substring(0, i);
29914 this.sourcePath = pattern.substring(0, last + i);
29916 if (search.length > 0) {
29918 while ((m = searchPlaceholder.exec(search))) {
29919 p = matchDetails(m, true);
29920 param = addParameter(p.id, p.type, p.cfg, "search");
29921 last = placeholder.lastIndex;
29926 this.sourcePath = pattern;
29927 this.sourceSearch = '';
29930 compiled += quoteRegExp(segment) + (config.strict === false ? '\/?' : '') + '$';
29931 segments.push(segment);
29933 this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined);
29934 this.prefix = segments[0];
29935 this.$$paramNames = paramNames;
29940 * @name ui.router.util.type:UrlMatcher#concat
29941 * @methodOf ui.router.util.type:UrlMatcher
29944 * Returns a new matcher for a pattern constructed by appending the path part and adding the
29945 * search parameters of the specified pattern to this pattern. The current pattern is not
29946 * modified. This can be understood as creating a pattern for URLs that are relative to (or
29947 * suffixes of) the current pattern.
29950 * The following two matchers are equivalent:
29952 * new UrlMatcher('/user/{id}?q').concat('/details?date');
29953 * new UrlMatcher('/user/{id}/details?q&date');
29956 * @param {string} pattern The pattern to append.
29957 * @param {Object} config An object hash of the configuration for the matcher.
29958 * @returns {UrlMatcher} A matcher for the concatenated pattern.
29960 UrlMatcher.prototype.concat = function (pattern, config) {
29961 // Because order of search parameters is irrelevant, we can add our own search
29962 // parameters to the end of the new pattern. Parse the new pattern by itself
29963 // and then join the bits together, but it's much easier to do this on a string level.
29964 var defaultConfig = {
29965 caseInsensitive: $$UMFP.caseInsensitive(),
29966 strict: $$UMFP.strictMode(),
29967 squash: $$UMFP.defaultSquashPolicy()
29969 return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch, extend(defaultConfig, config), this);
29972 UrlMatcher.prototype.toString = function () {
29973 return this.source;
29978 * @name ui.router.util.type:UrlMatcher#exec
29979 * @methodOf ui.router.util.type:UrlMatcher
29982 * Tests the specified path against this matcher, and returns an object containing the captured
29983 * parameter values, or null if the path does not match. The returned object contains the values
29984 * of any search parameters that are mentioned in the pattern, but their value may be null if
29985 * they are not present in `searchParams`. This means that search parameters are always treated
29990 * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', {
29991 * x: '1', q: 'hello'
29993 * // returns { id: 'bob', q: 'hello', r: null }
29996 * @param {string} path The URL path to match, e.g. `$location.path()`.
29997 * @param {Object} searchParams URL search parameters, e.g. `$location.search()`.
29998 * @returns {Object} The captured parameter values.
30000 UrlMatcher.prototype.exec = function (path, searchParams) {
30001 var m = this.regexp.exec(path);
30002 if (!m) return null;
30003 searchParams = searchParams || {};
30005 var paramNames = this.parameters(), nTotal = paramNames.length,
30006 nPath = this.segments.length - 1,
30007 values = {}, i, j, cfg, paramName;
30009 if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'");
30011 function decodePathArray(string) {
30012 function reverseString(str) { return str.split("").reverse().join(""); }
30013 function unquoteDashes(str) { return str.replace(/\\-/g, "-"); }
30015 var split = reverseString(string).split(/-(?!\\)/);
30016 var allReversed = map(split, reverseString);
30017 return map(allReversed, unquoteDashes).reverse();
30020 for (i = 0; i < nPath; i++) {
30021 paramName = paramNames[i];
30022 var param = this.params[paramName];
30023 var paramVal = m[i+1];
30024 // if the param value matches a pre-replace pair, replace the value before decoding.
30025 for (j = 0; j < param.replace; j++) {
30026 if (param.replace[j].from === paramVal) paramVal = param.replace[j].to;
30028 if (paramVal && param.array === true) paramVal = decodePathArray(paramVal);
30029 values[paramName] = param.value(paramVal);
30031 for (/**/; i < nTotal; i++) {
30032 paramName = paramNames[i];
30033 values[paramName] = this.params[paramName].value(searchParams[paramName]);
30041 * @name ui.router.util.type:UrlMatcher#parameters
30042 * @methodOf ui.router.util.type:UrlMatcher
30045 * Returns the names of all path and search parameters of this pattern in an unspecified order.
30047 * @returns {Array.<string>} An array of parameter names. Must be treated as read-only. If the
30048 * pattern has no parameters, an empty array is returned.
30050 UrlMatcher.prototype.parameters = function (param) {
30051 if (!isDefined(param)) return this.$$paramNames;
30052 return this.params[param] || null;
30057 * @name ui.router.util.type:UrlMatcher#validate
30058 * @methodOf ui.router.util.type:UrlMatcher
30061 * Checks an object hash of parameters to validate their correctness according to the parameter
30062 * types of this `UrlMatcher`.
30064 * @param {Object} params The object hash of parameters to validate.
30065 * @returns {boolean} Returns `true` if `params` validates, otherwise `false`.
30067 UrlMatcher.prototype.validates = function (params) {
30068 return this.params.$$validates(params);
30073 * @name ui.router.util.type:UrlMatcher#format
30074 * @methodOf ui.router.util.type:UrlMatcher
30077 * Creates a URL that matches this pattern by substituting the specified values
30078 * for the path and search parameters. Null values for path parameters are
30079 * treated as empty strings.
30083 * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' });
30084 * // returns '/user/bob?q=yes'
30087 * @param {Object} values the values to substitute for the parameters in this pattern.
30088 * @returns {string} the formatted URL (path and optionally search part).
30090 UrlMatcher.prototype.format = function (values) {
30091 values = values || {};
30092 var segments = this.segments, params = this.parameters(), paramset = this.params;
30093 if (!this.validates(values)) return null;
30095 var i, search = false, nPath = segments.length - 1, nTotal = params.length, result = segments[0];
30097 function encodeDashes(str) { // Replace dashes with encoded "\-"
30098 return encodeURIComponent(str).replace(/-/g, function(c) { return '%5C%' + c.charCodeAt(0).toString(16).toUpperCase(); });
30101 for (i = 0; i < nTotal; i++) {
30102 var isPathParam = i < nPath;
30103 var name = params[i], param = paramset[name], value = param.value(values[name]);
30104 var isDefaultValue = param.isOptional && param.type.equals(param.value(), value);
30105 var squash = isDefaultValue ? param.squash : false;
30106 var encoded = param.type.encode(value);
30109 var nextSegment = segments[i + 1];
30110 if (squash === false) {
30111 if (encoded != null) {
30112 if (isArray(encoded)) {
30113 result += map(encoded, encodeDashes).join("-");
30115 result += encodeURIComponent(encoded);
30118 result += nextSegment;
30119 } else if (squash === true) {
30120 var capture = result.match(/\/$/) ? /\/?(.*)/ : /(.*)/;
30121 result += nextSegment.match(capture)[1];
30122 } else if (isString(squash)) {
30123 result += squash + nextSegment;
30126 if (encoded == null || (isDefaultValue && squash !== false)) continue;
30127 if (!isArray(encoded)) encoded = [ encoded ];
30128 encoded = map(encoded, encodeURIComponent).join('&' + name + '=');
30129 result += (search ? '&' : '?') + (name + '=' + encoded);
30139 * @name ui.router.util.type:Type
30142 * Implements an interface to define custom parameter types that can be decoded from and encoded to
30143 * string parameters matched in a URL. Used by {@link ui.router.util.type:UrlMatcher `UrlMatcher`}
30144 * objects when matching or formatting URLs, or comparing or validating parameter values.
30146 * See {@link ui.router.util.$urlMatcherFactory#methods_type `$urlMatcherFactory#type()`} for more
30147 * information on registering custom types.
30149 * @param {Object} config A configuration object which contains the custom type definition. The object's
30150 * properties will override the default methods and/or pattern in `Type`'s public interface.
30154 * decode: function(val) { return parseInt(val, 10); },
30155 * encode: function(val) { return val && val.toString(); },
30156 * equals: function(a, b) { return this.is(a) && a === b; },
30157 * is: function(val) { return angular.isNumber(val) isFinite(val) && val % 1 === 0; },
30162 * @property {RegExp} pattern The regular expression pattern used to match values of this type when
30163 * coming from a substring of a URL.
30165 * @returns {Object} Returns a new `Type` object.
30167 function Type(config) {
30168 extend(this, config);
30173 * @name ui.router.util.type:Type#is
30174 * @methodOf ui.router.util.type:Type
30177 * Detects whether a value is of a particular type. Accepts a native (decoded) value
30178 * and determines whether it matches the current `Type` object.
30180 * @param {*} val The value to check.
30181 * @param {string} key Optional. If the type check is happening in the context of a specific
30182 * {@link ui.router.util.type:UrlMatcher `UrlMatcher`} object, this is the name of the
30183 * parameter in which `val` is stored. Can be used for meta-programming of `Type` objects.
30184 * @returns {Boolean} Returns `true` if the value matches the type, otherwise `false`.
30186 Type.prototype.is = function(val, key) {
30192 * @name ui.router.util.type:Type#encode
30193 * @methodOf ui.router.util.type:Type
30196 * Encodes a custom/native type value to a string that can be embedded in a URL. Note that the
30197 * return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it
30198 * only needs to be a representation of `val` that has been coerced to a string.
30200 * @param {*} val The value to encode.
30201 * @param {string} key The name of the parameter in which `val` is stored. Can be used for
30202 * meta-programming of `Type` objects.
30203 * @returns {string} Returns a string representation of `val` that can be encoded in a URL.
30205 Type.prototype.encode = function(val, key) {
30211 * @name ui.router.util.type:Type#decode
30212 * @methodOf ui.router.util.type:Type
30215 * Converts a parameter value (from URL string or transition param) to a custom/native value.
30217 * @param {string} val The URL parameter value to decode.
30218 * @param {string} key The name of the parameter in which `val` is stored. Can be used for
30219 * meta-programming of `Type` objects.
30220 * @returns {*} Returns a custom representation of the URL parameter value.
30222 Type.prototype.decode = function(val, key) {
30228 * @name ui.router.util.type:Type#equals
30229 * @methodOf ui.router.util.type:Type
30232 * Determines whether two decoded values are equivalent.
30234 * @param {*} a A value to compare against.
30235 * @param {*} b A value to compare against.
30236 * @returns {Boolean} Returns `true` if the values are equivalent/equal, otherwise `false`.
30238 Type.prototype.equals = function(a, b) {
30242 Type.prototype.$subPattern = function() {
30243 var sub = this.pattern.toString();
30244 return sub.substr(1, sub.length - 2);
30247 Type.prototype.pattern = /.*/;
30249 Type.prototype.toString = function() { return "{Type:" + this.name + "}"; };
30251 /** Given an encoded string, or a decoded object, returns a decoded object */
30252 Type.prototype.$normalize = function(val) {
30253 return this.is(val) ? val : this.decode(val);
30257 * Wraps an existing custom Type as an array of Type, depending on 'mode'.
30259 * - urlmatcher pattern "/path?{queryParam[]:int}"
30260 * - url: "/path?queryParam=1&queryParam=2
30261 * - $stateParams.queryParam will be [1, 2]
30262 * if `mode` is "auto", then
30263 * - url: "/path?queryParam=1 will create $stateParams.queryParam: 1
30264 * - url: "/path?queryParam=1&queryParam=2 will create $stateParams.queryParam: [1, 2]
30266 Type.prototype.$asArray = function(mode, isSearch) {
30267 if (!mode) return this;
30268 if (mode === "auto" && !isSearch) throw new Error("'auto' array mode is for query parameters only");
30270 function ArrayType(type, mode) {
30271 function bindTo(type, callbackName) {
30272 return function() {
30273 return type[callbackName].apply(type, arguments);
30277 // Wrap non-array value as array
30278 function arrayWrap(val) { return isArray(val) ? val : (isDefined(val) ? [ val ] : []); }
30279 // Unwrap array value for "auto" mode. Return undefined for empty array.
30280 function arrayUnwrap(val) {
30281 switch(val.length) {
30282 case 0: return undefined;
30283 case 1: return mode === "auto" ? val[0] : val;
30284 default: return val;
30287 function falsey(val) { return !val; }
30289 // Wraps type (.is/.encode/.decode) functions to operate on each value of an array
30290 function arrayHandler(callback, allTruthyMode) {
30291 return function handleArray(val) {
30292 val = arrayWrap(val);
30293 var result = map(val, callback);
30294 if (allTruthyMode === true)
30295 return filter(result, falsey).length === 0;
30296 return arrayUnwrap(result);
30300 // Wraps type (.equals) functions to operate on each value of an array
30301 function arrayEqualsHandler(callback) {
30302 return function handleArray(val1, val2) {
30303 var left = arrayWrap(val1), right = arrayWrap(val2);
30304 if (left.length !== right.length) return false;
30305 for (var i = 0; i < left.length; i++) {
30306 if (!callback(left[i], right[i])) return false;
30312 this.encode = arrayHandler(bindTo(type, 'encode'));
30313 this.decode = arrayHandler(bindTo(type, 'decode'));
30314 this.is = arrayHandler(bindTo(type, 'is'), true);
30315 this.equals = arrayEqualsHandler(bindTo(type, 'equals'));
30316 this.pattern = type.pattern;
30317 this.$normalize = arrayHandler(bindTo(type, '$normalize'));
30318 this.name = type.name;
30319 this.$arrayMode = mode;
30322 return new ArrayType(this, mode);
30329 * @name ui.router.util.$urlMatcherFactory
30332 * Factory for {@link ui.router.util.type:UrlMatcher `UrlMatcher`} instances. The factory
30333 * is also available to providers under the name `$urlMatcherFactoryProvider`.
30335 function $UrlMatcherFactory() {
30338 var isCaseInsensitive = false, isStrictMode = true, defaultSquashPolicy = false;
30340 function valToString(val) { return val != null ? val.toString().replace(/\//g, "%2F") : val; }
30341 function valFromString(val) { return val != null ? val.toString().replace(/%2F/g, "/") : val; }
30343 var $types = {}, enqueue = true, typeQueue = [], injector, defaultTypes = {
30345 encode: valToString,
30346 decode: valFromString,
30347 // TODO: in 1.0, make string .is() return false if value is undefined/null by default.
30348 // In 0.2.x, string params are optional by default for backwards compat
30349 is: function(val) { return val == null || !isDefined(val) || typeof val === "string"; },
30353 encode: valToString,
30354 decode: function(val) { return parseInt(val, 10); },
30355 is: function(val) { return isDefined(val) && this.decode(val.toString()) === val; },
30359 encode: function(val) { return val ? 1 : 0; },
30360 decode: function(val) { return parseInt(val, 10) !== 0; },
30361 is: function(val) { return val === true || val === false; },
30365 encode: function (val) {
30368 return [ val.getFullYear(),
30369 ('0' + (val.getMonth() + 1)).slice(-2),
30370 ('0' + val.getDate()).slice(-2)
30373 decode: function (val) {
30374 if (this.is(val)) return val;
30375 var match = this.capture.exec(val);
30376 return match ? new Date(match[1], match[2] - 1, match[3]) : undefined;
30378 is: function(val) { return val instanceof Date && !isNaN(val.valueOf()); },
30379 equals: function (a, b) { return this.is(a) && this.is(b) && a.toISOString() === b.toISOString(); },
30380 pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/,
30381 capture: /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/
30384 encode: angular.toJson,
30385 decode: angular.fromJson,
30386 is: angular.isObject,
30387 equals: angular.equals,
30390 any: { // does not encode/decode
30391 encode: angular.identity,
30392 decode: angular.identity,
30393 equals: angular.equals,
30398 function getDefaultConfig() {
30400 strict: isStrictMode,
30401 caseInsensitive: isCaseInsensitive
30405 function isInjectable(value) {
30406 return (isFunction(value) || (isArray(value) && isFunction(value[value.length - 1])));
30410 * [Internal] Get the default value of a parameter, which may be an injectable function.
30412 $UrlMatcherFactory.$$getDefaultValue = function(config) {
30413 if (!isInjectable(config.value)) return config.value;
30414 if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
30415 return injector.invoke(config.value);
30420 * @name ui.router.util.$urlMatcherFactory#caseInsensitive
30421 * @methodOf ui.router.util.$urlMatcherFactory
30424 * Defines whether URL matching should be case sensitive (the default behavior), or not.
30426 * @param {boolean} value `false` to match URL in a case sensitive manner; otherwise `true`;
30427 * @returns {boolean} the current value of caseInsensitive
30429 this.caseInsensitive = function(value) {
30430 if (isDefined(value))
30431 isCaseInsensitive = value;
30432 return isCaseInsensitive;
30437 * @name ui.router.util.$urlMatcherFactory#strictMode
30438 * @methodOf ui.router.util.$urlMatcherFactory
30441 * Defines whether URLs should match trailing slashes, or not (the default behavior).
30443 * @param {boolean=} value `false` to match trailing slashes in URLs, otherwise `true`.
30444 * @returns {boolean} the current value of strictMode
30446 this.strictMode = function(value) {
30447 if (isDefined(value))
30448 isStrictMode = value;
30449 return isStrictMode;
30454 * @name ui.router.util.$urlMatcherFactory#defaultSquashPolicy
30455 * @methodOf ui.router.util.$urlMatcherFactory
30458 * Sets the default behavior when generating or matching URLs with default parameter values.
30460 * @param {string} value A string that defines the default parameter URL squashing behavior.
30461 * `nosquash`: When generating an href with a default parameter value, do not squash the parameter value from the URL
30462 * `slash`: When generating an href with a default parameter value, squash (remove) the parameter value, and, if the
30463 * parameter is surrounded by slashes, squash (remove) one slash from the URL
30464 * any other string, e.g. "~": When generating an href with a default parameter value, squash (remove)
30465 * the parameter value from the URL and replace it with this string.
30467 this.defaultSquashPolicy = function(value) {
30468 if (!isDefined(value)) return defaultSquashPolicy;
30469 if (value !== true && value !== false && !isString(value))
30470 throw new Error("Invalid squash policy: " + value + ". Valid policies: false, true, arbitrary-string");
30471 defaultSquashPolicy = value;
30477 * @name ui.router.util.$urlMatcherFactory#compile
30478 * @methodOf ui.router.util.$urlMatcherFactory
30481 * Creates a {@link ui.router.util.type:UrlMatcher `UrlMatcher`} for the specified pattern.
30483 * @param {string} pattern The URL pattern.
30484 * @param {Object} config The config object hash.
30485 * @returns {UrlMatcher} The UrlMatcher.
30487 this.compile = function (pattern, config) {
30488 return new UrlMatcher(pattern, extend(getDefaultConfig(), config));
30493 * @name ui.router.util.$urlMatcherFactory#isMatcher
30494 * @methodOf ui.router.util.$urlMatcherFactory
30497 * Returns true if the specified object is a `UrlMatcher`, or false otherwise.
30499 * @param {Object} object The object to perform the type check against.
30500 * @returns {Boolean} Returns `true` if the object matches the `UrlMatcher` interface, by
30501 * implementing all the same methods.
30503 this.isMatcher = function (o) {
30504 if (!isObject(o)) return false;
30507 forEach(UrlMatcher.prototype, function(val, name) {
30508 if (isFunction(val)) {
30509 result = result && (isDefined(o[name]) && isFunction(o[name]));
30517 * @name ui.router.util.$urlMatcherFactory#type
30518 * @methodOf ui.router.util.$urlMatcherFactory
30521 * Registers a custom {@link ui.router.util.type:Type `Type`} object that can be used to
30522 * generate URLs with typed parameters.
30524 * @param {string} name The type name.
30525 * @param {Object|Function} definition The type definition. See
30526 * {@link ui.router.util.type:Type `Type`} for information on the values accepted.
30527 * @param {Object|Function} definitionFn (optional) A function that is injected before the app
30528 * runtime starts. The result of this function is merged into the existing `definition`.
30529 * See {@link ui.router.util.type:Type `Type`} for information on the values accepted.
30531 * @returns {Object} Returns `$urlMatcherFactoryProvider`.
30534 * This is a simple example of a custom type that encodes and decodes items from an
30535 * array, using the array index as the URL-encoded value:
30538 * var list = ['John', 'Paul', 'George', 'Ringo'];
30540 * $urlMatcherFactoryProvider.type('listItem', {
30541 * encode: function(item) {
30542 * // Represent the list item in the URL using its corresponding index
30543 * return list.indexOf(item);
30545 * decode: function(item) {
30546 * // Look up the list item by index
30547 * return list[parseInt(item, 10)];
30549 * is: function(item) {
30550 * // Ensure the item is valid by checking to see that it appears
30552 * return list.indexOf(item) > -1;
30556 * $stateProvider.state('list', {
30557 * url: "/list/{item:listItem}",
30558 * controller: function($scope, $stateParams) {
30559 * console.log($stateParams.item);
30565 * // Changes URL to '/list/3', logs "Ringo" to the console
30566 * $state.go('list', { item: "Ringo" });
30569 * This is a more complex example of a type that relies on dependency injection to
30570 * interact with services, and uses the parameter name from the URL to infer how to
30571 * handle encoding and decoding parameter values:
30574 * // Defines a custom type that gets a value from a service,
30575 * // where each service gets different types of values from
30576 * // a backend API:
30577 * $urlMatcherFactoryProvider.type('dbObject', {}, function(Users, Posts) {
30579 * // Matches up services to URL parameter names
30586 * encode: function(object) {
30587 * // Represent the object in the URL using its unique ID
30588 * return object.id;
30590 * decode: function(value, key) {
30591 * // Look up the object by ID, using the parameter
30592 * // name (key) to call the correct service
30593 * return services[key].findById(value);
30595 * is: function(object, key) {
30596 * // Check that object is a valid dbObject
30597 * return angular.isObject(object) && object.id && services[key];
30599 * equals: function(a, b) {
30600 * // Check the equality of decoded objects by comparing
30601 * // their unique IDs
30602 * return a.id === b.id;
30607 * // In a config() block, you can then attach URLs with
30608 * // type-annotated parameters:
30609 * $stateProvider.state('users', {
30612 * }).state('users.item', {
30613 * url: "/{user:dbObject}",
30614 * controller: function($scope, $stateParams) {
30615 * // $stateParams.user will now be an object returned from
30616 * // the Users service
30622 this.type = function (name, definition, definitionFn) {
30623 if (!isDefined(definition)) return $types[name];
30624 if ($types.hasOwnProperty(name)) throw new Error("A type named '" + name + "' has already been defined.");
30626 $types[name] = new Type(extend({ name: name }, definition));
30627 if (definitionFn) {
30628 typeQueue.push({ name: name, def: definitionFn });
30629 if (!enqueue) flushTypeQueue();
30634 // `flushTypeQueue()` waits until `$urlMatcherFactory` is injected before invoking the queued `definitionFn`s
30635 function flushTypeQueue() {
30636 while(typeQueue.length) {
30637 var type = typeQueue.shift();
30638 if (type.pattern) throw new Error("You cannot override a type's .pattern at runtime.");
30639 angular.extend($types[type.name], injector.invoke(type.def));
30643 // Register default types. Store them in the prototype of $types.
30644 forEach(defaultTypes, function(type, name) { $types[name] = new Type(extend({name: name}, type)); });
30645 $types = inherit($types, {});
30647 /* No need to document $get, since it returns this */
30648 this.$get = ['$injector', function ($injector) {
30649 injector = $injector;
30653 forEach(defaultTypes, function(type, name) {
30654 if (!$types[name]) $types[name] = new Type(type);
30659 this.Param = function Param(id, type, config, location) {
30661 config = unwrapShorthand(config);
30662 type = getType(config, type, location);
30663 var arrayMode = getArrayMode();
30664 type = arrayMode ? type.$asArray(arrayMode, location === "search") : type;
30665 if (type.name === "string" && !arrayMode && location === "path" && config.value === undefined)
30666 config.value = ""; // for 0.2.x; in 0.3.0+ do not automatically default to ""
30667 var isOptional = config.value !== undefined;
30668 var squash = getSquashPolicy(config, isOptional);
30669 var replace = getReplace(config, arrayMode, isOptional, squash);
30671 function unwrapShorthand(config) {
30672 var keys = isObject(config) ? objectKeys(config) : [];
30673 var isShorthand = indexOf(keys, "value") === -1 && indexOf(keys, "type") === -1 &&
30674 indexOf(keys, "squash") === -1 && indexOf(keys, "array") === -1;
30675 if (isShorthand) config = { value: config };
30676 config.$$fn = isInjectable(config.value) ? config.value : function () { return config.value; };
30680 function getType(config, urlType, location) {
30681 if (config.type && urlType) throw new Error("Param '"+id+"' has two type configurations.");
30682 if (urlType) return urlType;
30683 if (!config.type) return (location === "config" ? $types.any : $types.string);
30684 return config.type instanceof Type ? config.type : new Type(config.type);
30687 // array config: param name (param[]) overrides default settings. explicit config overrides param name.
30688 function getArrayMode() {
30689 var arrayDefaults = { array: (location === "search" ? "auto" : false) };
30690 var arrayParamNomenclature = id.match(/\[\]$/) ? { array: true } : {};
30691 return extend(arrayDefaults, arrayParamNomenclature, config).array;
30695 * returns false, true, or the squash value to indicate the "default parameter url squash policy".
30697 function getSquashPolicy(config, isOptional) {
30698 var squash = config.squash;
30699 if (!isOptional || squash === false) return false;
30700 if (!isDefined(squash) || squash == null) return defaultSquashPolicy;
30701 if (squash === true || isString(squash)) return squash;
30702 throw new Error("Invalid squash policy: '" + squash + "'. Valid policies: false, true, or arbitrary string");
30705 function getReplace(config, arrayMode, isOptional, squash) {
30706 var replace, configuredKeys, defaultPolicy = [
30707 { from: "", to: (isOptional || arrayMode ? undefined : "") },
30708 { from: null, to: (isOptional || arrayMode ? undefined : "") }
30710 replace = isArray(config.replace) ? config.replace : [];
30711 if (isString(squash))
30712 replace.push({ from: squash, to: undefined });
30713 configuredKeys = map(replace, function(item) { return item.from; } );
30714 return filter(defaultPolicy, function(item) { return indexOf(configuredKeys, item.from) === -1; }).concat(replace);
30718 * [Internal] Get the default value of a parameter, which may be an injectable function.
30720 function $$getDefaultValue() {
30721 if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
30722 var defaultValue = injector.invoke(config.$$fn);
30723 if (defaultValue !== null && defaultValue !== undefined && !self.type.is(defaultValue))
30724 throw new Error("Default value (" + defaultValue + ") for parameter '" + self.id + "' is not an instance of Type (" + self.type.name + ")");
30725 return defaultValue;
30729 * [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the
30730 * default value, which may be the result of an injectable function.
30732 function $value(value) {
30733 function hasReplaceVal(val) { return function(obj) { return obj.from === val; }; }
30734 function $replace(value) {
30735 var replacement = map(filter(self.replace, hasReplaceVal(value)), function(obj) { return obj.to; });
30736 return replacement.length ? replacement[0] : value;
30738 value = $replace(value);
30739 return !isDefined(value) ? $$getDefaultValue() : self.type.$normalize(value);
30742 function toString() { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}"; }
30747 location: location,
30751 isOptional: isOptional,
30753 dynamic: undefined,
30759 function ParamSet(params) {
30760 extend(this, params || {});
30763 ParamSet.prototype = {
30764 $$new: function() {
30765 return inherit(this, extend(new ParamSet(), { $$parent: this}));
30767 $$keys: function () {
30768 var keys = [], chain = [], parent = this,
30769 ignore = objectKeys(ParamSet.prototype);
30770 while (parent) { chain.push(parent); parent = parent.$$parent; }
30772 forEach(chain, function(paramset) {
30773 forEach(objectKeys(paramset), function(key) {
30774 if (indexOf(keys, key) === -1 && indexOf(ignore, key) === -1) keys.push(key);
30779 $$values: function(paramValues) {
30780 var values = {}, self = this;
30781 forEach(self.$$keys(), function(key) {
30782 values[key] = self[key].value(paramValues && paramValues[key]);
30786 $$equals: function(paramValues1, paramValues2) {
30787 var equal = true, self = this;
30788 forEach(self.$$keys(), function(key) {
30789 var left = paramValues1 && paramValues1[key], right = paramValues2 && paramValues2[key];
30790 if (!self[key].type.equals(left, right)) equal = false;
30794 $$validates: function $$validate(paramValues) {
30795 var keys = this.$$keys(), i, param, rawVal, normalized, encoded;
30796 for (i = 0; i < keys.length; i++) {
30797 param = this[keys[i]];
30798 rawVal = paramValues[keys[i]];
30799 if ((rawVal === undefined || rawVal === null) && param.isOptional)
30800 break; // There was no parameter value, but the param is optional
30801 normalized = param.type.$normalize(rawVal);
30802 if (!param.type.is(normalized))
30803 return false; // The value was not of the correct Type, and could not be decoded to the correct Type
30804 encoded = param.type.encode(normalized);
30805 if (angular.isString(encoded) && !param.type.pattern.exec(encoded))
30806 return false; // The value was of the correct type, but when encoded, did not match the Type's regexp
30810 $$parent: undefined
30813 this.ParamSet = ParamSet;
30816 // Register as a provider so it's available to other providers
30817 angular.module('ui.router.util').provider('$urlMatcherFactory', $UrlMatcherFactory);
30818 angular.module('ui.router.util').run(['$urlMatcherFactory', function($urlMatcherFactory) { }]);
30822 * @name ui.router.router.$urlRouterProvider
30824 * @requires ui.router.util.$urlMatcherFactoryProvider
30825 * @requires $locationProvider
30828 * `$urlRouterProvider` has the responsibility of watching `$location`.
30829 * When `$location` changes it runs through a list of rules one by one until a
30830 * match is found. `$urlRouterProvider` is used behind the scenes anytime you specify
30831 * a url in a state configuration. All urls are compiled into a UrlMatcher object.
30833 * There are several methods on `$urlRouterProvider` that make it useful to use directly
30834 * in your module config.
30836 $UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider'];
30837 function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
30838 var rules = [], otherwise = null, interceptDeferred = false, listener;
30840 // Returns a string that is a prefix of all strings matching the RegExp
30841 function regExpPrefix(re) {
30842 var prefix = /^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(re.source);
30843 return (prefix != null) ? prefix[1].replace(/\\(.)/g, "$1") : '';
30846 // Interpolates matched values into a String.replace()-style pattern
30847 function interpolate(pattern, match) {
30848 return pattern.replace(/\$(\$|\d{1,2})/, function (m, what) {
30849 return match[what === '$' ? 0 : Number(what)];
30855 * @name ui.router.router.$urlRouterProvider#rule
30856 * @methodOf ui.router.router.$urlRouterProvider
30859 * Defines rules that are used by `$urlRouterProvider` to find matches for
30864 * var app = angular.module('app', ['ui.router.router']);
30866 * app.config(function ($urlRouterProvider) {
30867 * // Here's an example of how you might allow case insensitive urls
30868 * $urlRouterProvider.rule(function ($injector, $location) {
30869 * var path = $location.path(),
30870 * normalized = path.toLowerCase();
30872 * if (path !== normalized) {
30873 * return normalized;
30879 * @param {object} rule Handler function that takes `$injector` and `$location`
30880 * services as arguments. You can use them to return a valid path as a string.
30882 * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
30884 this.rule = function (rule) {
30885 if (!isFunction(rule)) throw new Error("'rule' must be a function");
30892 * @name ui.router.router.$urlRouterProvider#otherwise
30893 * @methodOf ui.router.router.$urlRouterProvider
30896 * Defines a path that is used when an invalid route is requested.
30900 * var app = angular.module('app', ['ui.router.router']);
30902 * app.config(function ($urlRouterProvider) {
30903 * // if the path doesn't match any of the urls you configured
30904 * // otherwise will take care of routing the user to the
30906 * $urlRouterProvider.otherwise('/index');
30908 * // Example of using function rule as param
30909 * $urlRouterProvider.otherwise(function ($injector, $location) {
30910 * return '/a/valid/url';
30915 * @param {string|object} rule The url path you want to redirect to or a function
30916 * rule that returns the url path. The function version is passed two params:
30917 * `$injector` and `$location` services, and must return a url string.
30919 * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
30921 this.otherwise = function (rule) {
30922 if (isString(rule)) {
30923 var redirect = rule;
30924 rule = function () { return redirect; };
30926 else if (!isFunction(rule)) throw new Error("'rule' must be a function");
30932 function handleIfMatch($injector, handler, match) {
30933 if (!match) return false;
30934 var result = $injector.invoke(handler, handler, { $match: match });
30935 return isDefined(result) ? result : true;
30940 * @name ui.router.router.$urlRouterProvider#when
30941 * @methodOf ui.router.router.$urlRouterProvider
30944 * Registers a handler for a given url matching. if handle is a string, it is
30945 * treated as a redirect, and is interpolated according to the syntax of match
30946 * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise).
30948 * If the handler is a function, it is injectable. It gets invoked if `$location`
30949 * matches. You have the option of inject the match object as `$match`.
30951 * The handler can return
30953 * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter`
30954 * will continue trying to find another one that matches.
30955 * - **string** which is treated as a redirect and passed to `$location.url()`
30956 * - **void** or any **truthy** value tells `$urlRouter` that the url was handled.
30960 * var app = angular.module('app', ['ui.router.router']);
30962 * app.config(function ($urlRouterProvider) {
30963 * $urlRouterProvider.when($state.url, function ($match, $stateParams) {
30964 * if ($state.$current.navigable !== state ||
30965 * !equalForKeys($match, $stateParams) {
30966 * $state.transitionTo(state, $match, false);
30972 * @param {string|object} what The incoming path that you want to redirect.
30973 * @param {string|object} handler The path you want to redirect your user to.
30975 this.when = function (what, handler) {
30976 var redirect, handlerIsString = isString(handler);
30977 if (isString(what)) what = $urlMatcherFactory.compile(what);
30979 if (!handlerIsString && !isFunction(handler) && !isArray(handler))
30980 throw new Error("invalid 'handler' in when()");
30983 matcher: function (what, handler) {
30984 if (handlerIsString) {
30985 redirect = $urlMatcherFactory.compile(handler);
30986 handler = ['$match', function ($match) { return redirect.format($match); }];
30988 return extend(function ($injector, $location) {
30989 return handleIfMatch($injector, handler, what.exec($location.path(), $location.search()));
30991 prefix: isString(what.prefix) ? what.prefix : ''
30994 regex: function (what, handler) {
30995 if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky");
30997 if (handlerIsString) {
30998 redirect = handler;
30999 handler = ['$match', function ($match) { return interpolate(redirect, $match); }];
31001 return extend(function ($injector, $location) {
31002 return handleIfMatch($injector, handler, what.exec($location.path()));
31004 prefix: regExpPrefix(what)
31009 var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp };
31011 for (var n in check) {
31012 if (check[n]) return this.rule(strategies[n](what, handler));
31015 throw new Error("invalid 'what' in when()");
31020 * @name ui.router.router.$urlRouterProvider#deferIntercept
31021 * @methodOf ui.router.router.$urlRouterProvider
31024 * Disables (or enables) deferring location change interception.
31026 * If you wish to customize the behavior of syncing the URL (for example, if you wish to
31027 * defer a transition but maintain the current URL), call this method at configuration time.
31028 * Then, at run time, call `$urlRouter.listen()` after you have configured your own
31029 * `$locationChangeSuccess` event handler.
31033 * var app = angular.module('app', ['ui.router.router']);
31035 * app.config(function ($urlRouterProvider) {
31037 * // Prevent $urlRouter from automatically intercepting URL changes;
31038 * // this allows you to configure custom behavior in between
31039 * // location changes and route synchronization:
31040 * $urlRouterProvider.deferIntercept();
31042 * }).run(function ($rootScope, $urlRouter, UserService) {
31044 * $rootScope.$on('$locationChangeSuccess', function(e) {
31045 * // UserService is an example service for managing user state
31046 * if (UserService.isLoggedIn()) return;
31048 * // Prevent $urlRouter's default handler from firing
31049 * e.preventDefault();
31051 * UserService.handleLogin().then(function() {
31052 * // Once the user has logged in, sync the current URL
31053 * // to the router:
31054 * $urlRouter.sync();
31058 * // Configures $urlRouter's listener *after* your custom listener
31059 * $urlRouter.listen();
31063 * @param {boolean} defer Indicates whether to defer location change interception. Passing
31064 no parameter is equivalent to `true`.
31066 this.deferIntercept = function (defer) {
31067 if (defer === undefined) defer = true;
31068 interceptDeferred = defer;
31073 * @name ui.router.router.$urlRouter
31075 * @requires $location
31076 * @requires $rootScope
31077 * @requires $injector
31078 * @requires $browser
31084 $get.$inject = ['$location', '$rootScope', '$injector', '$browser'];
31085 function $get( $location, $rootScope, $injector, $browser) {
31087 var baseHref = $browser.baseHref(), location = $location.url(), lastPushedUrl;
31089 function appendBasePath(url, isHtml5, absolute) {
31090 if (baseHref === '/') return url;
31091 if (isHtml5) return baseHref.slice(0, -1) + url;
31092 if (absolute) return baseHref.slice(1) + url;
31096 // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree
31097 function update(evt) {
31098 if (evt && evt.defaultPrevented) return;
31099 var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl;
31100 lastPushedUrl = undefined;
31101 // TODO: Re-implement this in 1.0 for https://github.com/angular-ui/ui-router/issues/1573
31102 //if (ignoreUpdate) return true;
31104 function check(rule) {
31105 var handled = rule($injector, $location);
31107 if (!handled) return false;
31108 if (isString(handled)) $location.replace().url(handled);
31111 var n = rules.length, i;
31113 for (i = 0; i < n; i++) {
31114 if (check(rules[i])) return;
31116 // always check otherwise last to allow dynamic updates to the set of rules
31117 if (otherwise) check(otherwise);
31120 function listen() {
31121 listener = listener || $rootScope.$on('$locationChangeSuccess', update);
31125 if (!interceptDeferred) listen();
31130 * @name ui.router.router.$urlRouter#sync
31131 * @methodOf ui.router.router.$urlRouter
31134 * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`.
31135 * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event,
31136 * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed
31137 * with the transition by calling `$urlRouter.sync()`.
31141 * angular.module('app', ['ui.router'])
31142 * .run(function($rootScope, $urlRouter) {
31143 * $rootScope.$on('$locationChangeSuccess', function(evt) {
31144 * // Halt state change from even starting
31145 * evt.preventDefault();
31146 * // Perform custom logic
31147 * var meetsRequirement = ...
31148 * // Continue with the update and state transition if logic allows
31149 * if (meetsRequirement) $urlRouter.sync();
31158 listen: function() {
31162 update: function(read) {
31164 location = $location.url();
31167 if ($location.url() === location) return;
31169 $location.url(location);
31170 $location.replace();
31173 push: function(urlMatcher, params, options) {
31174 var url = urlMatcher.format(params || {});
31176 // Handle the special hash param, if needed
31177 if (url !== null && params && params['#']) {
31178 url += '#' + params['#'];
31181 $location.url(url);
31182 lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined;
31183 if (options && options.replace) $location.replace();
31188 * @name ui.router.router.$urlRouter#href
31189 * @methodOf ui.router.router.$urlRouter
31192 * A URL generation method that returns the compiled URL for a given
31193 * {@link ui.router.util.type:UrlMatcher `UrlMatcher`}, populated with the provided parameters.
31197 * $bob = $urlRouter.href(new UrlMatcher("/about/:person"), {
31200 * // $bob == "/about/bob";
31203 * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate.
31204 * @param {object=} params An object of parameter values to fill the matcher's required parameters.
31205 * @param {object=} options Options object. The options are:
31207 * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
31209 * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher`
31211 href: function(urlMatcher, params, options) {
31212 if (!urlMatcher.validates(params)) return null;
31214 var isHtml5 = $locationProvider.html5Mode();
31215 if (angular.isObject(isHtml5)) {
31216 isHtml5 = isHtml5.enabled;
31219 var url = urlMatcher.format(params);
31220 options = options || {};
31222 if (!isHtml5 && url !== null) {
31223 url = "#" + $locationProvider.hashPrefix() + url;
31226 // Handle special hash param, if needed
31227 if (url !== null && params && params['#']) {
31228 url += '#' + params['#'];
31231 url = appendBasePath(url, isHtml5, options.absolute);
31233 if (!options.absolute || !url) {
31237 var slash = (!isHtml5 && url ? '/' : ''), port = $location.port();
31238 port = (port === 80 || port === 443 ? '' : ':' + port);
31240 return [$location.protocol(), '://', $location.host(), port, slash, url].join('');
31246 angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);
31250 * @name ui.router.state.$stateProvider
31252 * @requires ui.router.router.$urlRouterProvider
31253 * @requires ui.router.util.$urlMatcherFactoryProvider
31256 * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely
31259 * A state corresponds to a "place" in the application in terms of the overall UI and
31260 * navigation. A state describes (via the controller / template / view properties) what
31261 * the UI looks like and does at that place.
31263 * States often have things in common, and the primary way of factoring out these
31264 * commonalities in this model is via the state hierarchy, i.e. parent/child states aka
31267 * The `$stateProvider` provides interfaces to declare these states for your app.
31269 $StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider'];
31270 function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
31272 var root, states = {}, $state, queue = {}, abstractKey = 'abstract';
31274 // Builds state properties from definition passed to registerState()
31275 var stateBuilder = {
31277 // Derive parent state from a hierarchical name only if 'parent' is not explicitly defined.
31278 // state.children = [];
31279 // if (parent) parent.children.push(state);
31280 parent: function(state) {
31281 if (isDefined(state.parent) && state.parent) return findState(state.parent);
31282 // regex matches any valid composite state name
31283 // would match "contact.list" but not "contacts"
31284 var compositeName = /^(.+)\.[^.]+$/.exec(state.name);
31285 return compositeName ? findState(compositeName[1]) : root;
31288 // inherit 'data' from parent and override by own values (if any)
31289 data: function(state) {
31290 if (state.parent && state.parent.data) {
31291 state.data = state.self.data = extend({}, state.parent.data, state.data);
31296 // Build a URLMatcher if necessary, either via a relative or absolute URL
31297 url: function(state) {
31298 var url = state.url, config = { params: state.params || {} };
31300 if (isString(url)) {
31301 if (url.charAt(0) == '^') return $urlMatcherFactory.compile(url.substring(1), config);
31302 return (state.parent.navigable || root).url.concat(url, config);
31305 if (!url || $urlMatcherFactory.isMatcher(url)) return url;
31306 throw new Error("Invalid url '" + url + "' in state '" + state + "'");
31309 // Keep track of the closest ancestor state that has a URL (i.e. is navigable)
31310 navigable: function(state) {
31311 return state.url ? state : (state.parent ? state.parent.navigable : null);
31314 // Own parameters for this state. state.url.params is already built at this point. Create and add non-url params
31315 ownParams: function(state) {
31316 var params = state.url && state.url.params || new $$UMFP.ParamSet();
31317 forEach(state.params || {}, function(config, id) {
31318 if (!params[id]) params[id] = new $$UMFP.Param(id, null, config, "config");
31323 // Derive parameters for this state and ensure they're a super-set of parent's parameters
31324 params: function(state) {
31325 return state.parent && state.parent.params ? extend(state.parent.params.$$new(), state.ownParams) : new $$UMFP.ParamSet();
31328 // If there is no explicit multi-view configuration, make one up so we don't have
31329 // to handle both cases in the view directive later. Note that having an explicit
31330 // 'views' property will mean the default unnamed view properties are ignored. This
31331 // is also a good time to resolve view names to absolute names, so everything is a
31332 // straight lookup at link time.
31333 views: function(state) {
31336 forEach(isDefined(state.views) ? state.views : { '': state }, function (view, name) {
31337 if (name.indexOf('@') < 0) name += '@' + state.parent.name;
31338 views[name] = view;
31343 // Keep a full path from the root down to this state as this is needed for state activation.
31344 path: function(state) {
31345 return state.parent ? state.parent.path.concat(state) : []; // exclude root from path
31348 // Speed up $state.contains() as it's used a lot
31349 includes: function(state) {
31350 var includes = state.parent ? extend({}, state.parent.includes) : {};
31351 includes[state.name] = true;
31358 function isRelative(stateName) {
31359 return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0;
31362 function findState(stateOrName, base) {
31363 if (!stateOrName) return undefined;
31365 var isStr = isString(stateOrName),
31366 name = isStr ? stateOrName : stateOrName.name,
31367 path = isRelative(name);
31370 if (!base) throw new Error("No reference point given for path '" + name + "'");
31371 base = findState(base);
31373 var rel = name.split("."), i = 0, pathLength = rel.length, current = base;
31375 for (; i < pathLength; i++) {
31376 if (rel[i] === "" && i === 0) {
31380 if (rel[i] === "^") {
31381 if (!current.parent) throw new Error("Path '" + name + "' not valid for state '" + base.name + "'");
31382 current = current.parent;
31387 rel = rel.slice(i).join(".");
31388 name = current.name + (current.name && rel ? "." : "") + rel;
31390 var state = states[name];
31392 if (state && (isStr || (!isStr && (state === stateOrName || state.self === stateOrName)))) {
31398 function queueState(parentName, state) {
31399 if (!queue[parentName]) {
31400 queue[parentName] = [];
31402 queue[parentName].push(state);
31405 function flushQueuedChildren(parentName) {
31406 var queued = queue[parentName] || [];
31407 while(queued.length) {
31408 registerState(queued.shift());
31412 function registerState(state) {
31413 // Wrap a new object around the state so we can store our private details easily.
31414 state = inherit(state, {
31416 resolve: state.resolve || {},
31417 toString: function() { return this.name; }
31420 var name = state.name;
31421 if (!isString(name) || name.indexOf('@') >= 0) throw new Error("State must have a valid name");
31422 if (states.hasOwnProperty(name)) throw new Error("State '" + name + "'' is already defined");
31425 var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.'))
31426 : (isString(state.parent)) ? state.parent
31427 : (isObject(state.parent) && isString(state.parent.name)) ? state.parent.name
31430 // If parent is not registered yet, add state to queue and register later
31431 if (parentName && !states[parentName]) {
31432 return queueState(parentName, state.self);
31435 for (var key in stateBuilder) {
31436 if (isFunction(stateBuilder[key])) state[key] = stateBuilder[key](state, stateBuilder.$delegates[key]);
31438 states[name] = state;
31440 // Register the state in the global state list and with $urlRouter if necessary.
31441 if (!state[abstractKey] && state.url) {
31442 $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) {
31443 if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) {
31444 $state.transitionTo(state, $match, { inherit: true, location: false });
31449 // Register any queued children
31450 flushQueuedChildren(name);
31455 // Checks text to see if it looks like a glob.
31456 function isGlob (text) {
31457 return text.indexOf('*') > -1;
31460 // Returns true if glob matches current $state name.
31461 function doesStateMatchGlob (glob) {
31462 var globSegments = glob.split('.'),
31463 segments = $state.$current.name.split('.');
31465 //match single stars
31466 for (var i = 0, l = globSegments.length; i < l; i++) {
31467 if (globSegments[i] === '*') {
31472 //match greedy starts
31473 if (globSegments[0] === '**') {
31474 segments = segments.slice(indexOf(segments, globSegments[1]));
31475 segments.unshift('**');
31477 //match greedy ends
31478 if (globSegments[globSegments.length - 1] === '**') {
31479 segments.splice(indexOf(segments, globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE);
31480 segments.push('**');
31483 if (globSegments.length != segments.length) {
31487 return segments.join('') === globSegments.join('');
31491 // Implicit root state that is always active
31492 root = registerState({
31498 root.navigable = null;
31503 * @name ui.router.state.$stateProvider#decorator
31504 * @methodOf ui.router.state.$stateProvider
31507 * Allows you to extend (carefully) or override (at your own peril) the
31508 * `stateBuilder` object used internally by `$stateProvider`. This can be used
31509 * to add custom functionality to ui-router, for example inferring templateUrl
31510 * based on the state name.
31512 * When passing only a name, it returns the current (original or decorated) builder
31513 * function that matches `name`.
31515 * The builder functions that can be decorated are listed below. Though not all
31516 * necessarily have a good use case for decoration, that is up to you to decide.
31518 * In addition, users can attach custom decorators, which will generate new
31519 * properties within the state's internal definition. There is currently no clear
31520 * use-case for this beyond accessing internal states (i.e. $state.$current),
31521 * however, expect this to become increasingly relevant as we introduce additional
31522 * meta-programming features.
31524 * **Warning**: Decorators should not be interdependent because the order of
31525 * execution of the builder functions in non-deterministic. Builder functions
31526 * should only be dependent on the state definition object and super function.
31529 * Existing builder functions and current return values:
31531 * - **parent** `{object}` - returns the parent state object.
31532 * - **data** `{object}` - returns state data, including any inherited data that is not
31533 * overridden by own values (if any).
31534 * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher}
31536 * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is
31538 * - **params** `{object}` - returns an array of state params that are ensured to
31539 * be a super-set of parent's params.
31540 * - **views** `{object}` - returns a views object where each key is an absolute view
31541 * name (i.e. "viewName@stateName") and each value is the config object
31542 * (template, controller) for the view. Even when you don't use the views object
31543 * explicitly on a state config, one is still created for you internally.
31544 * So by decorating this builder function you have access to decorating template
31545 * and controller properties.
31546 * - **ownParams** `{object}` - returns an array of params that belong to the state,
31547 * not including any params defined by ancestor states.
31548 * - **path** `{string}` - returns the full path from the root down to this state.
31549 * Needed for state activation.
31550 * - **includes** `{object}` - returns an object that includes every state that
31551 * would pass a `$state.includes()` test.
31555 * // Override the internal 'views' builder with a function that takes the state
31556 * // definition, and a reference to the internal function being overridden:
31557 * $stateProvider.decorator('views', function (state, parent) {
31559 * views = parent(state);
31561 * angular.forEach(views, function (config, name) {
31562 * var autoName = (state.name + '.' + name).replace('.', '/');
31563 * config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';
31564 * result[name] = config;
31569 * $stateProvider.state('home', {
31571 * 'contact.list': { controller: 'ListController' },
31572 * 'contact.item': { controller: 'ItemController' }
31578 * $state.go('home');
31579 * // Auto-populates list and item views with /partials/home/contact/list.html,
31580 * // and /partials/home/contact/item.html, respectively.
31583 * @param {string} name The name of the builder function to decorate.
31584 * @param {object} func A function that is responsible for decorating the original
31585 * builder function. The function receives two parameters:
31587 * - `{object}` - state - The state config object.
31588 * - `{object}` - super - The original builder function.
31590 * @return {object} $stateProvider - $stateProvider instance
31592 this.decorator = decorator;
31593 function decorator(name, func) {
31594 /*jshint validthis: true */
31595 if (isString(name) && !isDefined(func)) {
31596 return stateBuilder[name];
31598 if (!isFunction(func) || !isString(name)) {
31601 if (stateBuilder[name] && !stateBuilder.$delegates[name]) {
31602 stateBuilder.$delegates[name] = stateBuilder[name];
31604 stateBuilder[name] = func;
31610 * @name ui.router.state.$stateProvider#state
31611 * @methodOf ui.router.state.$stateProvider
31614 * Registers a state configuration under a given state name. The stateConfig object
31615 * has the following acceptable properties.
31617 * @param {string} name A unique state name, e.g. "home", "about", "contacts".
31618 * To create a parent/child state use a dot, e.g. "about.sales", "home.newest".
31619 * @param {object} stateConfig State configuration object.
31620 * @param {string|function=} stateConfig.template
31621 * <a id='template'></a>
31622 * html template as a string or a function that returns
31623 * an html template as a string which should be used by the uiView directives. This property
31624 * takes precedence over templateUrl.
31626 * If `template` is a function, it will be called with the following parameters:
31628 * - {array.<object>} - state parameters extracted from the current $location.path() by
31629 * applying the current state
31632 * "<h1>inline template definition</h1>" +
31633 * "<div ui-view></div>"</pre>
31634 * <pre>template: function(params) {
31635 * return "<h1>generated template</h1>"; }</pre>
31638 * @param {string|function=} stateConfig.templateUrl
31639 * <a id='templateUrl'></a>
31641 * path or function that returns a path to an html
31642 * template that should be used by uiView.
31644 * If `templateUrl` is a function, it will be called with the following parameters:
31646 * - {array.<object>} - state parameters extracted from the current $location.path() by
31647 * applying the current state
31649 * <pre>templateUrl: "home.html"</pre>
31650 * <pre>templateUrl: function(params) {
31651 * return myTemplates[params.pageId]; }</pre>
31653 * @param {function=} stateConfig.templateProvider
31654 * <a id='templateProvider'></a>
31655 * Provider function that returns HTML content string.
31656 * <pre> templateProvider:
31657 * function(MyTemplateService, params) {
31658 * return MyTemplateService.getTemplate(params.pageId);
31661 * @param {string|function=} stateConfig.controller
31662 * <a id='controller'></a>
31664 * Controller fn that should be associated with newly
31665 * related scope or the name of a registered controller if passed as a string.
31666 * Optionally, the ControllerAs may be declared here.
31667 * <pre>controller: "MyRegisteredController"</pre>
31669 * "MyRegisteredController as fooCtrl"}</pre>
31670 * <pre>controller: function($scope, MyService) {
31671 * $scope.data = MyService.getData(); }</pre>
31673 * @param {function=} stateConfig.controllerProvider
31674 * <a id='controllerProvider'></a>
31676 * Injectable provider function that returns the actual controller or string.
31677 * <pre>controllerProvider:
31678 * function(MyResolveData) {
31679 * if (MyResolveData.foo)
31681 * else if (MyResolveData.bar)
31682 * return "BarCtrl";
31683 * else return function($scope) {
31684 * $scope.baz = "Qux";
31688 * @param {string=} stateConfig.controllerAs
31689 * <a id='controllerAs'></a>
31691 * A controller alias name. If present the controller will be
31692 * published to scope under the controllerAs name.
31693 * <pre>controllerAs: "myCtrl"</pre>
31695 * @param {string|object=} stateConfig.parent
31696 * <a id='parent'></a>
31697 * Optionally specifies the parent state of this state.
31699 * <pre>parent: 'parentState'</pre>
31700 * <pre>parent: parentState // JS variable</pre>
31702 * @param {object=} stateConfig.resolve
31703 * <a id='resolve'></a>
31705 * An optional map<string, function> of dependencies which
31706 * should be injected into the controller. If any of these dependencies are promises,
31707 * the router will wait for them all to be resolved before the controller is instantiated.
31708 * If all the promises are resolved successfully, the $stateChangeSuccess event is fired
31709 * and the values of the resolved promises are injected into any controllers that reference them.
31710 * If any of the promises are rejected the $stateChangeError event is fired.
31712 * The map object is:
31714 * - key - {string}: name of dependency to be injected into controller
31715 * - factory - {string|function}: If string then it is alias for service. Otherwise if function,
31716 * it is injected and return value it treated as dependency. If result is a promise, it is
31717 * resolved before its value is injected into controller.
31721 * function($http, $stateParams) {
31722 * return $http.get("/api/foos/"+stateParams.fooID);
31726 * @param {string=} stateConfig.url
31729 * A url fragment with optional parameters. When a state is navigated or
31730 * transitioned to, the `$stateParams` service will be populated with any
31731 * parameters that were passed.
31733 * (See {@link ui.router.util.type:UrlMatcher UrlMatcher} `UrlMatcher`} for
31734 * more details on acceptable patterns )
31737 * <pre>url: "/home"
31738 * url: "/users/:userid"
31739 * url: "/books/{bookid:[a-zA-Z_-]}"
31740 * url: "/books/{categoryid:int}"
31741 * url: "/books/{publishername:string}/{categoryid:int}"
31742 * url: "/messages?before&after"
31743 * url: "/messages?{before:date}&{after:date}"
31744 * url: "/messages/:mailboxid?{before:date}&{after:date}"
31747 * @param {object=} stateConfig.views
31748 * <a id='views'></a>
31749 * an optional map<string, object> which defined multiple views, or targets views
31750 * manually/explicitly.
31754 * Targets three named `ui-view`s in the parent state's template
31757 * controller: "headerCtrl",
31758 * templateUrl: "header.html"
31760 * controller: "bodyCtrl",
31761 * templateUrl: "body.html"
31763 * controller: "footCtrl",
31764 * templateUrl: "footer.html"
31768 * Targets named `ui-view="header"` from grandparent state 'top''s template, and named `ui-view="body" from parent state's template.
31771 * controller: "msgHeaderCtrl",
31772 * templateUrl: "msgHeader.html"
31774 * controller: "messagesCtrl",
31775 * templateUrl: "messages.html"
31779 * @param {boolean=} [stateConfig.abstract=false]
31780 * <a id='abstract'></a>
31781 * An abstract state will never be directly activated,
31782 * but can provide inherited properties to its common children states.
31783 * <pre>abstract: true</pre>
31785 * @param {function=} stateConfig.onEnter
31786 * <a id='onEnter'></a>
31788 * Callback function for when a state is entered. Good way
31789 * to trigger an action or dispatch an event, such as opening a dialog.
31790 * If minifying your scripts, make sure to explictly annotate this function,
31791 * because it won't be automatically annotated by your build tools.
31793 * <pre>onEnter: function(MyService, $stateParams) {
31794 * MyService.foo($stateParams.myParam);
31797 * @param {function=} stateConfig.onExit
31798 * <a id='onExit'></a>
31800 * Callback function for when a state is exited. Good way to
31801 * trigger an action or dispatch an event, such as opening a dialog.
31802 * If minifying your scripts, make sure to explictly annotate this function,
31803 * because it won't be automatically annotated by your build tools.
31805 * <pre>onExit: function(MyService, $stateParams) {
31806 * MyService.cleanup($stateParams.myParam);
31809 * @param {boolean=} [stateConfig.reloadOnSearch=true]
31810 * <a id='reloadOnSearch'></a>
31812 * If `false`, will not retrigger the same state
31813 * just because a search/query parameter has changed (via $location.search() or $location.hash()).
31814 * Useful for when you'd like to modify $location.search() without triggering a reload.
31815 * <pre>reloadOnSearch: false</pre>
31817 * @param {object=} stateConfig.data
31818 * <a id='data'></a>
31820 * Arbitrary data object, useful for custom configuration. The parent state's `data` is
31821 * prototypally inherited. In other words, adding a data property to a state adds it to
31822 * the entire subtree via prototypal inheritance.
31825 * requiredRole: 'foo'
31828 * @param {object=} stateConfig.params
31829 * <a id='params'></a>
31831 * A map which optionally configures parameters declared in the `url`, or
31832 * defines additional non-url parameters. For each parameter being
31833 * configured, add a configuration object keyed to the name of the parameter.
31835 * Each parameter configuration object may contain the following properties:
31837 * - ** value ** - {object|function=}: specifies the default value for this
31838 * parameter. This implicitly sets this parameter as optional.
31840 * When UI-Router routes to a state and no value is
31841 * specified for this parameter in the URL or transition, the
31842 * default value will be used instead. If `value` is a function,
31843 * it will be injected and invoked, and the return value used.
31845 * *Note*: `undefined` is treated as "no default value" while `null`
31846 * is treated as "the default value is `null`".
31848 * *Shorthand*: If you only need to configure the default value of the
31849 * parameter, you may use a shorthand syntax. In the **`params`**
31850 * map, instead mapping the param name to a full parameter configuration
31851 * object, simply set map it to the default parameter value, e.g.:
31853 * <pre>// define a parameter's default value
31855 * param1: { value: "defaultValue" }
31857 * // shorthand default values
31859 * param1: "defaultValue",
31860 * param2: "param2Default"
31863 * - ** array ** - {boolean=}: *(default: false)* If true, the param value will be
31864 * treated as an array of values. If you specified a Type, the value will be
31865 * treated as an array of the specified Type. Note: query parameter values
31866 * default to a special `"auto"` mode.
31868 * For query parameters in `"auto"` mode, if multiple values for a single parameter
31869 * are present in the URL (e.g.: `/foo?bar=1&bar=2&bar=3`) then the values
31870 * are mapped to an array (e.g.: `{ foo: [ '1', '2', '3' ] }`). However, if
31871 * only one value is present (e.g.: `/foo?bar=1`) then the value is treated as single
31872 * value (e.g.: `{ foo: '1' }`).
31875 * param1: { array: true }
31878 * - ** squash ** - {bool|string=}: `squash` configures how a default parameter value is represented in the URL when
31879 * the current parameter value is the same as the default value. If `squash` is not set, it uses the
31880 * configured default squash policy.
31881 * (See {@link ui.router.util.$urlMatcherFactory#methods_defaultSquashPolicy `defaultSquashPolicy()`})
31883 * There are three squash settings:
31885 * - false: The parameter's default value is not squashed. It is encoded and included in the URL
31886 * - true: The parameter's default value is omitted from the URL. If the parameter is preceeded and followed
31887 * by slashes in the state's `url` declaration, then one of those slashes are omitted.
31888 * This can allow for cleaner looking URLs.
31889 * - `"<arbitrary string>"`: The parameter's default value is replaced with an arbitrary placeholder of your choice.
31893 * value: "defaultId",
31896 * // squash "defaultValue" to "~"
31899 * value: "defaultValue",
31907 * // Some state name examples
31909 * // stateName can be a single top-level name (must be unique).
31910 * $stateProvider.state("home", {});
31912 * // Or it can be a nested state name. This state is a child of the
31913 * // above "home" state.
31914 * $stateProvider.state("home.newest", {});
31916 * // Nest states as deeply as needed.
31917 * $stateProvider.state("home.newest.abc.xyz.inception", {});
31919 * // state() returns $stateProvider, so you can chain state declarations.
31921 * .state("home", {})
31922 * .state("about", {})
31923 * .state("contacts", {});
31927 this.state = state;
31928 function state(name, definition) {
31929 /*jshint validthis: true */
31930 if (isObject(name)) definition = name;
31931 else definition.name = name;
31932 registerState(definition);
31938 * @name ui.router.state.$state
31940 * @requires $rootScope
31942 * @requires ui.router.state.$view
31943 * @requires $injector
31944 * @requires ui.router.util.$resolve
31945 * @requires ui.router.state.$stateParams
31946 * @requires ui.router.router.$urlRouter
31948 * @property {object} params A param object, e.g. {sectionId: section.id)}, that
31949 * you'd like to test against the current active state.
31950 * @property {object} current A reference to the state's config object. However
31951 * you passed it in. Useful for accessing custom data.
31952 * @property {object} transition Currently pending transition. A promise that'll
31953 * resolve or reject.
31956 * `$state` service is responsible for representing states as well as transitioning
31957 * between them. It also provides interfaces to ask for current state or even states
31958 * you're coming from.
31961 $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$urlRouter', '$location', '$urlMatcherFactory'];
31962 function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $urlRouter, $location, $urlMatcherFactory) {
31964 var TransitionSuperseded = $q.reject(new Error('transition superseded'));
31965 var TransitionPrevented = $q.reject(new Error('transition prevented'));
31966 var TransitionAborted = $q.reject(new Error('transition aborted'));
31967 var TransitionFailed = $q.reject(new Error('transition failed'));
31969 // Handles the case where a state which is the target of a transition is not found, and the user
31970 // can optionally retry or defer the transition
31971 function handleRedirect(redirect, state, params, options) {
31974 * @name ui.router.state.$state#$stateNotFound
31975 * @eventOf ui.router.state.$state
31976 * @eventType broadcast on root scope
31978 * Fired when a requested state **cannot be found** using the provided state name during transition.
31979 * The event is broadcast allowing any handlers a single chance to deal with the error (usually by
31980 * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler,
31981 * you can see its three properties in the example. You can use `event.preventDefault()` to abort the
31982 * transition and the promise returned from `go` will be rejected with a `'transition aborted'` value.
31984 * @param {Object} event Event object.
31985 * @param {Object} unfoundState Unfound State information. Contains: `to, toParams, options` properties.
31986 * @param {State} fromState Current state object.
31987 * @param {Object} fromParams Current state params.
31992 * // somewhere, assume lazy.state has not been defined
31993 * $state.go("lazy.state", {a:1, b:2}, {inherit:false});
31995 * // somewhere else
31996 * $scope.$on('$stateNotFound',
31997 * function(event, unfoundState, fromState, fromParams){
31998 * console.log(unfoundState.to); // "lazy.state"
31999 * console.log(unfoundState.toParams); // {a:1, b:2}
32000 * console.log(unfoundState.options); // {inherit:false} + default options
32004 var evt = $rootScope.$broadcast('$stateNotFound', redirect, state, params);
32006 if (evt.defaultPrevented) {
32007 $urlRouter.update();
32008 return TransitionAborted;
32015 // Allow the handler to return a promise to defer state lookup retry
32016 if (options.$retry) {
32017 $urlRouter.update();
32018 return TransitionFailed;
32020 var retryTransition = $state.transition = $q.when(evt.retry);
32022 retryTransition.then(function() {
32023 if (retryTransition !== $state.transition) return TransitionSuperseded;
32024 redirect.options.$retry = true;
32025 return $state.transitionTo(redirect.to, redirect.toParams, redirect.options);
32027 return TransitionAborted;
32029 $urlRouter.update();
32031 return retryTransition;
32034 root.locals = { resolve: null, globals: { $stateParams: {} } };
32038 current: root.self,
32045 * @name ui.router.state.$state#reload
32046 * @methodOf ui.router.state.$state
32049 * A method that force reloads the current state. All resolves are re-resolved,
32050 * controllers reinstantiated, and events re-fired.
32054 * var app angular.module('app', ['ui.router']);
32056 * app.controller('ctrl', function ($scope, $state) {
32057 * $scope.reload = function(){
32063 * `reload()` is just an alias for:
32065 * $state.transitionTo($state.current, $stateParams, {
32066 * reload: true, inherit: false, notify: true
32070 * @param {string=|object=} state - A state name or a state object, which is the root of the resolves to be re-resolved.
32073 * //assuming app application consists of 3 states: 'contacts', 'contacts.detail', 'contacts.detail.item'
32074 * //and current state is 'contacts.detail.item'
32075 * var app angular.module('app', ['ui.router']);
32077 * app.controller('ctrl', function ($scope, $state) {
32078 * $scope.reload = function(){
32079 * //will reload 'contact.detail' and 'contact.detail.item' states
32080 * $state.reload('contact.detail');
32085 * `reload()` is just an alias for:
32087 * $state.transitionTo($state.current, $stateParams, {
32088 * reload: true, inherit: false, notify: true
32092 * @returns {promise} A promise representing the state of the new transition. See
32093 * {@link ui.router.state.$state#methods_go $state.go}.
32095 $state.reload = function reload(state) {
32096 return $state.transitionTo($state.current, $stateParams, { reload: state || true, inherit: false, notify: true});
32101 * @name ui.router.state.$state#go
32102 * @methodOf ui.router.state.$state
32105 * Convenience method for transitioning to a new state. `$state.go` calls
32106 * `$state.transitionTo` internally but automatically sets options to
32107 * `{ location: true, inherit: true, relative: $state.$current, notify: true }`.
32108 * This allows you to easily use an absolute or relative to path and specify
32109 * only the parameters you'd like to update (while letting unspecified parameters
32110 * inherit from the currently active ancestor states).
32114 * var app = angular.module('app', ['ui.router']);
32116 * app.controller('ctrl', function ($scope, $state) {
32117 * $scope.changeState = function () {
32118 * $state.go('contact.detail');
32122 * <img src='../ngdoc_assets/StateGoExamples.png'/>
32124 * @param {string} to Absolute state name or relative state path. Some examples:
32126 * - `$state.go('contact.detail')` - will go to the `contact.detail` state
32127 * - `$state.go('^')` - will go to a parent state
32128 * - `$state.go('^.sibling')` - will go to a sibling state
32129 * - `$state.go('.child.grandchild')` - will go to grandchild state
32131 * @param {object=} params A map of the parameters that will be sent to the state,
32132 * will populate $stateParams. Any parameters that are not specified will be inherited from currently
32133 * defined parameters. This allows, for example, going to a sibling state that shares parameters
32134 * specified in a parent state. Parameter inheritance only works between common ancestor states, I.e.
32135 * transitioning to a sibling will get you the parameters for all parents, transitioning to a child
32136 * will get you all current parameters, etc.
32137 * @param {object=} options Options object. The options are:
32139 * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
32140 * will not. If string, must be `"replace"`, which will update url and also replace last history record.
32141 * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
32142 * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
32143 * defines which state to be relative from.
32144 * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
32145 * - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params
32146 * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
32147 * use this when you want to force a reload when *everything* is the same, including search params.
32149 * @returns {promise} A promise representing the state of the new transition.
32151 * Possible success values:
32155 * <br/>Possible rejection values:
32157 * - 'transition superseded' - when a newer transition has been started after this one
32158 * - 'transition prevented' - when `event.preventDefault()` has been called in a `$stateChangeStart` listener
32159 * - 'transition aborted' - when `event.preventDefault()` has been called in a `$stateNotFound` listener or
32160 * when a `$stateNotFound` `event.retry` promise errors.
32161 * - 'transition failed' - when a state has been unsuccessfully found after 2 tries.
32162 * - *resolve error* - when an error has occurred with a `resolve`
32165 $state.go = function go(to, params, options) {
32166 return $state.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options));
32171 * @name ui.router.state.$state#transitionTo
32172 * @methodOf ui.router.state.$state
32175 * Low-level method for transitioning to a new state. {@link ui.router.state.$state#methods_go $state.go}
32176 * uses `transitionTo` internally. `$state.go` is recommended in most situations.
32180 * var app = angular.module('app', ['ui.router']);
32182 * app.controller('ctrl', function ($scope, $state) {
32183 * $scope.changeState = function () {
32184 * $state.transitionTo('contact.detail');
32189 * @param {string} to State name.
32190 * @param {object=} toParams A map of the parameters that will be sent to the state,
32191 * will populate $stateParams.
32192 * @param {object=} options Options object. The options are:
32194 * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
32195 * will not. If string, must be `"replace"`, which will update url and also replace last history record.
32196 * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url.
32197 * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'),
32198 * defines which state to be relative from.
32199 * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
32200 * - **`reload`** (v0.2.5) - {boolean=false|string=|object=}, If `true` will force transition even if the state or params
32201 * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
32202 * use this when you want to force a reload when *everything* is the same, including search params.
32203 * if String, then will reload the state with the name given in reload, and any children.
32204 * if Object, then a stateObj is expected, will reload the state found in stateObj, and any children.
32206 * @returns {promise} A promise representing the state of the new transition. See
32207 * {@link ui.router.state.$state#methods_go $state.go}.
32209 $state.transitionTo = function transitionTo(to, toParams, options) {
32210 toParams = toParams || {};
32212 location: true, inherit: false, relative: null, notify: true, reload: false, $retry: false
32215 var from = $state.$current, fromParams = $state.params, fromPath = from.path;
32216 var evt, toState = findState(to, options.relative);
32218 // Store the hash param for later (since it will be stripped out by various methods)
32219 var hash = toParams['#'];
32221 if (!isDefined(toState)) {
32222 var redirect = { to: to, toParams: toParams, options: options };
32223 var redirectResult = handleRedirect(redirect, from.self, fromParams, options);
32225 if (redirectResult) {
32226 return redirectResult;
32229 // Always retry once if the $stateNotFound was not prevented
32230 // (handles either redirect changed or state lazy-definition)
32232 toParams = redirect.toParams;
32233 options = redirect.options;
32234 toState = findState(to, options.relative);
32236 if (!isDefined(toState)) {
32237 if (!options.relative) throw new Error("No such state '" + to + "'");
32238 throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'");
32241 if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'");
32242 if (options.inherit) toParams = inheritParams($stateParams, toParams || {}, $state.$current, toState);
32243 if (!toState.params.$$validates(toParams)) return TransitionFailed;
32245 toParams = toState.params.$$values(toParams);
32248 var toPath = to.path;
32250 // Starting from the root of the path, keep all levels that haven't changed
32251 var keep = 0, state = toPath[keep], locals = root.locals, toLocals = [];
32253 if (!options.reload) {
32254 while (state && state === fromPath[keep] && state.ownParams.$$equals(toParams, fromParams)) {
32255 locals = toLocals[keep] = state.locals;
32257 state = toPath[keep];
32259 } else if (isString(options.reload) || isObject(options.reload)) {
32260 if (isObject(options.reload) && !options.reload.name) {
32261 throw new Error('Invalid reload state object');
32264 var reloadState = options.reload === true ? fromPath[0] : findState(options.reload);
32265 if (options.reload && !reloadState) {
32266 throw new Error("No such reload state '" + (isString(options.reload) ? options.reload : options.reload.name) + "'");
32269 while (state && state === fromPath[keep] && state !== reloadState) {
32270 locals = toLocals[keep] = state.locals;
32272 state = toPath[keep];
32276 // If we're going to the same state and all locals are kept, we've got nothing to do.
32277 // But clear 'transition', as we still want to cancel any other pending transitions.
32278 // TODO: We may not want to bump 'transition' if we're called from a location change
32279 // that we've initiated ourselves, because we might accidentally abort a legitimate
32280 // transition initiated from code?
32281 if (shouldSkipReload(to, toParams, from, fromParams, locals, options)) {
32282 if (hash) toParams['#'] = hash;
32283 $state.params = toParams;
32284 copy($state.params, $stateParams);
32285 if (options.location && to.navigable && to.navigable.url) {
32286 $urlRouter.push(to.navigable.url, toParams, {
32287 $$avoidResync: true, replace: options.location === 'replace'
32289 $urlRouter.update(true);
32291 $state.transition = null;
32292 return $q.when($state.current);
32295 // Filter parameters before we pass them to event handlers etc.
32296 toParams = filterByKeys(to.params.$$keys(), toParams || {});
32298 // Broadcast start event and cancel the transition if requested
32299 if (options.notify) {
32302 * @name ui.router.state.$state#$stateChangeStart
32303 * @eventOf ui.router.state.$state
32304 * @eventType broadcast on root scope
32306 * Fired when the state transition **begins**. You can use `event.preventDefault()`
32307 * to prevent the transition from happening and then the transition promise will be
32308 * rejected with a `'transition prevented'` value.
32310 * @param {Object} event Event object.
32311 * @param {State} toState The state being transitioned to.
32312 * @param {Object} toParams The params supplied to the `toState`.
32313 * @param {State} fromState The current state, pre-transition.
32314 * @param {Object} fromParams The params supplied to the `fromState`.
32319 * $rootScope.$on('$stateChangeStart',
32320 * function(event, toState, toParams, fromState, fromParams){
32321 * event.preventDefault();
32322 * // transitionTo() promise will be rejected with
32323 * // a 'transition prevented' error
32327 if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams).defaultPrevented) {
32328 $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams);
32329 $urlRouter.update();
32330 return TransitionPrevented;
32334 // Resolve locals for the remaining states, but don't update any global state just
32335 // yet -- if anything fails to resolve the current state needs to remain untouched.
32336 // We also set up an inheritance chain for the locals here. This allows the view directive
32337 // to quickly look up the correct definition for each view in the current state. Even
32338 // though we create the locals object itself outside resolveState(), it is initially
32339 // empty and gets filled asynchronously. We need to keep track of the promise for the
32340 // (fully resolved) current locals, and pass this down the chain.
32341 var resolved = $q.when(locals);
32343 for (var l = keep; l < toPath.length; l++, state = toPath[l]) {
32344 locals = toLocals[l] = inherit(locals);
32345 resolved = resolveState(state, toParams, state === to, resolved, locals, options);
32348 // Once everything is resolved, we are ready to perform the actual transition
32349 // and return a promise for the new state. We also keep track of what the
32350 // current promise is, so that we can detect overlapping transitions and
32351 // keep only the outcome of the last transition.
32352 var transition = $state.transition = resolved.then(function () {
32353 var l, entering, exiting;
32355 if ($state.transition !== transition) return TransitionSuperseded;
32357 // Exit 'from' states not kept
32358 for (l = fromPath.length - 1; l >= keep; l--) {
32359 exiting = fromPath[l];
32360 if (exiting.self.onExit) {
32361 $injector.invoke(exiting.self.onExit, exiting.self, exiting.locals.globals);
32363 exiting.locals = null;
32366 // Enter 'to' states not kept
32367 for (l = keep; l < toPath.length; l++) {
32368 entering = toPath[l];
32369 entering.locals = toLocals[l];
32370 if (entering.self.onEnter) {
32371 $injector.invoke(entering.self.onEnter, entering.self, entering.locals.globals);
32375 // Re-add the saved hash before we start returning things
32376 if (hash) toParams['#'] = hash;
32378 // Run it again, to catch any transitions in callbacks
32379 if ($state.transition !== transition) return TransitionSuperseded;
32381 // Update globals in $state
32382 $state.$current = to;
32383 $state.current = to.self;
32384 $state.params = toParams;
32385 copy($state.params, $stateParams);
32386 $state.transition = null;
32388 if (options.location && to.navigable) {
32389 $urlRouter.push(to.navigable.url, to.navigable.locals.globals.$stateParams, {
32390 $$avoidResync: true, replace: options.location === 'replace'
32394 if (options.notify) {
32397 * @name ui.router.state.$state#$stateChangeSuccess
32398 * @eventOf ui.router.state.$state
32399 * @eventType broadcast on root scope
32401 * Fired once the state transition is **complete**.
32403 * @param {Object} event Event object.
32404 * @param {State} toState The state being transitioned to.
32405 * @param {Object} toParams The params supplied to the `toState`.
32406 * @param {State} fromState The current state, pre-transition.
32407 * @param {Object} fromParams The params supplied to the `fromState`.
32409 $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams);
32411 $urlRouter.update(true);
32413 return $state.current;
32414 }, function (error) {
32415 if ($state.transition !== transition) return TransitionSuperseded;
32417 $state.transition = null;
32420 * @name ui.router.state.$state#$stateChangeError
32421 * @eventOf ui.router.state.$state
32422 * @eventType broadcast on root scope
32424 * Fired when an **error occurs** during transition. It's important to note that if you
32425 * have any errors in your resolve functions (javascript errors, non-existent services, etc)
32426 * they will not throw traditionally. You must listen for this $stateChangeError event to
32427 * catch **ALL** errors.
32429 * @param {Object} event Event object.
32430 * @param {State} toState The state being transitioned to.
32431 * @param {Object} toParams The params supplied to the `toState`.
32432 * @param {State} fromState The current state, pre-transition.
32433 * @param {Object} fromParams The params supplied to the `fromState`.
32434 * @param {Error} error The resolve error object.
32436 evt = $rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error);
32438 if (!evt.defaultPrevented) {
32439 $urlRouter.update();
32442 return $q.reject(error);
32450 * @name ui.router.state.$state#is
32451 * @methodOf ui.router.state.$state
32454 * Similar to {@link ui.router.state.$state#methods_includes $state.includes},
32455 * but only checks for the full state name. If params is supplied then it will be
32456 * tested for strict equality against the current active params object, so all params
32457 * must match with none missing and no extras.
32461 * $state.$current.name = 'contacts.details.item';
32464 * $state.is('contact.details.item'); // returns true
32465 * $state.is(contactDetailItemStateObject); // returns true
32467 * // relative name (. and ^), typically from a template
32468 * // E.g. from the 'contacts.details' template
32469 * <div ng-class="{highlighted: $state.is('.item')}">Item</div>
32472 * @param {string|object} stateOrName The state name (absolute or relative) or state object you'd like to check.
32473 * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like
32474 * to test against the current active state.
32475 * @param {object=} options An options object. The options are:
32477 * - **`relative`** - {string|object} - If `stateOrName` is a relative state name and `options.relative` is set, .is will
32478 * test relative to `options.relative` state (or name).
32480 * @returns {boolean} Returns true if it is the state.
32482 $state.is = function is(stateOrName, params, options) {
32483 options = extend({ relative: $state.$current }, options || {});
32484 var state = findState(stateOrName, options.relative);
32486 if (!isDefined(state)) { return undefined; }
32487 if ($state.$current !== state) { return false; }
32488 return params ? equalForKeys(state.params.$$values(params), $stateParams) : true;
32493 * @name ui.router.state.$state#includes
32494 * @methodOf ui.router.state.$state
32497 * A method to determine if the current active state is equal to or is the child of the
32498 * state stateName. If any params are passed then they will be tested for a match as well.
32499 * Not all the parameters need to be passed, just the ones you'd like to test for equality.
32502 * Partial and relative names
32504 * $state.$current.name = 'contacts.details.item';
32506 * // Using partial names
32507 * $state.includes("contacts"); // returns true
32508 * $state.includes("contacts.details"); // returns true
32509 * $state.includes("contacts.details.item"); // returns true
32510 * $state.includes("contacts.list"); // returns false
32511 * $state.includes("about"); // returns false
32513 * // Using relative names (. and ^), typically from a template
32514 * // E.g. from the 'contacts.details' template
32515 * <div ng-class="{highlighted: $state.includes('.item')}">Item</div>
32518 * Basic globbing patterns
32520 * $state.$current.name = 'contacts.details.item.url';
32522 * $state.includes("*.details.*.*"); // returns true
32523 * $state.includes("*.details.**"); // returns true
32524 * $state.includes("**.item.**"); // returns true
32525 * $state.includes("*.details.item.url"); // returns true
32526 * $state.includes("*.details.*.url"); // returns true
32527 * $state.includes("*.details.*"); // returns false
32528 * $state.includes("item.**"); // returns false
32531 * @param {string} stateOrName A partial name, relative name, or glob pattern
32532 * to be searched for within the current state name.
32533 * @param {object=} params A param object, e.g. `{sectionId: section.id}`,
32534 * that you'd like to test against the current active state.
32535 * @param {object=} options An options object. The options are:
32537 * - **`relative`** - {string|object=} - If `stateOrName` is a relative state reference and `options.relative` is set,
32538 * .includes will test relative to `options.relative` state (or name).
32540 * @returns {boolean} Returns true if it does include the state
32542 $state.includes = function includes(stateOrName, params, options) {
32543 options = extend({ relative: $state.$current }, options || {});
32544 if (isString(stateOrName) && isGlob(stateOrName)) {
32545 if (!doesStateMatchGlob(stateOrName)) {
32548 stateOrName = $state.$current.name;
32551 var state = findState(stateOrName, options.relative);
32552 if (!isDefined(state)) { return undefined; }
32553 if (!isDefined($state.$current.includes[state.name])) { return false; }
32554 return params ? equalForKeys(state.params.$$values(params), $stateParams, objectKeys(params)) : true;
32560 * @name ui.router.state.$state#href
32561 * @methodOf ui.router.state.$state
32564 * A url generation method that returns the compiled url for the given state populated with the given params.
32568 * expect($state.href("about.person", { person: "bob" })).toEqual("/about/bob");
32571 * @param {string|object} stateOrName The state name or state object you'd like to generate a url from.
32572 * @param {object=} params An object of parameter values to fill the state's required parameters.
32573 * @param {object=} options Options object. The options are:
32575 * - **`lossy`** - {boolean=true} - If true, and if there is no url associated with the state provided in the
32576 * first parameter, then the constructed href url will be built from the first navigable ancestor (aka
32577 * ancestor with a valid url).
32578 * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
32579 * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
32580 * defines which state to be relative from.
32581 * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
32583 * @returns {string} compiled state url
32585 $state.href = function href(stateOrName, params, options) {
32590 relative: $state.$current
32593 var state = findState(stateOrName, options.relative);
32595 if (!isDefined(state)) return null;
32596 if (options.inherit) params = inheritParams($stateParams, params || {}, $state.$current, state);
32598 var nav = (state && options.lossy) ? state.navigable : state;
32600 if (!nav || nav.url === undefined || nav.url === null) {
32603 return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys().concat('#'), params || {}), {
32604 absolute: options.absolute
32610 * @name ui.router.state.$state#get
32611 * @methodOf ui.router.state.$state
32614 * Returns the state configuration object for any specific state or all states.
32616 * @param {string|object=} stateOrName (absolute or relative) If provided, will only get the config for
32617 * the requested state. If not provided, returns an array of ALL state configs.
32618 * @param {string|object=} context When stateOrName is a relative state reference, the state will be retrieved relative to context.
32619 * @returns {Object|Array} State configuration object or array of all objects.
32621 $state.get = function (stateOrName, context) {
32622 if (arguments.length === 0) return map(objectKeys(states), function(name) { return states[name].self; });
32623 var state = findState(stateOrName, context || $state.$current);
32624 return (state && state.self) ? state.self : null;
32627 function resolveState(state, params, paramsAreFiltered, inherited, dst, options) {
32628 // Make a restricted $stateParams with only the parameters that apply to this state if
32629 // necessary. In addition to being available to the controller and onEnter/onExit callbacks,
32630 // we also need $stateParams to be available for any $injector calls we make during the
32631 // dependency resolution process.
32632 var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params);
32633 var locals = { $stateParams: $stateParams };
32635 // Resolve 'global' dependencies for the state, i.e. those not specific to a view.
32636 // We're also including $stateParams in this; that way the parameters are restricted
32637 // to the set that should be visible to the state, and are independent of when we update
32638 // the global $state and $stateParams values.
32639 dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state);
32640 var promises = [dst.resolve.then(function (globals) {
32641 dst.globals = globals;
32643 if (inherited) promises.push(inherited);
32645 function resolveViews() {
32646 var viewsPromises = [];
32648 // Resolve template and dependencies for all views.
32649 forEach(state.views, function (view, name) {
32650 var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {});
32651 injectables.$template = [ function () {
32652 return $view.load(name, { view: view, locals: dst.globals, params: $stateParams, notify: options.notify }) || '';
32655 viewsPromises.push($resolve.resolve(injectables, dst.globals, dst.resolve, state).then(function (result) {
32656 // References to the controller (only instantiated at link time)
32657 if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) {
32658 var injectLocals = angular.extend({}, injectables, dst.globals);
32659 result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals);
32661 result.$$controller = view.controller;
32663 // Provide access to the state itself for internal use
32664 result.$$state = state;
32665 result.$$controllerAs = view.controllerAs;
32666 dst[name] = result;
32670 return $q.all(viewsPromises).then(function(){
32671 return dst.globals;
32675 // Wait for all the promises and then return the activation object
32676 return $q.all(promises).then(resolveViews).then(function (values) {
32684 function shouldSkipReload(to, toParams, from, fromParams, locals, options) {
32685 // Return true if there are no differences in non-search (path/object) params, false if there are differences
32686 function nonSearchParamsEqual(fromAndToState, fromParams, toParams) {
32687 // Identify whether all the parameters that differ between `fromParams` and `toParams` were search params.
32688 function notSearchParam(key) {
32689 return fromAndToState.params[key].location != "search";
32691 var nonQueryParamKeys = fromAndToState.params.$$keys().filter(notSearchParam);
32692 var nonQueryParams = pick.apply({}, [fromAndToState.params].concat(nonQueryParamKeys));
32693 var nonQueryParamSet = new $$UMFP.ParamSet(nonQueryParams);
32694 return nonQueryParamSet.$$equals(fromParams, toParams);
32697 // If reload was not explicitly requested
32698 // and we're transitioning to the same state we're already in
32699 // and the locals didn't change
32700 // or they changed in a way that doesn't merit reloading
32701 // (reloadOnParams:false, or reloadOnSearch.false and only search params changed)
32702 // Then return true.
32703 if (!options.reload && to === from &&
32704 (locals === from.locals || (to.self.reloadOnSearch === false && nonSearchParamsEqual(from, fromParams, toParams)))) {
32710 angular.module('ui.router.state')
32711 .value('$stateParams', {})
32712 .provider('$state', $StateProvider);
32715 $ViewProvider.$inject = [];
32716 function $ViewProvider() {
32721 * @name ui.router.state.$view
32723 * @requires ui.router.util.$templateFactory
32724 * @requires $rootScope
32729 $get.$inject = ['$rootScope', '$templateFactory'];
32730 function $get( $rootScope, $templateFactory) {
32732 // $view.load('full.viewName', { template: ..., controller: ..., resolve: ..., async: false, params: ... })
32735 * @name ui.router.state.$view#load
32736 * @methodOf ui.router.state.$view
32740 * @param {string} name name
32741 * @param {object} options option object.
32743 load: function load(name, options) {
32744 var result, defaults = {
32745 template: null, controller: null, view: null, locals: null, notify: true, async: true, params: {}
32747 options = extend(defaults, options);
32749 if (options.view) {
32750 result = $templateFactory.fromConfig(options.view, options.params, options.locals);
32752 if (result && options.notify) {
32755 * @name ui.router.state.$state#$viewContentLoading
32756 * @eventOf ui.router.state.$view
32757 * @eventType broadcast on root scope
32760 * Fired once the view **begins loading**, *before* the DOM is rendered.
32762 * @param {Object} event Event object.
32763 * @param {Object} viewConfig The view config properties (template, controller, etc).
32768 * $scope.$on('$viewContentLoading',
32769 * function(event, viewConfig){
32770 * // Access to all the view config properties.
32771 * // and one special property 'targetView'
32772 * // viewConfig.targetView
32776 $rootScope.$broadcast('$viewContentLoading', options);
32784 angular.module('ui.router.state').provider('$view', $ViewProvider);
32788 * @name ui.router.state.$uiViewScrollProvider
32791 * Provider that returns the {@link ui.router.state.$uiViewScroll} service function.
32793 function $ViewScrollProvider() {
32795 var useAnchorScroll = false;
32799 * @name ui.router.state.$uiViewScrollProvider#useAnchorScroll
32800 * @methodOf ui.router.state.$uiViewScrollProvider
32803 * Reverts back to using the core [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) service for
32804 * scrolling based on the url anchor.
32806 this.useAnchorScroll = function () {
32807 useAnchorScroll = true;
32812 * @name ui.router.state.$uiViewScroll
32814 * @requires $anchorScroll
32815 * @requires $timeout
32818 * When called with a jqLite element, it scrolls the element into view (after a
32819 * `$timeout` so the DOM has time to refresh).
32821 * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor,
32822 * this can be enabled by calling {@link ui.router.state.$uiViewScrollProvider#methods_useAnchorScroll `$uiViewScrollProvider.useAnchorScroll()`}.
32824 this.$get = ['$anchorScroll', '$timeout', function ($anchorScroll, $timeout) {
32825 if (useAnchorScroll) {
32826 return $anchorScroll;
32829 return function ($element) {
32830 return $timeout(function () {
32831 $element[0].scrollIntoView();
32837 angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider);
32841 * @name ui.router.state.directive:ui-view
32843 * @requires ui.router.state.$state
32844 * @requires $compile
32845 * @requires $controller
32846 * @requires $injector
32847 * @requires ui.router.state.$uiViewScroll
32848 * @requires $document
32853 * The ui-view directive tells $state where to place your templates.
32855 * @param {string=} name A view name. The name should be unique amongst the other views in the
32856 * same state. You can have views of the same name that live in different states.
32858 * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window
32859 * when a view is populated. By default, $anchorScroll is overridden by ui-router's custom scroll
32860 * service, {@link ui.router.state.$uiViewScroll}. This custom service let's you
32861 * scroll ui-view elements into view when they are populated during a state activation.
32863 * *Note: To revert back to old [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll)
32864 * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.*
32866 * @param {string=} onload Expression to evaluate whenever the view updates.
32869 * A view can be unnamed or named.
32872 * <div ui-view></div>
32875 * <div ui-view="viewName"></div>
32878 * You can only have one unnamed view within any template (or root html). If you are only using a
32879 * single view and it is unnamed then you can populate it like so:
32881 * <div ui-view></div>
32882 * $stateProvider.state("home", {
32883 * template: "<h1>HELLO!</h1>"
32887 * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#views `views`}
32888 * config property, by name, in this case an empty name:
32890 * $stateProvider.state("home", {
32893 * template: "<h1>HELLO!</h1>"
32899 * But typically you'll only use the views property if you name your view or have more than one view
32900 * in the same template. There's not really a compelling reason to name a view if its the only one,
32901 * but you could if you wanted, like so:
32903 * <div ui-view="main"></div>
32906 * $stateProvider.state("home", {
32909 * template: "<h1>HELLO!</h1>"
32915 * Really though, you'll use views to set up multiple views:
32917 * <div ui-view></div>
32918 * <div ui-view="chart"></div>
32919 * <div ui-view="data"></div>
32923 * $stateProvider.state("home", {
32926 * template: "<h1>HELLO!</h1>"
32929 * template: "<chart_thing/>"
32932 * template: "<data_thing/>"
32938 * Examples for `autoscroll`:
32941 * <!-- If autoscroll present with no expression,
32942 * then scroll ui-view into view -->
32943 * <ui-view autoscroll/>
32945 * <!-- If autoscroll present with valid expression,
32946 * then scroll ui-view into view if expression evaluates to true -->
32947 * <ui-view autoscroll='true'/>
32948 * <ui-view autoscroll='false'/>
32949 * <ui-view autoscroll='scopeVariable'/>
32952 $ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll', '$interpolate'];
32953 function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate) {
32955 function getService() {
32956 return ($injector.has) ? function(service) {
32957 return $injector.has(service) ? $injector.get(service) : null;
32958 } : function(service) {
32960 return $injector.get(service);
32967 var service = getService(),
32968 $animator = service('$animator'),
32969 $animate = service('$animate');
32971 // Returns a set of DOM manipulation functions based on which Angular version
32973 function getRenderer(attrs, scope) {
32974 var statics = function() {
32976 enter: function (element, target, cb) { target.after(element); cb(); },
32977 leave: function (element, cb) { element.remove(); cb(); }
32983 enter: function(element, target, cb) {
32984 var promise = $animate.enter(element, null, target, cb);
32985 if (promise && promise.then) promise.then(cb);
32987 leave: function(element, cb) {
32988 var promise = $animate.leave(element, cb);
32989 if (promise && promise.then) promise.then(cb);
32995 var animate = $animator && $animator(scope, attrs);
32998 enter: function(element, target, cb) {animate.enter(element, null, target); cb(); },
32999 leave: function(element, cb) { animate.leave(element); cb(); }
33010 transclude: 'element',
33011 compile: function (tElement, tAttrs, $transclude) {
33012 return function (scope, $element, attrs) {
33013 var previousEl, currentEl, currentScope, latestLocals,
33014 onloadExp = attrs.onload || '',
33015 autoScrollExp = attrs.autoscroll,
33016 renderer = getRenderer(attrs, scope);
33018 scope.$on('$stateChangeSuccess', function() {
33021 scope.$on('$viewContentLoading', function() {
33027 function cleanupLastView() {
33029 previousEl.remove();
33033 if (currentScope) {
33034 currentScope.$destroy();
33035 currentScope = null;
33039 renderer.leave(currentEl, function() {
33043 previousEl = currentEl;
33048 function updateView(firstTime) {
33050 name = getUiViewName(scope, attrs, $element, $interpolate),
33051 previousLocals = name && $state.$current && $state.$current.locals[name];
33053 if (!firstTime && previousLocals === latestLocals) return; // nothing to do
33054 newScope = scope.$new();
33055 latestLocals = $state.$current.locals[name];
33057 var clone = $transclude(newScope, function(clone) {
33058 renderer.enter(clone, $element, function onUiViewEnter() {
33060 currentScope.$emit('$viewContentAnimationEnded');
33063 if (angular.isDefined(autoScrollExp) && !autoScrollExp || scope.$eval(autoScrollExp)) {
33064 $uiViewScroll(clone);
33071 currentScope = newScope;
33074 * @name ui.router.state.directive:ui-view#$viewContentLoaded
33075 * @eventOf ui.router.state.directive:ui-view
33076 * @eventType emits on ui-view directive scope
33078 * Fired once the view is **loaded**, *after* the DOM is rendered.
33080 * @param {Object} event Event object.
33082 currentScope.$emit('$viewContentLoaded');
33083 currentScope.$eval(onloadExp);
33092 $ViewDirectiveFill.$inject = ['$compile', '$controller', '$state', '$interpolate'];
33093 function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate) {
33097 compile: function (tElement) {
33098 var initial = tElement.html();
33099 return function (scope, $element, attrs) {
33100 var current = $state.$current,
33101 name = getUiViewName(scope, attrs, $element, $interpolate),
33102 locals = current && current.locals[name];
33108 $element.data('$uiView', { name: name, state: locals.$$state });
33109 $element.html(locals.$template ? locals.$template : initial);
33111 var link = $compile($element.contents());
33113 if (locals.$$controller) {
33114 locals.$scope = scope;
33115 locals.$element = $element;
33116 var controller = $controller(locals.$$controller, locals);
33117 if (locals.$$controllerAs) {
33118 scope[locals.$$controllerAs] = controller;
33120 $element.data('$ngControllerController', controller);
33121 $element.children().data('$ngControllerController', controller);
33131 * Shared ui-view code for both directives:
33132 * Given scope, element, and its attributes, return the view's name
33134 function getUiViewName(scope, attrs, element, $interpolate) {
33135 var name = $interpolate(attrs.uiView || attrs.name || '')(scope);
33136 var inherited = element.inheritedData('$uiView');
33137 return name.indexOf('@') >= 0 ? name : (name + '@' + (inherited ? inherited.state.name : ''));
33140 angular.module('ui.router.state').directive('uiView', $ViewDirective);
33141 angular.module('ui.router.state').directive('uiView', $ViewDirectiveFill);
33143 function parseStateRef(ref, current) {
33144 var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed;
33145 if (preparsed) ref = current + '(' + preparsed[1] + ')';
33146 parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
33147 if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'");
33148 return { state: parsed[1], paramExpr: parsed[3] || null };
33151 function stateContext(el) {
33152 var stateData = el.parent().inheritedData('$uiView');
33154 if (stateData && stateData.state && stateData.state.name) {
33155 return stateData.state;
33161 * @name ui.router.state.directive:ui-sref
33163 * @requires ui.router.state.$state
33164 * @requires $timeout
33169 * A directive that binds a link (`<a>` tag) to a state. If the state has an associated
33170 * URL, the directive will automatically generate & update the `href` attribute via
33171 * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking
33172 * the link will trigger a state transition with optional parameters.
33174 * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
33175 * handled natively by the browser.
33177 * You can also use relative state paths within ui-sref, just like the relative
33178 * paths passed to `$state.go()`. You just need to be aware that the path is relative
33179 * to the state that the link lives in, in other words the state that loaded the
33180 * template containing the link.
33182 * You can specify options to pass to {@link ui.router.state.$state#go $state.go()}
33183 * using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`,
33187 * Here's an example of how you'd use ui-sref and how it would compile. If you have the
33188 * following template:
33190 * <a ui-sref="home">Home</a> | <a ui-sref="about">About</a> | <a ui-sref="{page: 2}">Next page</a>
33193 * <li ng-repeat="contact in contacts">
33194 * <a ui-sref="contacts.detail({ id: contact.id })">{{ contact.name }}</a>
33199 * Then the compiled html would be (assuming Html5Mode is off and current state is contacts):
33201 * <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>
33204 * <li ng-repeat="contact in contacts">
33205 * <a href="#/contacts/1" ui-sref="contacts.detail({ id: contact.id })">Joe</a>
33207 * <li ng-repeat="contact in contacts">
33208 * <a href="#/contacts/2" ui-sref="contacts.detail({ id: contact.id })">Alice</a>
33210 * <li ng-repeat="contact in contacts">
33211 * <a href="#/contacts/3" ui-sref="contacts.detail({ id: contact.id })">Bob</a>
33215 * <a ui-sref="home" ui-sref-opts="{reload: true}">Home</a>
33218 * @param {string} ui-sref 'stateName' can be any valid absolute or relative state
33219 * @param {Object} ui-sref-opts options to pass to {@link ui.router.state.$state#go $state.go()}
33221 $StateRefDirective.$inject = ['$state', '$timeout'];
33222 function $StateRefDirective($state, $timeout) {
33223 var allowedOptions = ['location', 'inherit', 'reload', 'absolute'];
33227 require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
33228 link: function(scope, element, attrs, uiSrefActive) {
33229 var ref = parseStateRef(attrs.uiSref, $state.current.name);
33230 var params = null, url = null, base = stateContext(element) || $state.$current;
33231 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
33232 var hrefKind = Object.prototype.toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
33233 'xlink:href' : 'href';
33234 var newHref = null, isAnchor = element.prop("tagName").toUpperCase() === "A";
33235 var isForm = element[0].nodeName === "FORM";
33236 var attr = isForm ? "action" : hrefKind, nav = true;
33238 var options = { relative: base, inherit: true };
33239 var optionsOverride = scope.$eval(attrs.uiSrefOpts) || {};
33241 angular.forEach(allowedOptions, function(option) {
33242 if (option in optionsOverride) {
33243 options[option] = optionsOverride[option];
33247 var update = function(newVal) {
33248 if (newVal) params = angular.copy(newVal);
33251 newHref = $state.href(ref.state, params, options);
33253 var activeDirective = uiSrefActive[1] || uiSrefActive[0];
33254 if (activeDirective) {
33255 activeDirective.$$addStateInfo(ref.state, params);
33257 if (newHref === null) {
33261 attrs.$set(attr, newHref);
33264 if (ref.paramExpr) {
33265 scope.$watch(ref.paramExpr, function(newVal, oldVal) {
33266 if (newVal !== params) update(newVal);
33268 params = angular.copy(scope.$eval(ref.paramExpr));
33272 if (isForm) return;
33274 element.bind("click", function(e) {
33275 var button = e.which || e.button;
33276 if ( !(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || element.attr('target')) ) {
33277 // HACK: This is to allow ng-clicks to be processed before the transition is initiated:
33278 var transition = $timeout(function() {
33279 $state.go(ref.state, params, options);
33281 e.preventDefault();
33283 // if the state has no URL, ignore one preventDefault from the <a> directive.
33284 var ignorePreventDefaultCount = isAnchor && !newHref ? 1: 0;
33285 e.preventDefault = function() {
33286 if (ignorePreventDefaultCount-- <= 0)
33287 $timeout.cancel(transition);
33297 * @name ui.router.state.directive:ui-sref-active
33299 * @requires ui.router.state.$state
33300 * @requires ui.router.state.$stateParams
33301 * @requires $interpolate
33306 * A directive working alongside ui-sref to add classes to an element when the
33307 * related ui-sref directive's state is active, and removing them when it is inactive.
33308 * The primary use-case is to simplify the special appearance of navigation menus
33309 * relying on `ui-sref`, by having the "active" state's menu button appear different,
33310 * distinguishing it from the inactive menu items.
33312 * ui-sref-active can live on the same element as ui-sref or on a parent element. The first
33313 * ui-sref-active found at the same level or above the ui-sref will be used.
33315 * Will activate when the ui-sref's target state or any child state is active. If you
33316 * need to activate only when the ui-sref target state is active and *not* any of
33317 * it's children, then you will use
33318 * {@link ui.router.state.directive:ui-sref-active-eq ui-sref-active-eq}
33321 * Given the following template:
33324 * <li ui-sref-active="active" class="item">
33325 * <a href ui-sref="app.user({user: 'bilbobaggins'})">@bilbobaggins</a>
33331 * When the app state is "app.user" (or any children states), and contains the state parameter "user" with value "bilbobaggins",
33332 * the resulting HTML will appear as (note the 'active' class):
33335 * <li ui-sref-active="active" class="item active">
33336 * <a ui-sref="app.user({user: 'bilbobaggins'})" href="/users/bilbobaggins">@bilbobaggins</a>
33341 * The class name is interpolated **once** during the directives link time (any further changes to the
33342 * interpolated value are ignored).
33344 * Multiple classes may be specified in a space-separated format:
33347 * <li ui-sref-active='class1 class2 class3'>
33348 * <a ui-sref="app.user">link</a>
33356 * @name ui.router.state.directive:ui-sref-active-eq
33358 * @requires ui.router.state.$state
33359 * @requires ui.router.state.$stateParams
33360 * @requires $interpolate
33365 * The same as {@link ui.router.state.directive:ui-sref-active ui-sref-active} but will only activate
33366 * when the exact target state used in the `ui-sref` is active; no child states.
33369 $StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
33370 function $StateRefActiveDirective($state, $stateParams, $interpolate) {
33373 controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
33374 var states = [], activeClass;
33376 // There probably isn't much point in $observing this
33377 // uiSrefActive and uiSrefActiveEq share the same directive object with some
33378 // slight difference in logic routing
33379 activeClass = $interpolate($attrs.uiSrefActiveEq || $attrs.uiSrefActive || '', false)($scope);
33381 // Allow uiSref to communicate with uiSrefActive[Equals]
33382 this.$$addStateInfo = function (newState, newParams) {
33383 var state = $state.get(newState, stateContext($element));
33386 state: state || { name: newState },
33393 $scope.$on('$stateChangeSuccess', update);
33395 // Update route state
33396 function update() {
33398 $element.addClass(activeClass);
33400 $element.removeClass(activeClass);
33404 function anyMatch() {
33405 for (var i = 0; i < states.length; i++) {
33406 if (isMatch(states[i].state, states[i].params)) {
33413 function isMatch(state, params) {
33414 if (typeof $attrs.uiSrefActiveEq !== 'undefined') {
33415 return $state.is(state.name, params);
33417 return $state.includes(state.name, params);
33424 angular.module('ui.router.state')
33425 .directive('uiSref', $StateRefDirective)
33426 .directive('uiSrefActive', $StateRefActiveDirective)
33427 .directive('uiSrefActiveEq', $StateRefActiveDirective);
33431 * @name ui.router.state.filter:isState
33433 * @requires ui.router.state.$state
33436 * Translates to {@link ui.router.state.$state#methods_is $state.is("stateName")}.
33438 $IsStateFilter.$inject = ['$state'];
33439 function $IsStateFilter($state) {
33440 var isFilter = function (state) {
33441 return $state.is(state);
33443 isFilter.$stateful = true;
33449 * @name ui.router.state.filter:includedByState
33451 * @requires ui.router.state.$state
33454 * Translates to {@link ui.router.state.$state#methods_includes $state.includes('fullOrPartialStateName')}.
33456 $IncludedByStateFilter.$inject = ['$state'];
33457 function $IncludedByStateFilter($state) {
33458 var includesFilter = function (state) {
33459 return $state.includes(state);
33461 includesFilter.$stateful = true;
33462 return includesFilter;
33465 angular.module('ui.router.state')
33466 .filter('isState', $IsStateFilter)
33467 .filter('includedByState', $IncludedByStateFilter);
33468 })(window, window.angular);
33472 /***/ function(module, exports, __webpack_require__) {
33474 __webpack_require__(5);
33475 module.exports = 'ngResource';
33480 /***/ function(module, exports) {
33483 * @license AngularJS v1.4.8
33484 * (c) 2010-2015 Google, Inc. http://angularjs.org
33487 (function(window, angular, undefined) {'use strict';
33489 var $resourceMinErr = angular.$$minErr('$resource');
33491 // Helper functions and regex to lookup a dotted path on an object
33492 // stopping at undefined/null. The path must be composed of ASCII
33493 // identifiers (just like $parse)
33494 var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/;
33496 function isValidDottedPath(path) {
33497 return (path != null && path !== '' && path !== 'hasOwnProperty' &&
33498 MEMBER_NAME_REGEX.test('.' + path));
33501 function lookupDottedPath(obj, path) {
33502 if (!isValidDottedPath(path)) {
33503 throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path);
33505 var keys = path.split('.');
33506 for (var i = 0, ii = keys.length; i < ii && angular.isDefined(obj); i++) {
33508 obj = (obj !== null) ? obj[key] : undefined;
33514 * Create a shallow copy of an object and clear other fields from the destination
33516 function shallowClearAndCopy(src, dst) {
33519 angular.forEach(dst, function(value, key) {
33523 for (var key in src) {
33524 if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
33525 dst[key] = src[key];
33539 * The `ngResource` module provides interaction support with RESTful services
33540 * via the $resource service.
33543 * <div doc-module-components="ngResource"></div>
33545 * See {@link ngResource.$resource `$resource`} for usage.
33554 * A factory which creates a resource object that lets you interact with
33555 * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
33557 * The returned resource object has action methods which provide high-level behaviors without
33558 * the need to interact with the low level {@link ng.$http $http} service.
33560 * Requires the {@link ngResource `ngResource`} module to be installed.
33562 * By default, trailing slashes will be stripped from the calculated URLs,
33563 * which can pose problems with server backends that do not expect that
33564 * behavior. This can be disabled by configuring the `$resourceProvider` like
33568 app.config(['$resourceProvider', function($resourceProvider) {
33569 // Don't strip trailing slashes from calculated URLs
33570 $resourceProvider.defaults.stripTrailingSlashes = false;
33574 * @param {string} url A parameterized URL template with parameters prefixed by `:` as in
33575 * `/user/:username`. If you are using a URL with a port number (e.g.
33576 * `http://example.com:8080/api`), it will be respected.
33578 * If you are using a url with a suffix, just add the suffix, like this:
33579 * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')`
33580 * or even `$resource('http://example.com/resource/:resource_id.:format')`
33581 * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be
33582 * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you
33583 * can escape it with `/\.`.
33585 * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
33586 * `actions` methods. If any of the parameter value is a function, it will be executed every time
33587 * when a param value needs to be obtained for a request (unless the param was overridden).
33589 * Each key value in the parameter object is first bound to url template if present and then any
33590 * excess keys are appended to the url search query after the `?`.
33592 * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
33593 * URL `/path/greet?salutation=Hello`.
33595 * If the parameter value is prefixed with `@` then the value for that parameter will be extracted
33596 * from the corresponding property on the `data` object (provided when calling an action method). For
33597 * example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of `someParam`
33598 * will be `data.someProp`.
33600 * @param {Object.<Object>=} actions Hash with declaration of custom actions that should extend
33601 * the default set of resource actions. The declaration should be created in the format of {@link
33602 * ng.$http#usage $http.config}:
33604 * {action1: {method:?, params:?, isArray:?, headers:?, ...},
33605 * action2: {method:?, params:?, isArray:?, headers:?, ...},
33610 * - **`action`** – {string} – The name of action. This name becomes the name of the method on
33611 * your resource object.
33612 * - **`method`** – {string} – Case insensitive HTTP method (e.g. `GET`, `POST`, `PUT`,
33613 * `DELETE`, `JSONP`, etc).
33614 * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of
33615 * the parameter value is a function, it will be executed every time when a param value needs to
33616 * be obtained for a request (unless the param was overridden).
33617 * - **`url`** – {string} – action specific `url` override. The url templating is supported just
33618 * like for the resource-level urls.
33619 * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array,
33620 * see `returns` section.
33621 * - **`transformRequest`** –
33622 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
33623 * transform function or an array of such functions. The transform function takes the http
33624 * request body and headers and returns its transformed (typically serialized) version.
33625 * By default, transformRequest will contain one function that checks if the request data is
33626 * an object and serializes to using `angular.toJson`. To prevent this behavior, set
33627 * `transformRequest` to an empty array: `transformRequest: []`
33628 * - **`transformResponse`** –
33629 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
33630 * transform function or an array of such functions. The transform function takes the http
33631 * response body and headers and returns its transformed (typically deserialized) version.
33632 * By default, transformResponse will contain one function that checks if the response looks like
33633 * a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior, set
33634 * `transformResponse` to an empty array: `transformResponse: []`
33635 * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
33636 * GET request, otherwise if a cache instance built with
33637 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
33639 * - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that
33640 * should abort the request when resolved.
33641 * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the
33643 * [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5)
33644 * for more information.
33645 * - **`responseType`** - `{string}` - see
33646 * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType).
33647 * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
33648 * `response` and `responseError`. Both `response` and `responseError` interceptors get called
33649 * with `http response` object. See {@link ng.$http $http interceptors}.
33651 * @param {Object} options Hash with custom settings that should extend the
33652 * default `$resourceProvider` behavior. The only supported option is
33656 * - **`stripTrailingSlashes`** – {boolean} – If true then the trailing
33657 * slashes from any calculated URL will be stripped. (Defaults to true.)
33659 * @returns {Object} A resource "class" object with methods for the default set of resource actions
33660 * optionally extended with custom `actions`. The default set contains these actions:
33662 * { 'get': {method:'GET'},
33663 * 'save': {method:'POST'},
33664 * 'query': {method:'GET', isArray:true},
33665 * 'remove': {method:'DELETE'},
33666 * 'delete': {method:'DELETE'} };
33669 * Calling these methods invoke an {@link ng.$http} with the specified http method,
33670 * destination and parameters. When the data is returned from the server then the object is an
33671 * instance of the resource class. The actions `save`, `remove` and `delete` are available on it
33672 * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
33673 * read, update, delete) on server-side data like this:
33675 * var User = $resource('/user/:userId', {userId:'@id'});
33676 * var user = User.get({userId:123}, function() {
33682 * It is important to realize that invoking a $resource object method immediately returns an
33683 * empty reference (object or array depending on `isArray`). Once the data is returned from the
33684 * server the existing reference is populated with the actual data. This is a useful trick since
33685 * usually the resource is assigned to a model which is then rendered by the view. Having an empty
33686 * object results in no rendering, once the data arrives from the server then the object is
33687 * populated with the data and the view automatically re-renders itself showing the new data. This
33688 * means that in most cases one never has to write a callback function for the action methods.
33690 * The action methods on the class object or instance object can be invoked with the following
33693 * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
33694 * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
33695 * - non-GET instance actions: `instance.$action([parameters], [success], [error])`
33698 * Success callback is called with (value, responseHeaders) arguments, where the value is
33699 * the populated resource instance or collection object. The error callback is called
33700 * with (httpResponse) argument.
33702 * Class actions return empty instance (with additional properties below).
33703 * Instance actions return promise of the action.
33705 * The Resource instances and collection have these additional properties:
33707 * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this
33708 * instance or collection.
33710 * On success, the promise is resolved with the same resource instance or collection object,
33711 * updated with data from server. This makes it easy to use in
33712 * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view
33713 * rendering until the resource(s) are loaded.
33715 * On failure, the promise is resolved with the {@link ng.$http http response} object, without
33716 * the `resource` property.
33718 * If an interceptor object was provided, the promise will instead be resolved with the value
33719 * returned by the interceptor.
33721 * - `$resolved`: `true` after first server interaction is completed (either with success or
33722 * rejection), `false` before that. Knowing if the Resource has been resolved is useful in
33727 * # Credit card resource
33730 // Define CreditCard class
33731 var CreditCard = $resource('/user/:userId/card/:cardId',
33732 {userId:123, cardId:'@id'}, {
33733 charge: {method:'POST', params:{charge:true}}
33736 // We can retrieve a collection from the server
33737 var cards = CreditCard.query(function() {
33738 // GET: /user/123/card
33739 // server returns: [ {id:456, number:'1234', name:'Smith'} ];
33741 var card = cards[0];
33742 // each item is an instance of CreditCard
33743 expect(card instanceof CreditCard).toEqual(true);
33744 card.name = "J. Smith";
33745 // non GET methods are mapped onto the instances
33747 // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
33748 // server returns: {id:456, number:'1234', name: 'J. Smith'};
33750 // our custom method is mapped as well.
33751 card.$charge({amount:9.99});
33752 // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
33755 // we can create an instance as well
33756 var newCard = new CreditCard({number:'0123'});
33757 newCard.name = "Mike Smith";
33759 // POST: /user/123/card {number:'0123', name:'Mike Smith'}
33760 // server returns: {id:789, number:'0123', name: 'Mike Smith'};
33761 expect(newCard.id).toEqual(789);
33764 * The object returned from this function execution is a resource "class" which has "static" method
33765 * for each action in the definition.
33767 * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and
33769 * When the data is returned from the server then the object is an instance of the resource type and
33770 * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
33771 * operations (create, read, update, delete) on server-side data.
33774 var User = $resource('/user/:userId', {userId:'@id'});
33775 User.get({userId:123}, function(user) {
33781 * It's worth noting that the success callback for `get`, `query` and other methods gets passed
33782 * in the response that came from the server as well as $http header getter function, so one
33783 * could rewrite the above example and get access to http headers as:
33786 var User = $resource('/user/:userId', {userId:'@id'});
33787 User.get({userId:123}, function(u, getResponseHeaders){
33789 u.$save(function(u, putResponseHeaders) {
33790 //u => saved user object
33791 //putResponseHeaders => $http header getter
33796 * You can also access the raw `$http` promise via the `$promise` property on the object returned
33799 var User = $resource('/user/:userId', {userId:'@id'});
33800 User.get({userId:123})
33801 .$promise.then(function(user) {
33802 $scope.user = user;
33806 * # Creating a custom 'PUT' request
33807 * In this example we create a custom method on our resource to make a PUT request
33809 * var app = angular.module('app', ['ngResource', 'ngRoute']);
33811 * // Some APIs expect a PUT request in the format URL/object/ID
33812 * // Here we are creating an 'update' method
33813 * app.factory('Notes', ['$resource', function($resource) {
33814 * return $resource('/notes/:id', null,
33816 * 'update': { method:'PUT' }
33820 * // In our controller we get the ID from the URL using ngRoute and $routeParams
33821 * // We pass in $routeParams and our Notes factory along with $scope
33822 * app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes',
33823 function($scope, $routeParams, Notes) {
33824 * // First get a note object from the factory
33825 * var note = Notes.get({ id:$routeParams.id });
33828 * // Now call update passing in the ID first then the object you are updating
33829 * Notes.update({ id:$id }, note);
33831 * // This will PUT /notes/ID with the note object in the request payload
33835 angular.module('ngResource', ['ng']).
33836 provider('$resource', function() {
33837 var PROTOCOL_AND_DOMAIN_REGEX = /^https?:\/\/[^\/]*/;
33838 var provider = this;
33841 // Strip slashes by default
33842 stripTrailingSlashes: true,
33844 // Default actions configuration
33846 'get': {method: 'GET'},
33847 'save': {method: 'POST'},
33848 'query': {method: 'GET', isArray: true},
33849 'remove': {method: 'DELETE'},
33850 'delete': {method: 'DELETE'}
33854 this.$get = ['$http', '$q', function($http, $q) {
33856 var noop = angular.noop,
33857 forEach = angular.forEach,
33858 extend = angular.extend,
33859 copy = angular.copy,
33860 isFunction = angular.isFunction;
33863 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
33864 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set
33865 * (pchar) allowed in path segments:
33867 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
33868 * pct-encoded = "%" HEXDIG HEXDIG
33869 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
33870 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
33871 * / "*" / "+" / "," / ";" / "="
33873 function encodeUriSegment(val) {
33874 return encodeUriQuery(val, true).
33875 replace(/%26/gi, '&').
33876 replace(/%3D/gi, '=').
33877 replace(/%2B/gi, '+');
33882 * This method is intended for encoding *key* or *value* parts of query component. We need a
33883 * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't
33884 * have to be encoded per http://tools.ietf.org/html/rfc3986:
33885 * query = *( pchar / "/" / "?" )
33886 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
33887 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
33888 * pct-encoded = "%" HEXDIG HEXDIG
33889 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
33890 * / "*" / "+" / "," / ";" / "="
33892 function encodeUriQuery(val, pctEncodeSpaces) {
33893 return encodeURIComponent(val).
33894 replace(/%40/gi, '@').
33895 replace(/%3A/gi, ':').
33896 replace(/%24/g, '$').
33897 replace(/%2C/gi, ',').
33898 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
33901 function Route(template, defaults) {
33902 this.template = template;
33903 this.defaults = extend({}, provider.defaults, defaults);
33904 this.urlParams = {};
33907 Route.prototype = {
33908 setUrlParams: function(config, params, actionUrl) {
33910 url = actionUrl || self.template,
33913 protocolAndDomain = '';
33915 var urlParams = self.urlParams = {};
33916 forEach(url.split(/\W/), function(param) {
33917 if (param === 'hasOwnProperty') {
33918 throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name.");
33920 if (!(new RegExp("^\\d+$").test(param)) && param &&
33921 (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) {
33922 urlParams[param] = true;
33925 url = url.replace(/\\:/g, ':');
33926 url = url.replace(PROTOCOL_AND_DOMAIN_REGEX, function(match) {
33927 protocolAndDomain = match;
33931 params = params || {};
33932 forEach(self.urlParams, function(_, urlParam) {
33933 val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
33934 if (angular.isDefined(val) && val !== null) {
33935 encodedVal = encodeUriSegment(val);
33936 url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), function(match, p1) {
33937 return encodedVal + p1;
33940 url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match,
33941 leadingSlashes, tail) {
33942 if (tail.charAt(0) == '/') {
33945 return leadingSlashes + tail;
33951 // strip trailing slashes and set the url (unless this behavior is specifically disabled)
33952 if (self.defaults.stripTrailingSlashes) {
33953 url = url.replace(/\/+$/, '') || '/';
33956 // then replace collapse `/.` if found in the last URL path segment before the query
33957 // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x`
33958 url = url.replace(/\/\.(?=\w+($|\?))/, '.');
33959 // replace escaped `/\.` with `/.`
33960 config.url = protocolAndDomain + url.replace(/\/\\\./, '/.');
33963 // set params - delegate param encoding to $http
33964 forEach(params, function(value, key) {
33965 if (!self.urlParams[key]) {
33966 config.params = config.params || {};
33967 config.params[key] = value;
33974 function resourceFactory(url, paramDefaults, actions, options) {
33975 var route = new Route(url, options);
33977 actions = extend({}, provider.defaults.actions, actions);
33979 function extractParams(data, actionParams) {
33981 actionParams = extend({}, paramDefaults, actionParams);
33982 forEach(actionParams, function(value, key) {
33983 if (isFunction(value)) { value = value(); }
33984 ids[key] = value && value.charAt && value.charAt(0) == '@' ?
33985 lookupDottedPath(data, value.substr(1)) : value;
33990 function defaultResponseInterceptor(response) {
33991 return response.resource;
33994 function Resource(value) {
33995 shallowClearAndCopy(value || {}, this);
33998 Resource.prototype.toJSON = function() {
33999 var data = extend({}, this);
34000 delete data.$promise;
34001 delete data.$resolved;
34005 forEach(actions, function(action, name) {
34006 var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method);
34008 Resource[name] = function(a1, a2, a3, a4) {
34009 var params = {}, data, success, error;
34011 /* jshint -W086 */ /* (purposefully fall through case statements) */
34012 switch (arguments.length) {
34019 if (isFunction(a2)) {
34020 if (isFunction(a1)) {
34036 if (isFunction(a1)) success = a1;
34037 else if (hasBody) data = a1;
34042 throw $resourceMinErr('badargs',
34043 "Expected up to 4 arguments [params, data, success, error], got {0} arguments",
34046 /* jshint +W086 */ /* (purposefully fall through case statements) */
34048 var isInstanceCall = this instanceof Resource;
34049 var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data));
34050 var httpConfig = {};
34051 var responseInterceptor = action.interceptor && action.interceptor.response ||
34052 defaultResponseInterceptor;
34053 var responseErrorInterceptor = action.interceptor && action.interceptor.responseError ||
34056 forEach(action, function(value, key) {
34059 httpConfig[key] = copy(value);
34063 case 'interceptor':
34066 httpConfig[key] = value;
34071 if (hasBody) httpConfig.data = data;
34072 route.setUrlParams(httpConfig,
34073 extend({}, extractParams(data, action.params || {}), params),
34076 var promise = $http(httpConfig).then(function(response) {
34077 var data = response.data,
34078 promise = value.$promise;
34081 // Need to convert action.isArray to boolean in case it is undefined
34083 if (angular.isArray(data) !== (!!action.isArray)) {
34084 throw $resourceMinErr('badcfg',
34085 'Error in resource configuration for action `{0}`. Expected response to ' +
34086 'contain an {1} but got an {2} (Request: {3} {4})', name, action.isArray ? 'array' : 'object',
34087 angular.isArray(data) ? 'array' : 'object', httpConfig.method, httpConfig.url);
34090 if (action.isArray) {
34092 forEach(data, function(item) {
34093 if (typeof item === "object") {
34094 value.push(new Resource(item));
34096 // Valid JSON values may be string literals, and these should not be converted
34097 // into objects. These items will not have access to the Resource prototype
34098 // methods, but unfortunately there
34103 shallowClearAndCopy(data, value);
34104 value.$promise = promise;
34108 value.$resolved = true;
34110 response.resource = value;
34113 }, function(response) {
34114 value.$resolved = true;
34116 (error || noop)(response);
34118 return $q.reject(response);
34121 promise = promise.then(
34122 function(response) {
34123 var value = responseInterceptor(response);
34124 (success || noop)(value, response.headers);
34127 responseErrorInterceptor);
34129 if (!isInstanceCall) {
34130 // we are creating instance / collection
34131 // - set the initial promise
34132 // - return the instance / collection
34133 value.$promise = promise;
34134 value.$resolved = false;
34144 Resource.prototype['$' + name] = function(params, success, error) {
34145 if (isFunction(params)) {
34146 error = success; success = params; params = {};
34148 var result = Resource[name].call(this, params, this, success, error);
34149 return result.$promise || result;
34153 Resource.bind = function(additionalParamDefaults) {
34154 return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
34160 return resourceFactory;
34165 })(window, window.angular);
34170 /***/ function(module, exports, __webpack_require__) {
34172 __webpack_require__(7);
34174 module.exports = 'ui.bootstrap';
34179 /***/ function(module, exports) {
34182 * angular-ui-bootstrap
34183 * http://angular-ui.github.io/bootstrap/
34185 * Version: 1.0.0 - 2016-01-08
34188 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"]);
34189 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"]);
34190 angular.module('ui.bootstrap.collapse', [])
34192 .directive('uibCollapse', ['$animate', '$injector', function($animate, $injector) {
34193 var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null;
34195 link: function(scope, element, attrs) {
34196 if (!scope.$eval(attrs.uibCollapse)) {
34197 element.addClass('in')
34198 .addClass('collapse')
34199 .css({height: 'auto'});
34202 function expand() {
34203 element.removeClass('collapse')
34204 .addClass('collapsing')
34205 .attr('aria-expanded', true)
34206 .attr('aria-hidden', false);
34209 $animateCss(element, {
34212 to: { height: element[0].scrollHeight + 'px' }
34213 }).start()['finally'](expandDone);
34215 $animate.addClass(element, 'in', {
34216 to: { height: element[0].scrollHeight + 'px' }
34217 }).then(expandDone);
34221 function expandDone() {
34222 element.removeClass('collapsing')
34223 .addClass('collapse')
34224 .css({height: 'auto'});
34227 function collapse() {
34228 if (!element.hasClass('collapse') && !element.hasClass('in')) {
34229 return collapseDone();
34233 // IMPORTANT: The height must be set before adding "collapsing" class.
34234 // Otherwise, the browser attempts to animate from height 0 (in
34235 // collapsing class) to the given height here.
34236 .css({height: element[0].scrollHeight + 'px'})
34237 // initially all panel collapse have the collapse class, this removal
34238 // prevents the animation from jumping to collapsed state
34239 .removeClass('collapse')
34240 .addClass('collapsing')
34241 .attr('aria-expanded', false)
34242 .attr('aria-hidden', true);
34245 $animateCss(element, {
34248 }).start()['finally'](collapseDone);
34250 $animate.removeClass(element, 'in', {
34252 }).then(collapseDone);
34256 function collapseDone() {
34257 element.css({height: '0'}); // Required so that collapse works when animation is disabled
34258 element.removeClass('collapsing')
34259 .addClass('collapse');
34262 scope.$watch(attrs.uibCollapse, function(shouldCollapse) {
34263 if (shouldCollapse) {
34273 angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
34275 .constant('uibAccordionConfig', {
34279 .controller('UibAccordionController', ['$scope', '$attrs', 'uibAccordionConfig', function($scope, $attrs, accordionConfig) {
34280 // This array keeps track of the accordion groups
34283 // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
34284 this.closeOthers = function(openGroup) {
34285 var closeOthers = angular.isDefined($attrs.closeOthers) ?
34286 $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
34288 angular.forEach(this.groups, function(group) {
34289 if (group !== openGroup) {
34290 group.isOpen = false;
34296 // This is called from the accordion-group directive to add itself to the accordion
34297 this.addGroup = function(groupScope) {
34299 this.groups.push(groupScope);
34301 groupScope.$on('$destroy', function(event) {
34302 that.removeGroup(groupScope);
34306 // This is called from the accordion-group directive when to remove itself
34307 this.removeGroup = function(group) {
34308 var index = this.groups.indexOf(group);
34309 if (index !== -1) {
34310 this.groups.splice(index, 1);
34315 // The accordion directive simply sets up the directive controller
34316 // and adds an accordion CSS class to itself element.
34317 .directive('uibAccordion', function() {
34319 controller: 'UibAccordionController',
34320 controllerAs: 'accordion',
34322 templateUrl: function(element, attrs) {
34323 return attrs.templateUrl || 'uib/template/accordion/accordion.html';
34328 // The accordion-group directive indicates a block of html that will expand and collapse in an accordion
34329 .directive('uibAccordionGroup', function() {
34331 require: '^uibAccordion', // We need this directive to be inside an accordion
34332 transclude: true, // It transcludes the contents of the directive into the template
34333 replace: true, // The element containing the directive will be replaced with the template
34334 templateUrl: function(element, attrs) {
34335 return attrs.templateUrl || 'uib/template/accordion/accordion-group.html';
34338 heading: '@', // Interpolate the heading attribute onto this scope
34342 controller: function() {
34343 this.setHeading = function(element) {
34344 this.heading = element;
34347 link: function(scope, element, attrs, accordionCtrl) {
34348 accordionCtrl.addGroup(scope);
34350 scope.openClass = attrs.openClass || 'panel-open';
34351 scope.panelClass = attrs.panelClass || 'panel-default';
34352 scope.$watch('isOpen', function(value) {
34353 element.toggleClass(scope.openClass, !!value);
34355 accordionCtrl.closeOthers(scope);
34359 scope.toggleOpen = function($event) {
34360 if (!scope.isDisabled) {
34361 if (!$event || $event.which === 32) {
34362 scope.isOpen = !scope.isOpen;
34370 // Use accordion-heading below an accordion-group to provide a heading containing HTML
34371 .directive('uibAccordionHeading', function() {
34373 transclude: true, // Grab the contents to be used as the heading
34374 template: '', // In effect remove this element!
34376 require: '^uibAccordionGroup',
34377 link: function(scope, element, attrs, accordionGroupCtrl, transclude) {
34378 // Pass the heading to the accordion-group controller
34379 // so that it can be transcluded into the right place in the template
34380 // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
34381 accordionGroupCtrl.setHeading(transclude(scope, angular.noop));
34386 // Use in the accordion-group template to indicate where you want the heading to be transcluded
34387 // You must provide the property on the accordion-group controller that will hold the transcluded element
34388 .directive('uibAccordionTransclude', function() {
34390 require: '^uibAccordionGroup',
34391 link: function(scope, element, attrs, controller) {
34392 scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) {
34394 element.find('span').html('');
34395 element.find('span').append(heading);
34402 angular.module('ui.bootstrap.alert', [])
34404 .controller('UibAlertController', ['$scope', '$attrs', '$interpolate', '$timeout', function($scope, $attrs, $interpolate, $timeout) {
34405 $scope.closeable = !!$attrs.close;
34407 var dismissOnTimeout = angular.isDefined($attrs.dismissOnTimeout) ?
34408 $interpolate($attrs.dismissOnTimeout)($scope.$parent) : null;
34410 if (dismissOnTimeout) {
34411 $timeout(function() {
34413 }, parseInt(dismissOnTimeout, 10));
34417 .directive('uibAlert', function() {
34419 controller: 'UibAlertController',
34420 controllerAs: 'alert',
34421 templateUrl: function(element, attrs) {
34422 return attrs.templateUrl || 'uib/template/alert/alert.html';
34433 angular.module('ui.bootstrap.buttons', [])
34435 .constant('uibButtonConfig', {
34436 activeClass: 'active',
34437 toggleEvent: 'click'
34440 .controller('UibButtonsController', ['uibButtonConfig', function(buttonConfig) {
34441 this.activeClass = buttonConfig.activeClass || 'active';
34442 this.toggleEvent = buttonConfig.toggleEvent || 'click';
34445 .directive('uibBtnRadio', ['$parse', function($parse) {
34447 require: ['uibBtnRadio', 'ngModel'],
34448 controller: 'UibButtonsController',
34449 controllerAs: 'buttons',
34450 link: function(scope, element, attrs, ctrls) {
34451 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
34452 var uncheckableExpr = $parse(attrs.uibUncheckable);
34454 element.find('input').css({display: 'none'});
34457 ngModelCtrl.$render = function() {
34458 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.uibBtnRadio)));
34462 element.on(buttonsCtrl.toggleEvent, function() {
34463 if (attrs.disabled) {
34467 var isActive = element.hasClass(buttonsCtrl.activeClass);
34469 if (!isActive || angular.isDefined(attrs.uncheckable)) {
34470 scope.$apply(function() {
34471 ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.uibBtnRadio));
34472 ngModelCtrl.$render();
34477 if (attrs.uibUncheckable) {
34478 scope.$watch(uncheckableExpr, function(uncheckable) {
34479 attrs.$set('uncheckable', uncheckable ? '' : null);
34486 .directive('uibBtnCheckbox', function() {
34488 require: ['uibBtnCheckbox', 'ngModel'],
34489 controller: 'UibButtonsController',
34490 controllerAs: 'button',
34491 link: function(scope, element, attrs, ctrls) {
34492 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
34494 element.find('input').css({display: 'none'});
34496 function getTrueValue() {
34497 return getCheckboxValue(attrs.btnCheckboxTrue, true);
34500 function getFalseValue() {
34501 return getCheckboxValue(attrs.btnCheckboxFalse, false);
34504 function getCheckboxValue(attribute, defaultValue) {
34505 return angular.isDefined(attribute) ? scope.$eval(attribute) : defaultValue;
34509 ngModelCtrl.$render = function() {
34510 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
34514 element.on(buttonsCtrl.toggleEvent, function() {
34515 if (attrs.disabled) {
34519 scope.$apply(function() {
34520 ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
34521 ngModelCtrl.$render();
34528 angular.module('ui.bootstrap.carousel', [])
34530 .controller('UibCarouselController', ['$scope', '$element', '$interval', '$timeout', '$animate', function($scope, $element, $interval, $timeout, $animate) {
34532 slides = self.slides = $scope.slides = [],
34533 SLIDE_DIRECTION = 'uib-slideDirection',
34535 currentInterval, isPlaying, bufferedTransitions = [];
34536 self.currentSlide = null;
34538 var destroyed = false;
34540 self.addSlide = function(slide, element) {
34541 slide.$element = element;
34542 slides.push(slide);
34543 //if this is the first slide or the slide is set to active, select it
34544 if (slides.length === 1 || slide.active) {
34545 if ($scope.$currentTransition) {
34546 $scope.$currentTransition = null;
34549 self.select(slides[slides.length - 1]);
34550 if (slides.length === 1) {
34554 slide.active = false;
34558 self.getCurrentIndex = function() {
34559 if (self.currentSlide && angular.isDefined(self.currentSlide.index)) {
34560 return +self.currentSlide.index;
34562 return currentIndex;
34565 self.next = $scope.next = function() {
34566 var newIndex = (self.getCurrentIndex() + 1) % slides.length;
34568 if (newIndex === 0 && $scope.noWrap()) {
34573 return self.select(getSlideByIndex(newIndex), 'next');
34576 self.prev = $scope.prev = function() {
34577 var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1;
34579 if ($scope.noWrap() && newIndex === slides.length - 1) {
34584 return self.select(getSlideByIndex(newIndex), 'prev');
34587 self.removeSlide = function(slide) {
34588 if (angular.isDefined(slide.index)) {
34589 slides.sort(function(a, b) {
34590 return +a.index > +b.index;
34594 var bufferedIndex = bufferedTransitions.indexOf(slide);
34595 if (bufferedIndex !== -1) {
34596 bufferedTransitions.splice(bufferedIndex, 1);
34598 //get the index of the slide inside the carousel
34599 var index = slides.indexOf(slide);
34600 slides.splice(index, 1);
34601 $timeout(function() {
34602 if (slides.length > 0 && slide.active) {
34603 if (index >= slides.length) {
34604 self.select(slides[index - 1]);
34606 self.select(slides[index]);
34608 } else if (currentIndex > index) {
34613 //clean the currentSlide when no more slide
34614 if (slides.length === 0) {
34615 self.currentSlide = null;
34616 clearBufferedTransitions();
34620 /* direction: "prev" or "next" */
34621 self.select = $scope.select = function(nextSlide, direction) {
34622 var nextIndex = $scope.indexOfSlide(nextSlide);
34623 //Decide direction if it's not given
34624 if (direction === undefined) {
34625 direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
34627 //Prevent this user-triggered transition from occurring if there is already one in progress
34628 if (nextSlide && nextSlide !== self.currentSlide && !$scope.$currentTransition) {
34629 goNext(nextSlide, nextIndex, direction);
34630 } else if (nextSlide && nextSlide !== self.currentSlide && $scope.$currentTransition) {
34631 bufferedTransitions.push(nextSlide);
34635 /* Allow outside people to call indexOf on slides array */
34636 $scope.indexOfSlide = function(slide) {
34637 return angular.isDefined(slide.index) ? +slide.index : slides.indexOf(slide);
34640 $scope.isActive = function(slide) {
34641 return self.currentSlide === slide;
34644 $scope.pause = function() {
34645 if (!$scope.noPause) {
34651 $scope.play = function() {
34658 $scope.$on('$destroy', function() {
34663 $scope.$watch('noTransition', function(noTransition) {
34664 $animate.enabled($element, !noTransition);
34667 $scope.$watch('interval', restartTimer);
34669 $scope.$watchCollection('slides', resetTransition);
34671 function clearBufferedTransitions() {
34672 while (bufferedTransitions.length) {
34673 bufferedTransitions.shift();
34677 function getSlideByIndex(index) {
34678 if (angular.isUndefined(slides[index].index)) {
34679 return slides[index];
34681 for (var i = 0, l = slides.length; i < l; ++i) {
34682 if (slides[i].index === index) {
34688 function goNext(slide, index, direction) {
34689 if (destroyed) { return; }
34691 angular.extend(slide, {direction: direction, active: true});
34692 angular.extend(self.currentSlide || {}, {direction: direction, active: false});
34693 if ($animate.enabled($element) && !$scope.$currentTransition &&
34694 slide.$element && self.slides.length > 1) {
34695 slide.$element.data(SLIDE_DIRECTION, slide.direction);
34696 if (self.currentSlide && self.currentSlide.$element) {
34697 self.currentSlide.$element.data(SLIDE_DIRECTION, slide.direction);
34700 $scope.$currentTransition = true;
34701 $animate.on('addClass', slide.$element, function(element, phase) {
34702 if (phase === 'close') {
34703 $scope.$currentTransition = null;
34704 $animate.off('addClass', element);
34705 if (bufferedTransitions.length) {
34706 var nextSlide = bufferedTransitions.pop();
34707 var nextIndex = $scope.indexOfSlide(nextSlide);
34708 var nextDirection = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
34709 clearBufferedTransitions();
34711 goNext(nextSlide, nextIndex, nextDirection);
34717 self.currentSlide = slide;
34718 currentIndex = index;
34720 //every time you change slides, reset the timer
34724 function resetTimer() {
34725 if (currentInterval) {
34726 $interval.cancel(currentInterval);
34727 currentInterval = null;
34731 function resetTransition(slides) {
34732 if (!slides.length) {
34733 $scope.$currentTransition = null;
34734 clearBufferedTransitions();
34738 function restartTimer() {
34740 var interval = +$scope.interval;
34741 if (!isNaN(interval) && interval > 0) {
34742 currentInterval = $interval(timerFn, interval);
34746 function timerFn() {
34747 var interval = +$scope.interval;
34748 if (isPlaying && !isNaN(interval) && interval > 0 && slides.length) {
34756 .directive('uibCarousel', function() {
34760 controller: 'UibCarouselController',
34761 controllerAs: 'carousel',
34762 templateUrl: function(element, attrs) {
34763 return attrs.templateUrl || 'uib/template/carousel/carousel.html';
34774 .directive('uibSlide', function() {
34776 require: '^uibCarousel',
34779 templateUrl: function(element, attrs) {
34780 return attrs.templateUrl || 'uib/template/carousel/slide.html';
34787 link: function (scope, element, attrs, carouselCtrl) {
34788 carouselCtrl.addSlide(scope, element);
34789 //when the scope is destroyed then remove the slide from the current slides array
34790 scope.$on('$destroy', function() {
34791 carouselCtrl.removeSlide(scope);
34794 scope.$watch('active', function(active) {
34796 carouselCtrl.select(scope);
34803 .animation('.item', ['$animateCss',
34804 function($animateCss) {
34805 var SLIDE_DIRECTION = 'uib-slideDirection';
34807 function removeClass(element, className, callback) {
34808 element.removeClass(className);
34815 beforeAddClass: function(element, className, done) {
34816 if (className === 'active') {
34817 var stopped = false;
34818 var direction = element.data(SLIDE_DIRECTION);
34819 var directionClass = direction === 'next' ? 'left' : 'right';
34820 var removeClassFn = removeClass.bind(this, element,
34821 directionClass + ' ' + direction, done);
34822 element.addClass(direction);
34824 $animateCss(element, {addClass: directionClass})
34826 .done(removeClassFn);
34828 return function() {
34834 beforeRemoveClass: function (element, className, done) {
34835 if (className === 'active') {
34836 var stopped = false;
34837 var direction = element.data(SLIDE_DIRECTION);
34838 var directionClass = direction === 'next' ? 'left' : 'right';
34839 var removeClassFn = removeClass.bind(this, element, directionClass, done);
34841 $animateCss(element, {addClass: directionClass})
34843 .done(removeClassFn);
34845 return function() {
34854 angular.module('ui.bootstrap.dateparser', [])
34856 .service('uibDateParser', ['$log', '$locale', 'orderByFilter', function($log, $locale, orderByFilter) {
34857 // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js
34858 var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
34861 var formatCodeToRegex;
34863 this.init = function() {
34864 localeId = $locale.id;
34868 formatCodeToRegex = [
34872 apply: function(value) { this.year = +value; }
34877 apply: function(value) { this.year = +value + 2000; }
34882 apply: function(value) { this.year = +value; }
34886 regex: '0?[1-9]|1[0-2]',
34887 apply: function(value) { this.month = value - 1; }
34891 regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
34892 apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); }
34896 regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
34897 apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); }
34901 regex: '0[1-9]|1[0-2]',
34902 apply: function(value) { this.month = value - 1; }
34906 regex: '[1-9]|1[0-2]',
34907 apply: function(value) { this.month = value - 1; }
34911 regex: '[0-2]?[0-9]{1}|3[0-1]{1}',
34912 apply: function(value) { this.date = +value; }
34916 regex: '[0-2][0-9]{1}|3[0-1]{1}',
34917 apply: function(value) { this.date = +value; }
34921 regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
34922 apply: function(value) { this.date = +value; }
34926 regex: $locale.DATETIME_FORMATS.DAY.join('|')
34930 regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|')
34934 regex: '(?:0|1)[0-9]|2[0-3]',
34935 apply: function(value) { this.hours = +value; }
34939 regex: '0[0-9]|1[0-2]',
34940 apply: function(value) { this.hours = +value; }
34944 regex: '1?[0-9]|2[0-3]',
34945 apply: function(value) { this.hours = +value; }
34949 regex: '[0-9]|1[0-2]',
34950 apply: function(value) { this.hours = +value; }
34954 regex: '[0-5][0-9]',
34955 apply: function(value) { this.minutes = +value; }
34959 regex: '[0-9]|[1-5][0-9]',
34960 apply: function(value) { this.minutes = +value; }
34964 regex: '[0-9][0-9][0-9]',
34965 apply: function(value) { this.milliseconds = +value; }
34969 regex: '[0-5][0-9]',
34970 apply: function(value) { this.seconds = +value; }
34974 regex: '[0-9]|[1-5][0-9]',
34975 apply: function(value) { this.seconds = +value; }
34979 regex: $locale.DATETIME_FORMATS.AMPMS.join('|'),
34980 apply: function(value) {
34981 if (this.hours === 12) {
34985 if (value === 'PM') {
34992 regex: '[+-]\\d{4}',
34993 apply: function(value) {
34994 var matches = value.match(/([+-])(\d{2})(\d{2})/),
34996 hours = matches[2],
34997 minutes = matches[3];
34998 this.hours += toInt(sign + hours);
34999 this.minutes += toInt(sign + minutes);
35004 regex: '[0-4][0-9]|5[0-3]'
35008 regex: '[0-9]|[1-4][0-9]|5[0-3]'
35012 regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\s/g, '\\s')
35016 regex: $locale.DATETIME_FORMATS.ERAS.join('|')
35020 regex: $locale.DATETIME_FORMATS.ERAS.join('|')
35024 regex: $locale.DATETIME_FORMATS.ERAS.join('|')
35031 function createParser(format) {
35032 var map = [], regex = format.split('');
35034 // check for literal values
35035 var quoteIndex = format.indexOf('\'');
35036 if (quoteIndex > -1) {
35037 var inLiteral = false;
35038 format = format.split('');
35039 for (var i = quoteIndex; i < format.length; i++) {
35041 if (format[i] === '\'') {
35042 if (i + 1 < format.length && format[i+1] === '\'') { // escaped single quote
35045 } else { // end of literal
35052 if (format[i] === '\'') { // start of literal
35060 format = format.join('');
35063 angular.forEach(formatCodeToRegex, function(data) {
35064 var index = format.indexOf(data.key);
35067 format = format.split('');
35069 regex[index] = '(' + data.regex + ')';
35070 format[index] = '$'; // Custom symbol to define consumed part of format
35071 for (var i = index + 1, n = index + data.key.length; i < n; i++) {
35075 format = format.join('');
35080 matcher: data.regex
35086 regex: new RegExp('^' + regex.join('') + '$'),
35087 map: orderByFilter(map, 'index')
35091 this.parse = function(input, format, baseDate) {
35092 if (!angular.isString(input) || !format) {
35096 format = $locale.DATETIME_FORMATS[format] || format;
35097 format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&');
35099 if ($locale.id !== localeId) {
35103 if (!this.parsers[format]) {
35104 this.parsers[format] = createParser(format);
35107 var parser = this.parsers[format],
35108 regex = parser.regex,
35110 results = input.match(regex),
35112 if (results && results.length) {
35114 if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) {
35116 year: baseDate.getFullYear(),
35117 month: baseDate.getMonth(),
35118 date: baseDate.getDate(),
35119 hours: baseDate.getHours(),
35120 minutes: baseDate.getMinutes(),
35121 seconds: baseDate.getSeconds(),
35122 milliseconds: baseDate.getMilliseconds()
35126 $log.warn('dateparser:', 'baseDate is not a valid date');
35128 fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 };
35131 for (var i = 1, n = results.length; i < n; i++) {
35132 var mapper = map[i - 1];
35133 if (mapper.matcher === 'Z') {
35137 if (mapper.apply) {
35138 mapper.apply.call(fields, results[i]);
35142 var datesetter = tzOffset ? Date.prototype.setUTCFullYear :
35143 Date.prototype.setFullYear;
35144 var timesetter = tzOffset ? Date.prototype.setUTCHours :
35145 Date.prototype.setHours;
35147 if (isValid(fields.year, fields.month, fields.date)) {
35148 if (angular.isDate(baseDate) && !isNaN(baseDate.getTime()) && !tzOffset) {
35149 dt = new Date(baseDate);
35150 datesetter.call(dt, fields.year, fields.month, fields.date);
35151 timesetter.call(dt, fields.hours, fields.minutes,
35152 fields.seconds, fields.milliseconds);
35155 datesetter.call(dt, fields.year, fields.month, fields.date);
35156 timesetter.call(dt, fields.hours || 0, fields.minutes || 0,
35157 fields.seconds || 0, fields.milliseconds || 0);
35165 // Check if date is valid for specific month (and year for February).
35166 // Month: 0 = Jan, 1 = Feb, etc
35167 function isValid(year, month, date) {
35172 if (month === 1 && date > 28) {
35173 return date === 29 && (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0);
35176 if (month === 3 || month === 5 || month === 8 || month === 10) {
35183 function toInt(str) {
35184 return parseInt(str, 10);
35187 this.toTimezone = toTimezone;
35188 this.fromTimezone = fromTimezone;
35189 this.timezoneToOffset = timezoneToOffset;
35190 this.addDateMinutes = addDateMinutes;
35191 this.convertTimezoneToLocal = convertTimezoneToLocal;
35193 function toTimezone(date, timezone) {
35194 return date && timezone ? convertTimezoneToLocal(date, timezone) : date;
35197 function fromTimezone(date, timezone) {
35198 return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date;
35201 //https://github.com/angular/angular.js/blob/4daafd3dbe6a80d578f5a31df1bb99c77559543e/src/Angular.js#L1207
35202 function timezoneToOffset(timezone, fallback) {
35203 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
35204 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
35207 function addDateMinutes(date, minutes) {
35208 date = new Date(date.getTime());
35209 date.setMinutes(date.getMinutes() + minutes);
35213 function convertTimezoneToLocal(date, timezone, reverse) {
35214 reverse = reverse ? -1 : 1;
35215 var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
35216 return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
35220 // Avoiding use of ng-class as it creates a lot of watchers when a class is to be applied to
35221 // at most one element.
35222 angular.module('ui.bootstrap.isClass', [])
35223 .directive('uibIsClass', [
35225 function ($animate) {
35226 // 11111111 22222222
35227 var ON_REGEXP = /^\s*([\s\S]+?)\s+on\s+([\s\S]+?)\s*$/;
35228 // 11111111 22222222
35229 var IS_REGEXP = /^\s*([\s\S]+?)\s+for\s+([\s\S]+?)\s*$/;
35231 var dataPerTracked = {};
35235 compile: function (tElement, tAttrs) {
35236 var linkedScopes = [];
35237 var instances = [];
35238 var expToData = {};
35239 var lastActivated = null;
35240 var onExpMatches = tAttrs.uibIsClass.match(ON_REGEXP);
35241 var onExp = onExpMatches[2];
35242 var expsStr = onExpMatches[1];
35243 var exps = expsStr.split(',');
35247 function linkFn(scope, element, attrs) {
35248 linkedScopes.push(scope);
35254 exps.forEach(function (exp, k) {
35255 addForExp(exp, scope);
35258 scope.$on('$destroy', removeScope);
35261 function addForExp(exp, scope) {
35262 var matches = exp.match(IS_REGEXP);
35263 var clazz = scope.$eval(matches[1]);
35264 var compareWithExp = matches[2];
35265 var data = expToData[exp];
35267 var watchFn = function (compareWithVal) {
35268 var newActivated = null;
35269 instances.some(function (instance) {
35270 var thisVal = instance.scope.$eval(onExp);
35271 if (thisVal === compareWithVal) {
35272 newActivated = instance;
35276 if (data.lastActivated !== newActivated) {
35277 if (data.lastActivated) {
35278 $animate.removeClass(data.lastActivated.element, clazz);
35280 if (newActivated) {
35281 $animate.addClass(newActivated.element, clazz);
35283 data.lastActivated = newActivated;
35286 expToData[exp] = data = {
35287 lastActivated: null,
35290 compareWithExp: compareWithExp,
35291 watcher: scope.$watch(compareWithExp, watchFn)
35294 data.watchFn(scope.$eval(compareWithExp));
35297 function removeScope(e) {
35298 var removedScope = e.targetScope;
35299 var index = linkedScopes.indexOf(removedScope);
35300 linkedScopes.splice(index, 1);
35301 instances.splice(index, 1);
35302 if (linkedScopes.length) {
35303 var newWatchScope = linkedScopes[0];
35304 angular.forEach(expToData, function (data) {
35305 if (data.scope === removedScope) {
35306 data.watcher = newWatchScope.$watch(data.compareWithExp, data.watchFn);
35307 data.scope = newWatchScope;
35318 angular.module('ui.bootstrap.position', [])
35321 * A set of utility methods for working with the DOM.
35322 * It is meant to be used where we need to absolute-position elements in
35323 * relation to another element (this is the case for tooltips, popovers,
35324 * typeahead suggestions etc.).
35326 .factory('$uibPosition', ['$document', '$window', function($document, $window) {
35328 * Used by scrollbarWidth() function to cache scrollbar's width.
35329 * Do not access this variable directly, use scrollbarWidth() instead.
35331 var SCROLLBAR_WIDTH;
35332 var OVERFLOW_REGEX = {
35333 normal: /(auto|scroll)/,
35334 hidden: /(auto|scroll|hidden)/
35336 var PLACEMENT_REGEX = {
35337 auto: /\s?auto?\s?/i,
35338 primary: /^(top|bottom|left|right)$/,
35339 secondary: /^(top|bottom|left|right|center)$/,
35340 vertical: /^(top|bottom)$/
35346 * Provides a raw DOM element from a jQuery/jQLite element.
35348 * @param {element} elem - The element to convert.
35350 * @returns {element} A HTML element.
35352 getRawNode: function(elem) {
35353 return elem[0] || elem;
35357 * Provides a parsed number for a style property. Strips
35358 * units and casts invalid numbers to 0.
35360 * @param {string} value - The style value to parse.
35362 * @returns {number} A valid number.
35364 parseStyle: function(value) {
35365 value = parseFloat(value);
35366 return isFinite(value) ? value : 0;
35370 * Provides the closest positioned ancestor.
35372 * @param {element} element - The element to get the offest parent for.
35374 * @returns {element} The closest positioned ancestor.
35376 offsetParent: function(elem) {
35377 elem = this.getRawNode(elem);
35379 var offsetParent = elem.offsetParent || $document[0].documentElement;
35381 function isStaticPositioned(el) {
35382 return ($window.getComputedStyle(el).position || 'static') === 'static';
35385 while (offsetParent && offsetParent !== $document[0].documentElement && isStaticPositioned(offsetParent)) {
35386 offsetParent = offsetParent.offsetParent;
35389 return offsetParent || $document[0].documentElement;
35393 * Provides the scrollbar width, concept from TWBS measureScrollbar()
35394 * function in https://github.com/twbs/bootstrap/blob/master/js/modal.js
35396 * @returns {number} The width of the browser scollbar.
35398 scrollbarWidth: function() {
35399 if (angular.isUndefined(SCROLLBAR_WIDTH)) {
35400 var scrollElem = angular.element('<div style="position: absolute; top: -9999px; width: 50px; height: 50px; overflow: scroll;"></div>');
35401 $document.find('body').append(scrollElem);
35402 SCROLLBAR_WIDTH = scrollElem[0].offsetWidth - scrollElem[0].clientWidth;
35403 SCROLLBAR_WIDTH = isFinite(SCROLLBAR_WIDTH) ? SCROLLBAR_WIDTH : 0;
35404 scrollElem.remove();
35407 return SCROLLBAR_WIDTH;
35411 * Provides the closest scrollable ancestor.
35412 * A port of the jQuery UI scrollParent method:
35413 * https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js
35415 * @param {element} elem - The element to find the scroll parent of.
35416 * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
35417 * default is false.
35419 * @returns {element} A HTML element.
35421 scrollParent: function(elem, includeHidden) {
35422 elem = this.getRawNode(elem);
35424 var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
35425 var documentEl = $document[0].documentElement;
35426 var elemStyle = $window.getComputedStyle(elem);
35427 var excludeStatic = elemStyle.position === 'absolute';
35428 var scrollParent = elem.parentElement || documentEl;
35430 if (scrollParent === documentEl || elemStyle.position === 'fixed') {
35434 while (scrollParent.parentElement && scrollParent !== documentEl) {
35435 var spStyle = $window.getComputedStyle(scrollParent);
35436 if (excludeStatic && spStyle.position !== 'static') {
35437 excludeStatic = false;
35440 if (!excludeStatic && overflowRegex.test(spStyle.overflow + spStyle.overflowY + spStyle.overflowX)) {
35443 scrollParent = scrollParent.parentElement;
35446 return scrollParent;
35450 * Provides read-only equivalent of jQuery's position function:
35451 * http://api.jquery.com/position/ - distance to closest positioned
35452 * ancestor. Does not account for margins by default like jQuery position.
35454 * @param {element} elem - The element to caclulate the position on.
35455 * @param {boolean=} [includeMargins=false] - Should margins be accounted
35456 * for, default is false.
35458 * @returns {object} An object with the following properties:
35460 * <li>**width**: the width of the element</li>
35461 * <li>**height**: the height of the element</li>
35462 * <li>**top**: distance to top edge of offset parent</li>
35463 * <li>**left**: distance to left edge of offset parent</li>
35466 position: function(elem, includeMagins) {
35467 elem = this.getRawNode(elem);
35469 var elemOffset = this.offset(elem);
35470 if (includeMagins) {
35471 var elemStyle = $window.getComputedStyle(elem);
35472 elemOffset.top -= this.parseStyle(elemStyle.marginTop);
35473 elemOffset.left -= this.parseStyle(elemStyle.marginLeft);
35475 var parent = this.offsetParent(elem);
35476 var parentOffset = {top: 0, left: 0};
35478 if (parent !== $document[0].documentElement) {
35479 parentOffset = this.offset(parent);
35480 parentOffset.top += parent.clientTop - parent.scrollTop;
35481 parentOffset.left += parent.clientLeft - parent.scrollLeft;
35485 width: Math.round(angular.isNumber(elemOffset.width) ? elemOffset.width : elem.offsetWidth),
35486 height: Math.round(angular.isNumber(elemOffset.height) ? elemOffset.height : elem.offsetHeight),
35487 top: Math.round(elemOffset.top - parentOffset.top),
35488 left: Math.round(elemOffset.left - parentOffset.left)
35493 * Provides read-only equivalent of jQuery's offset function:
35494 * http://api.jquery.com/offset/ - distance to viewport. Does
35495 * not account for borders, margins, or padding on the body
35498 * @param {element} elem - The element to calculate the offset on.
35500 * @returns {object} An object with the following properties:
35502 * <li>**width**: the width of the element</li>
35503 * <li>**height**: the height of the element</li>
35504 * <li>**top**: distance to top edge of viewport</li>
35505 * <li>**right**: distance to bottom edge of viewport</li>
35508 offset: function(elem) {
35509 elem = this.getRawNode(elem);
35511 var elemBCR = elem.getBoundingClientRect();
35513 width: Math.round(angular.isNumber(elemBCR.width) ? elemBCR.width : elem.offsetWidth),
35514 height: Math.round(angular.isNumber(elemBCR.height) ? elemBCR.height : elem.offsetHeight),
35515 top: Math.round(elemBCR.top + ($window.pageYOffset || $document[0].documentElement.scrollTop)),
35516 left: Math.round(elemBCR.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft))
35521 * Provides offset distance to the closest scrollable ancestor
35522 * or viewport. Accounts for border and scrollbar width.
35524 * Right and bottom dimensions represent the distance to the
35525 * respective edge of the viewport element. If the element
35526 * edge extends beyond the viewport, a negative value will be
35529 * @param {element} elem - The element to get the viewport offset for.
35530 * @param {boolean=} [useDocument=false] - Should the viewport be the document element instead
35531 * of the first scrollable element, default is false.
35532 * @param {boolean=} [includePadding=true] - Should the padding on the offset parent element
35533 * be accounted for, default is true.
35535 * @returns {object} An object with the following properties:
35537 * <li>**top**: distance to the top content edge of viewport element</li>
35538 * <li>**bottom**: distance to the bottom content edge of viewport element</li>
35539 * <li>**left**: distance to the left content edge of viewport element</li>
35540 * <li>**right**: distance to the right content edge of viewport element</li>
35543 viewportOffset: function(elem, useDocument, includePadding) {
35544 elem = this.getRawNode(elem);
35545 includePadding = includePadding !== false ? true : false;
35547 var elemBCR = elem.getBoundingClientRect();
35548 var offsetBCR = {top: 0, left: 0, bottom: 0, right: 0};
35550 var offsetParent = useDocument ? $document[0].documentElement : this.scrollParent(elem);
35551 var offsetParentBCR = offsetParent.getBoundingClientRect();
35553 offsetBCR.top = offsetParentBCR.top + offsetParent.clientTop;
35554 offsetBCR.left = offsetParentBCR.left + offsetParent.clientLeft;
35555 if (offsetParent === $document[0].documentElement) {
35556 offsetBCR.top += $window.pageYOffset;
35557 offsetBCR.left += $window.pageXOffset;
35559 offsetBCR.bottom = offsetBCR.top + offsetParent.clientHeight;
35560 offsetBCR.right = offsetBCR.left + offsetParent.clientWidth;
35562 if (includePadding) {
35563 var offsetParentStyle = $window.getComputedStyle(offsetParent);
35564 offsetBCR.top += this.parseStyle(offsetParentStyle.paddingTop);
35565 offsetBCR.bottom -= this.parseStyle(offsetParentStyle.paddingBottom);
35566 offsetBCR.left += this.parseStyle(offsetParentStyle.paddingLeft);
35567 offsetBCR.right -= this.parseStyle(offsetParentStyle.paddingRight);
35571 top: Math.round(elemBCR.top - offsetBCR.top),
35572 bottom: Math.round(offsetBCR.bottom - elemBCR.bottom),
35573 left: Math.round(elemBCR.left - offsetBCR.left),
35574 right: Math.round(offsetBCR.right - elemBCR.right)
35579 * Provides an array of placement values parsed from a placement string.
35580 * Along with the 'auto' indicator, supported placement strings are:
35582 * <li>top: element on top, horizontally centered on host element.</li>
35583 * <li>top-left: element on top, left edge aligned with host element left edge.</li>
35584 * <li>top-right: element on top, lerightft edge aligned with host element right edge.</li>
35585 * <li>bottom: element on bottom, horizontally centered on host element.</li>
35586 * <li>bottom-left: element on bottom, left edge aligned with host element left edge.</li>
35587 * <li>bottom-right: element on bottom, right edge aligned with host element right edge.</li>
35588 * <li>left: element on left, vertically centered on host element.</li>
35589 * <li>left-top: element on left, top edge aligned with host element top edge.</li>
35590 * <li>left-bottom: element on left, bottom edge aligned with host element bottom edge.</li>
35591 * <li>right: element on right, vertically centered on host element.</li>
35592 * <li>right-top: element on right, top edge aligned with host element top edge.</li>
35593 * <li>right-bottom: element on right, bottom edge aligned with host element bottom edge.</li>
35595 * A placement string with an 'auto' indicator is expected to be
35596 * space separated from the placement, i.e: 'auto bottom-left' If
35597 * the primary and secondary placement values do not match 'top,
35598 * bottom, left, right' then 'top' will be the primary placement and
35599 * 'center' will be the secondary placement. If 'auto' is passed, true
35600 * will be returned as the 3rd value of the array.
35602 * @param {string} placement - The placement string to parse.
35604 * @returns {array} An array with the following values
35606 * <li>**[0]**: The primary placement.</li>
35607 * <li>**[1]**: The secondary placement.</li>
35608 * <li>**[2]**: If auto is passed: true, else undefined.</li>
35611 parsePlacement: function(placement) {
35612 var autoPlace = PLACEMENT_REGEX.auto.test(placement);
35614 placement = placement.replace(PLACEMENT_REGEX.auto, '');
35617 placement = placement.split('-');
35619 placement[0] = placement[0] || 'top';
35620 if (!PLACEMENT_REGEX.primary.test(placement[0])) {
35621 placement[0] = 'top';
35624 placement[1] = placement[1] || 'center';
35625 if (!PLACEMENT_REGEX.secondary.test(placement[1])) {
35626 placement[1] = 'center';
35630 placement[2] = true;
35632 placement[2] = false;
35639 * Provides coordinates for an element to be positioned relative to
35640 * another element. Passing 'auto' as part of the placement parameter
35641 * will enable smart placement - where the element fits. i.e:
35642 * 'auto left-top' will check to see if there is enough space to the left
35643 * of the hostElem to fit the targetElem, if not place right (same for secondary
35644 * top placement). Available space is calculated using the viewportOffset
35647 * @param {element} hostElem - The element to position against.
35648 * @param {element} targetElem - The element to position.
35649 * @param {string=} [placement=top] - The placement for the targetElem,
35650 * default is 'top'. 'center' is assumed as secondary placement for
35651 * 'top', 'left', 'right', and 'bottom' placements. Available placements are:
35654 * <li>top-right</li>
35655 * <li>top-left</li>
35657 * <li>bottom-left</li>
35658 * <li>bottom-right</li>
35660 * <li>left-top</li>
35661 * <li>left-bottom</li>
35663 * <li>right-top</li>
35664 * <li>right-bottom</li>
35666 * @param {boolean=} [appendToBody=false] - Should the top and left values returned
35667 * be calculated from the body element, default is false.
35669 * @returns {object} An object with the following properties:
35671 * <li>**top**: Value for targetElem top.</li>
35672 * <li>**left**: Value for targetElem left.</li>
35673 * <li>**placement**: The resolved placement.</li>
35676 positionElements: function(hostElem, targetElem, placement, appendToBody) {
35677 hostElem = this.getRawNode(hostElem);
35678 targetElem = this.getRawNode(targetElem);
35680 // need to read from prop to support tests.
35681 var targetWidth = angular.isDefined(targetElem.offsetWidth) ? targetElem.offsetWidth : targetElem.prop('offsetWidth');
35682 var targetHeight = angular.isDefined(targetElem.offsetHeight) ? targetElem.offsetHeight : targetElem.prop('offsetHeight');
35684 placement = this.parsePlacement(placement);
35686 var hostElemPos = appendToBody ? this.offset(hostElem) : this.position(hostElem);
35687 var targetElemPos = {top: 0, left: 0, placement: ''};
35689 if (placement[2]) {
35690 var viewportOffset = this.viewportOffset(hostElem);
35692 var targetElemStyle = $window.getComputedStyle(targetElem);
35693 var adjustedSize = {
35694 width: targetWidth + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginLeft) + this.parseStyle(targetElemStyle.marginRight))),
35695 height: targetHeight + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginTop) + this.parseStyle(targetElemStyle.marginBottom)))
35698 placement[0] = placement[0] === 'top' && adjustedSize.height > viewportOffset.top && adjustedSize.height <= viewportOffset.bottom ? 'bottom' :
35699 placement[0] === 'bottom' && adjustedSize.height > viewportOffset.bottom && adjustedSize.height <= viewportOffset.top ? 'top' :
35700 placement[0] === 'left' && adjustedSize.width > viewportOffset.left && adjustedSize.width <= viewportOffset.right ? 'right' :
35701 placement[0] === 'right' && adjustedSize.width > viewportOffset.right && adjustedSize.width <= viewportOffset.left ? 'left' :
35704 placement[1] = placement[1] === 'top' && adjustedSize.height - hostElemPos.height > viewportOffset.bottom && adjustedSize.height - hostElemPos.height <= viewportOffset.top ? 'bottom' :
35705 placement[1] === 'bottom' && adjustedSize.height - hostElemPos.height > viewportOffset.top && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom ? 'top' :
35706 placement[1] === 'left' && adjustedSize.width - hostElemPos.width > viewportOffset.right && adjustedSize.width - hostElemPos.width <= viewportOffset.left ? 'right' :
35707 placement[1] === 'right' && adjustedSize.width - hostElemPos.width > viewportOffset.left && adjustedSize.width - hostElemPos.width <= viewportOffset.right ? 'left' :
35710 if (placement[1] === 'center') {
35711 if (PLACEMENT_REGEX.vertical.test(placement[0])) {
35712 var xOverflow = hostElemPos.width / 2 - targetWidth / 2;
35713 if (viewportOffset.left + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.right) {
35714 placement[1] = 'left';
35715 } else if (viewportOffset.right + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.left) {
35716 placement[1] = 'right';
35719 var yOverflow = hostElemPos.height / 2 - adjustedSize.height / 2;
35720 if (viewportOffset.top + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom) {
35721 placement[1] = 'top';
35722 } else if (viewportOffset.bottom + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.top) {
35723 placement[1] = 'bottom';
35729 switch (placement[0]) {
35731 targetElemPos.top = hostElemPos.top - targetHeight;
35734 targetElemPos.top = hostElemPos.top + hostElemPos.height;
35737 targetElemPos.left = hostElemPos.left - targetWidth;
35740 targetElemPos.left = hostElemPos.left + hostElemPos.width;
35744 switch (placement[1]) {
35746 targetElemPos.top = hostElemPos.top;
35749 targetElemPos.top = hostElemPos.top + hostElemPos.height - targetHeight;
35752 targetElemPos.left = hostElemPos.left;
35755 targetElemPos.left = hostElemPos.left + hostElemPos.width - targetWidth;
35758 if (PLACEMENT_REGEX.vertical.test(placement[0])) {
35759 targetElemPos.left = hostElemPos.left + hostElemPos.width / 2 - targetWidth / 2;
35761 targetElemPos.top = hostElemPos.top + hostElemPos.height / 2 - targetHeight / 2;
35766 targetElemPos.top = Math.round(targetElemPos.top);
35767 targetElemPos.left = Math.round(targetElemPos.left);
35768 targetElemPos.placement = placement[1] === 'center' ? placement[0] : placement[0] + '-' + placement[1];
35770 return targetElemPos;
35774 * Provides a way for positioning tooltip & dropdown
35775 * arrows when using placement options beyond the standard
35776 * left, right, top, or bottom.
35778 * @param {element} elem - The tooltip/dropdown element.
35779 * @param {string} placement - The placement for the elem.
35781 positionArrow: function(elem, placement) {
35782 elem = this.getRawNode(elem);
35784 var isTooltip = true;
35786 var innerElem = elem.querySelector('.tooltip-inner');
35789 innerElem = elem.querySelector('.popover-inner');
35795 var arrowElem = isTooltip ? elem.querySelector('.tooltip-arrow') : elem.querySelector('.arrow');
35800 placement = this.parsePlacement(placement);
35801 if (placement[1] === 'center') {
35802 // no adjustment necessary - just reset styles
35803 angular.element(arrowElem).css({top: '', bottom: '', right: '', left: '', margin: ''});
35807 var borderProp = 'border-' + placement[0] + '-width';
35808 var borderWidth = $window.getComputedStyle(arrowElem)[borderProp];
35810 var borderRadiusProp = 'border-';
35811 if (PLACEMENT_REGEX.vertical.test(placement[0])) {
35812 borderRadiusProp += placement[0] + '-' + placement[1];
35814 borderRadiusProp += placement[1] + '-' + placement[0];
35816 borderRadiusProp += '-radius';
35817 var borderRadius = $window.getComputedStyle(isTooltip ? innerElem : elem)[borderRadiusProp];
35827 switch (placement[0]) {
35829 arrowCss.bottom = isTooltip ? '0' : '-' + borderWidth;
35832 arrowCss.top = isTooltip ? '0' : '-' + borderWidth;
35835 arrowCss.right = isTooltip ? '0' : '-' + borderWidth;
35838 arrowCss.left = isTooltip ? '0' : '-' + borderWidth;
35842 arrowCss[placement[1]] = borderRadius;
35844 angular.element(arrowElem).css(arrowCss);
35849 angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.isClass', 'ui.bootstrap.position'])
35851 .value('$datepickerSuppressError', false)
35853 .constant('uibDatepickerConfig', {
35855 formatMonth: 'MMMM',
35856 formatYear: 'yyyy',
35857 formatDayHeader: 'EEE',
35858 formatDayTitle: 'MMMM yyyy',
35859 formatMonthTitle: 'yyyy',
35860 datepickerMode: 'day',
35869 shortcutPropagation: false,
35873 .controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerSuppressError', 'uibDateParser',
35874 function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError, dateParser) {
35876 ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl;
35877 ngModelOptions = {};
35880 this.modes = ['day', 'month', 'year'];
35882 // Interpolated configuration attributes
35883 angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle'], function(key) {
35884 self[key] = angular.isDefined($attrs[key]) ? $interpolate($attrs[key])($scope.$parent) : datepickerConfig[key];
35887 // Evaled configuration attributes
35888 angular.forEach(['showWeeks', 'startingDay', 'yearRows', 'yearColumns', 'shortcutPropagation'], function(key) {
35889 self[key] = angular.isDefined($attrs[key]) ? $scope.$parent.$eval($attrs[key]) : datepickerConfig[key];
35892 // Watchable date attributes
35893 angular.forEach(['minDate', 'maxDate'], function(key) {
35895 $scope.$parent.$watch($attrs[key], function(value) {
35896 self[key] = value ? angular.isDate(value) ? dateParser.fromTimezone(new Date(value), ngModelOptions.timezone) : new Date(dateFilter(value, 'medium')) : null;
35897 self.refreshView();
35900 self[key] = datepickerConfig[key] ? dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) : null;
35904 angular.forEach(['minMode', 'maxMode'], function(key) {
35906 $scope.$parent.$watch($attrs[key], function(value) {
35907 self[key] = $scope[key] = angular.isDefined(value) ? value : $attrs[key];
35908 if (key === 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key]) ||
35909 key === 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key])) {
35910 $scope.datepickerMode = self[key];
35914 self[key] = $scope[key] = datepickerConfig[key] || null;
35918 $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode;
35919 $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
35921 if (angular.isDefined($attrs.initDate)) {
35922 this.activeDate = dateParser.fromTimezone($scope.$parent.$eval($attrs.initDate), ngModelOptions.timezone) || new Date();
35923 $scope.$parent.$watch($attrs.initDate, function(initDate) {
35924 if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
35925 self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone);
35926 self.refreshView();
35930 this.activeDate = new Date();
35933 $scope.disabled = angular.isDefined($attrs.disabled) || false;
35934 if (angular.isDefined($attrs.ngDisabled)) {
35935 $scope.$parent.$watch($attrs.ngDisabled, function(disabled) {
35936 $scope.disabled = disabled;
35937 self.refreshView();
35941 $scope.isActive = function(dateObject) {
35942 if (self.compare(dateObject.date, self.activeDate) === 0) {
35943 $scope.activeDateId = dateObject.uid;
35949 this.init = function(ngModelCtrl_) {
35950 ngModelCtrl = ngModelCtrl_;
35951 ngModelOptions = ngModelCtrl_.$options || datepickerConfig.ngModelOptions;
35953 if (ngModelCtrl.$modelValue) {
35954 this.activeDate = ngModelCtrl.$modelValue;
35957 ngModelCtrl.$render = function() {
35962 this.render = function() {
35963 if (ngModelCtrl.$viewValue) {
35964 var date = new Date(ngModelCtrl.$viewValue),
35965 isValid = !isNaN(date);
35968 this.activeDate = dateParser.fromTimezone(date, ngModelOptions.timezone);
35969 } else if (!$datepickerSuppressError) {
35970 $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.');
35973 this.refreshView();
35976 this.refreshView = function() {
35977 if (this.element) {
35978 $scope.selectedDt = null;
35979 this._refreshView();
35980 if ($scope.activeDt) {
35981 $scope.activeDateId = $scope.activeDt.uid;
35984 var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
35985 date = dateParser.fromTimezone(date, ngModelOptions.timezone);
35986 ngModelCtrl.$setValidity('dateDisabled', !date ||
35987 this.element && !this.isDisabled(date));
35991 this.createDateObject = function(date, format) {
35992 var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
35993 model = dateParser.fromTimezone(model, ngModelOptions.timezone);
35996 label: dateFilter(date, format),
35997 selected: model && this.compare(date, model) === 0,
35998 disabled: this.isDisabled(date),
35999 current: this.compare(date, new Date()) === 0,
36000 customClass: this.customClass(date) || null
36003 if (model && this.compare(date, model) === 0) {
36004 $scope.selectedDt = dt;
36007 if (self.activeDate && this.compare(dt.date, self.activeDate) === 0) {
36008 $scope.activeDt = dt;
36014 this.isDisabled = function(date) {
36015 return $scope.disabled ||
36016 this.minDate && this.compare(date, this.minDate) < 0 ||
36017 this.maxDate && this.compare(date, this.maxDate) > 0 ||
36018 $attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode});
36021 this.customClass = function(date) {
36022 return $scope.customClass({date: date, mode: $scope.datepickerMode});
36025 // Split array into smaller arrays
36026 this.split = function(arr, size) {
36028 while (arr.length > 0) {
36029 arrays.push(arr.splice(0, size));
36034 $scope.select = function(date) {
36035 if ($scope.datepickerMode === self.minMode) {
36036 var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.timezone) : new Date(0, 0, 0, 0, 0, 0, 0);
36037 dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
36038 dt = dateParser.toTimezone(dt, ngModelOptions.timezone);
36039 ngModelCtrl.$setViewValue(dt);
36040 ngModelCtrl.$render();
36042 self.activeDate = date;
36043 $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) - 1];
36047 $scope.move = function(direction) {
36048 var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
36049 month = self.activeDate.getMonth() + direction * (self.step.months || 0);
36050 self.activeDate.setFullYear(year, month, 1);
36051 self.refreshView();
36054 $scope.toggleMode = function(direction) {
36055 direction = direction || 1;
36057 if ($scope.datepickerMode === self.maxMode && direction === 1 ||
36058 $scope.datepickerMode === self.minMode && direction === -1) {
36062 $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) + direction];
36065 // Key event mapper
36066 $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' };
36068 var focusElement = function() {
36069 self.element[0].focus();
36072 // Listen for focus requests from popup directive
36073 $scope.$on('uib:datepicker.focus', focusElement);
36075 $scope.keydown = function(evt) {
36076 var key = $scope.keys[evt.which];
36078 if (!key || evt.shiftKey || evt.altKey || $scope.disabled) {
36082 evt.preventDefault();
36083 if (!self.shortcutPropagation) {
36084 evt.stopPropagation();
36087 if (key === 'enter' || key === 'space') {
36088 if (self.isDisabled(self.activeDate)) {
36089 return; // do nothing
36091 $scope.select(self.activeDate);
36092 } else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
36093 $scope.toggleMode(key === 'up' ? 1 : -1);
36095 self.handleKeyDown(key, evt);
36096 self.refreshView();
36101 .controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
36102 var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
36104 this.step = { months: 1 };
36105 this.element = $element;
36106 function getDaysInMonth(year, month) {
36107 return month === 1 && year % 4 === 0 &&
36108 (year % 100 !== 0 || year % 400 === 0) ? 29 : DAYS_IN_MONTH[month];
36111 this.init = function(ctrl) {
36112 angular.extend(ctrl, this);
36113 scope.showWeeks = ctrl.showWeeks;
36114 ctrl.refreshView();
36117 this.getDates = function(startDate, n) {
36118 var dates = new Array(n), current = new Date(startDate), i = 0, date;
36120 date = new Date(current);
36122 current.setDate(current.getDate() + 1);
36127 this._refreshView = function() {
36128 var year = this.activeDate.getFullYear(),
36129 month = this.activeDate.getMonth(),
36130 firstDayOfMonth = new Date(this.activeDate);
36132 firstDayOfMonth.setFullYear(year, month, 1);
36134 var difference = this.startingDay - firstDayOfMonth.getDay(),
36135 numDisplayedFromPreviousMonth = difference > 0 ?
36136 7 - difference : - difference,
36137 firstDate = new Date(firstDayOfMonth);
36139 if (numDisplayedFromPreviousMonth > 0) {
36140 firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
36143 // 42 is the number of days on a six-week calendar
36144 var days = this.getDates(firstDate, 42);
36145 for (var i = 0; i < 42; i ++) {
36146 days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), {
36147 secondary: days[i].getMonth() !== month,
36148 uid: scope.uniqueId + '-' + i
36152 scope.labels = new Array(7);
36153 for (var j = 0; j < 7; j++) {
36154 scope.labels[j] = {
36155 abbr: dateFilter(days[j].date, this.formatDayHeader),
36156 full: dateFilter(days[j].date, 'EEEE')
36160 scope.title = dateFilter(this.activeDate, this.formatDayTitle);
36161 scope.rows = this.split(days, 7);
36163 if (scope.showWeeks) {
36164 scope.weekNumbers = [];
36165 var thursdayIndex = (4 + 7 - this.startingDay) % 7,
36166 numWeeks = scope.rows.length;
36167 for (var curWeek = 0; curWeek < numWeeks; curWeek++) {
36168 scope.weekNumbers.push(
36169 getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date));
36174 this.compare = function(date1, date2) {
36175 var _date1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate());
36176 var _date2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
36177 _date1.setFullYear(date1.getFullYear());
36178 _date2.setFullYear(date2.getFullYear());
36179 return _date1 - _date2;
36182 function getISO8601WeekNumber(date) {
36183 var checkDate = new Date(date);
36184 checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
36185 var time = checkDate.getTime();
36186 checkDate.setMonth(0); // Compare with Jan 1
36187 checkDate.setDate(1);
36188 return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
36191 this.handleKeyDown = function(key, evt) {
36192 var date = this.activeDate.getDate();
36194 if (key === 'left') {
36196 } else if (key === 'up') {
36198 } else if (key === 'right') {
36200 } else if (key === 'down') {
36202 } else if (key === 'pageup' || key === 'pagedown') {
36203 var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1);
36204 this.activeDate.setMonth(month, 1);
36205 date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date);
36206 } else if (key === 'home') {
36208 } else if (key === 'end') {
36209 date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth());
36211 this.activeDate.setDate(date);
36215 .controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
36216 this.step = { years: 1 };
36217 this.element = $element;
36219 this.init = function(ctrl) {
36220 angular.extend(ctrl, this);
36221 ctrl.refreshView();
36224 this._refreshView = function() {
36225 var months = new Array(12),
36226 year = this.activeDate.getFullYear(),
36229 for (var i = 0; i < 12; i++) {
36230 date = new Date(this.activeDate);
36231 date.setFullYear(year, i, 1);
36232 months[i] = angular.extend(this.createDateObject(date, this.formatMonth), {
36233 uid: scope.uniqueId + '-' + i
36237 scope.title = dateFilter(this.activeDate, this.formatMonthTitle);
36238 scope.rows = this.split(months, 3);
36241 this.compare = function(date1, date2) {
36242 var _date1 = new Date(date1.getFullYear(), date1.getMonth());
36243 var _date2 = new Date(date2.getFullYear(), date2.getMonth());
36244 _date1.setFullYear(date1.getFullYear());
36245 _date2.setFullYear(date2.getFullYear());
36246 return _date1 - _date2;
36249 this.handleKeyDown = function(key, evt) {
36250 var date = this.activeDate.getMonth();
36252 if (key === 'left') {
36254 } else if (key === 'up') {
36256 } else if (key === 'right') {
36258 } else if (key === 'down') {
36260 } else if (key === 'pageup' || key === 'pagedown') {
36261 var year = this.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1);
36262 this.activeDate.setFullYear(year);
36263 } else if (key === 'home') {
36265 } else if (key === 'end') {
36268 this.activeDate.setMonth(date);
36272 .controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
36273 var columns, range;
36274 this.element = $element;
36276 function getStartingYear(year) {
36277 return parseInt((year - 1) / range, 10) * range + 1;
36280 this.yearpickerInit = function() {
36281 columns = this.yearColumns;
36282 range = this.yearRows * columns;
36283 this.step = { years: range };
36286 this._refreshView = function() {
36287 var years = new Array(range), date;
36289 for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) {
36290 date = new Date(this.activeDate);
36291 date.setFullYear(start + i, 0, 1);
36292 years[i] = angular.extend(this.createDateObject(date, this.formatYear), {
36293 uid: scope.uniqueId + '-' + i
36297 scope.title = [years[0].label, years[range - 1].label].join(' - ');
36298 scope.rows = this.split(years, columns);
36299 scope.columns = columns;
36302 this.compare = function(date1, date2) {
36303 return date1.getFullYear() - date2.getFullYear();
36306 this.handleKeyDown = function(key, evt) {
36307 var date = this.activeDate.getFullYear();
36309 if (key === 'left') {
36311 } else if (key === 'up') {
36312 date = date - columns;
36313 } else if (key === 'right') {
36315 } else if (key === 'down') {
36316 date = date + columns;
36317 } else if (key === 'pageup' || key === 'pagedown') {
36318 date += (key === 'pageup' ? - 1 : 1) * range;
36319 } else if (key === 'home') {
36320 date = getStartingYear(this.activeDate.getFullYear());
36321 } else if (key === 'end') {
36322 date = getStartingYear(this.activeDate.getFullYear()) + range - 1;
36324 this.activeDate.setFullYear(date);
36328 .directive('uibDatepicker', function() {
36331 templateUrl: function(element, attrs) {
36332 return attrs.templateUrl || 'uib/template/datepicker/datepicker.html';
36335 datepickerMode: '=?',
36338 shortcutPropagation: '&?'
36340 require: ['uibDatepicker', '^ngModel'],
36341 controller: 'UibDatepickerController',
36342 controllerAs: 'datepicker',
36343 link: function(scope, element, attrs, ctrls) {
36344 var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
36346 datepickerCtrl.init(ngModelCtrl);
36351 .directive('uibDaypicker', function() {
36354 templateUrl: function(element, attrs) {
36355 return attrs.templateUrl || 'uib/template/datepicker/day.html';
36357 require: ['^uibDatepicker', 'uibDaypicker'],
36358 controller: 'UibDaypickerController',
36359 link: function(scope, element, attrs, ctrls) {
36360 var datepickerCtrl = ctrls[0],
36361 daypickerCtrl = ctrls[1];
36363 daypickerCtrl.init(datepickerCtrl);
36368 .directive('uibMonthpicker', function() {
36371 templateUrl: function(element, attrs) {
36372 return attrs.templateUrl || 'uib/template/datepicker/month.html';
36374 require: ['^uibDatepicker', 'uibMonthpicker'],
36375 controller: 'UibMonthpickerController',
36376 link: function(scope, element, attrs, ctrls) {
36377 var datepickerCtrl = ctrls[0],
36378 monthpickerCtrl = ctrls[1];
36380 monthpickerCtrl.init(datepickerCtrl);
36385 .directive('uibYearpicker', function() {
36388 templateUrl: function(element, attrs) {
36389 return attrs.templateUrl || 'uib/template/datepicker/year.html';
36391 require: ['^uibDatepicker', 'uibYearpicker'],
36392 controller: 'UibYearpickerController',
36393 link: function(scope, element, attrs, ctrls) {
36394 var ctrl = ctrls[0];
36395 angular.extend(ctrl, ctrls[1]);
36396 ctrl.yearpickerInit();
36398 ctrl.refreshView();
36403 .constant('uibDatepickerPopupConfig', {
36404 datepickerPopup: 'yyyy-MM-dd',
36405 datepickerPopupTemplateUrl: 'uib/template/datepicker/popup.html',
36406 datepickerTemplateUrl: 'uib/template/datepicker/datepicker.html',
36408 date: 'yyyy-MM-dd',
36409 'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',
36412 currentText: 'Today',
36413 clearText: 'Clear',
36415 closeOnDateSelection: true,
36416 appendToBody: false,
36417 showButtonBar: true,
36419 altInputFormats: []
36422 .controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig',
36423 function(scope, element, attrs, $compile, $parse, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig) {
36426 isHtml5DateInput = false;
36427 var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
36428 datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl,
36429 ngModel, ngModelOptions, $popup, altInputFormats;
36431 scope.watchData = {};
36433 this.init = function(_ngModel_) {
36434 ngModel = _ngModel_;
36435 ngModelOptions = _ngModel_.$options || datepickerConfig.ngModelOptions;
36436 closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection;
36437 appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody;
36438 onOpenFocus = angular.isDefined(attrs.onOpenFocus) ? scope.$parent.$eval(attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus;
36439 datepickerPopupTemplateUrl = angular.isDefined(attrs.datepickerPopupTemplateUrl) ? attrs.datepickerPopupTemplateUrl : datepickerPopupConfig.datepickerPopupTemplateUrl;
36440 datepickerTemplateUrl = angular.isDefined(attrs.datepickerTemplateUrl) ? attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl;
36441 altInputFormats = angular.isDefined(attrs.altInputFormats) ? scope.$parent.$eval(attrs.altInputFormats) : datepickerPopupConfig.altInputFormats;
36443 scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar;
36445 if (datepickerPopupConfig.html5Types[attrs.type]) {
36446 dateFormat = datepickerPopupConfig.html5Types[attrs.type];
36447 isHtml5DateInput = true;
36449 dateFormat = attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup;
36450 attrs.$observe('uibDatepickerPopup', function(value, oldValue) {
36451 var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
36452 // Invalidate the $modelValue to ensure that formatters re-run
36453 // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
36454 if (newDateFormat !== dateFormat) {
36455 dateFormat = newDateFormat;
36456 ngModel.$modelValue = null;
36459 throw new Error('uibDatepickerPopup must have a date format specified.');
36466 throw new Error('uibDatepickerPopup must have a date format specified.');
36469 if (isHtml5DateInput && attrs.uibDatepickerPopup) {
36470 throw new Error('HTML5 date input types do not support custom formats.');
36473 // popup element used to display calendar
36474 popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>');
36475 scope.ngModelOptions = angular.copy(ngModelOptions);
36476 scope.ngModelOptions.timezone = null;
36478 'ng-model': 'date',
36479 'ng-model-options': 'ngModelOptions',
36480 'ng-change': 'dateSelection(date)',
36481 'template-url': datepickerPopupTemplateUrl
36484 // datepicker element
36485 datepickerEl = angular.element(popupEl.children()[0]);
36486 datepickerEl.attr('template-url', datepickerTemplateUrl);
36488 if (isHtml5DateInput) {
36489 if (attrs.type === 'month') {
36490 datepickerEl.attr('datepicker-mode', '"month"');
36491 datepickerEl.attr('min-mode', 'month');
36495 if (attrs.datepickerOptions) {
36496 var options = scope.$parent.$eval(attrs.datepickerOptions);
36497 if (options && options.initDate) {
36498 scope.initDate = dateParser.fromTimezone(options.initDate, ngModelOptions.timezone);
36499 datepickerEl.attr('init-date', 'initDate');
36500 delete options.initDate;
36502 angular.forEach(options, function(value, option) {
36503 datepickerEl.attr(cameltoDash(option), value);
36507 angular.forEach(['minMode', 'maxMode'], function(key) {
36509 scope.$parent.$watch(function() { return attrs[key]; }, function(value) {
36510 scope.watchData[key] = value;
36512 datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
36516 angular.forEach(['datepickerMode', 'shortcutPropagation'], function(key) {
36518 var getAttribute = $parse(attrs[key]);
36521 return getAttribute(scope.$parent);
36525 datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
36527 // Propagate changes from datepicker to outside
36528 if (key === 'datepickerMode') {
36529 var setAttribute = getAttribute.assign;
36530 propConfig.set = function(v) {
36531 setAttribute(scope.$parent, v);
36535 Object.defineProperty(scope.watchData, key, propConfig);
36539 angular.forEach(['minDate', 'maxDate', 'initDate'], function(key) {
36541 var getAttribute = $parse(attrs[key]);
36543 scope.$parent.$watch(getAttribute, function(value) {
36544 if (key === 'minDate' || key === 'maxDate') {
36545 cache[key] = angular.isDate(value) ? dateParser.fromTimezone(new Date(value), ngModelOptions.timezone) : new Date(dateFilter(value, 'medium'));
36548 scope.watchData[key] = cache[key] || dateParser.fromTimezone(new Date(value), ngModelOptions.timezone);
36551 datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
36555 if (attrs.dateDisabled) {
36556 datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })');
36559 angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle', 'showWeeks', 'startingDay', 'yearRows', 'yearColumns'], function(key) {
36560 if (angular.isDefined(attrs[key])) {
36561 datepickerEl.attr(cameltoDash(key), attrs[key]);
36565 if (attrs.customClass) {
36566 datepickerEl.attr('custom-class', 'customClass({ date: date, mode: mode })');
36569 if (!isHtml5DateInput) {
36570 // Internal API to maintain the correct ng-invalid-[key] class
36571 ngModel.$$parserName = 'date';
36572 ngModel.$validators.date = validator;
36573 ngModel.$parsers.unshift(parseDate);
36574 ngModel.$formatters.push(function(value) {
36575 if (ngModel.$isEmpty(value)) {
36576 scope.date = value;
36579 scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);
36580 return dateFilter(scope.date, dateFormat);
36583 ngModel.$formatters.push(function(value) {
36584 scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);
36589 // Detect changes in the view from the text box
36590 ngModel.$viewChangeListeners.push(function() {
36591 scope.date = parseDateString(ngModel.$viewValue);
36594 element.bind('keydown', inputKeydownBind);
36596 $popup = $compile(popupEl)(scope);
36597 // Prevent jQuery cache memory leak (template is now redundant after linking)
36600 if (appendToBody) {
36601 $document.find('body').append($popup);
36603 element.after($popup);
36606 scope.$on('$destroy', function() {
36607 if (scope.isOpen === true) {
36608 if (!$rootScope.$$phase) {
36609 scope.$apply(function() {
36610 scope.isOpen = false;
36616 element.unbind('keydown', inputKeydownBind);
36617 $document.unbind('click', documentClickBind);
36621 scope.getText = function(key) {
36622 return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
36625 scope.isDisabled = function(date) {
36626 if (date === 'today') {
36630 return scope.watchData.minDate && scope.compare(date, cache.minDate) < 0 ||
36631 scope.watchData.maxDate && scope.compare(date, cache.maxDate) > 0;
36634 scope.compare = function(date1, date2) {
36635 return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
36639 scope.dateSelection = function(dt) {
36640 if (angular.isDefined(dt)) {
36643 var date = scope.date ? dateFilter(scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function
36645 ngModel.$setViewValue(date);
36647 if (closeOnDateSelection) {
36648 scope.isOpen = false;
36649 element[0].focus();
36653 scope.keydown = function(evt) {
36654 if (evt.which === 27) {
36655 evt.stopPropagation();
36656 scope.isOpen = false;
36657 element[0].focus();
36661 scope.select = function(date) {
36662 if (date === 'today') {
36663 var today = new Date();
36664 if (angular.isDate(scope.date)) {
36665 date = new Date(scope.date);
36666 date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
36668 date = new Date(today.setHours(0, 0, 0, 0));
36671 scope.dateSelection(date);
36674 scope.close = function() {
36675 scope.isOpen = false;
36676 element[0].focus();
36679 scope.disabled = angular.isDefined(attrs.disabled) || false;
36680 if (attrs.ngDisabled) {
36681 scope.$parent.$watch($parse(attrs.ngDisabled), function(disabled) {
36682 scope.disabled = disabled;
36686 scope.$watch('isOpen', function(value) {
36688 if (!scope.disabled) {
36689 scope.position = appendToBody ? $position.offset(element) : $position.position(element);
36690 scope.position.top = scope.position.top + element.prop('offsetHeight');
36692 $timeout(function() {
36694 scope.$broadcast('uib:datepicker.focus');
36696 $document.bind('click', documentClickBind);
36699 scope.isOpen = false;
36702 $document.unbind('click', documentClickBind);
36706 function cameltoDash(string) {
36707 return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
36710 function parseDateString(viewValue) {
36711 var date = dateParser.parse(viewValue, dateFormat, scope.date);
36713 for (var i = 0; i < altInputFormats.length; i++) {
36714 date = dateParser.parse(viewValue, altInputFormats[i], scope.date);
36715 if (!isNaN(date)) {
36723 function parseDate(viewValue) {
36724 if (angular.isNumber(viewValue)) {
36725 // presumably timestamp to date object
36726 viewValue = new Date(viewValue);
36733 if (angular.isDate(viewValue) && !isNaN(viewValue)) {
36737 if (angular.isString(viewValue)) {
36738 var date = parseDateString(viewValue);
36739 if (!isNaN(date)) {
36740 return dateParser.toTimezone(date, ngModelOptions.timezone);
36744 return ngModel.$options && ngModel.$options.allowInvalid ? viewValue : undefined;
36747 function validator(modelValue, viewValue) {
36748 var value = modelValue || viewValue;
36750 if (!attrs.ngRequired && !value) {
36754 if (angular.isNumber(value)) {
36755 value = new Date(value);
36762 if (angular.isDate(value) && !isNaN(value)) {
36766 if (angular.isString(value)) {
36767 return !isNaN(parseDateString(viewValue));
36773 function documentClickBind(event) {
36774 if (!scope.isOpen && scope.disabled) {
36778 var popup = $popup[0];
36779 var dpContainsTarget = element[0].contains(event.target);
36780 // The popup node may not be an element node
36781 // In some browsers (IE) only element nodes have the 'contains' function
36782 var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target);
36783 if (scope.isOpen && !(dpContainsTarget || popupContainsTarget)) {
36784 scope.$apply(function() {
36785 scope.isOpen = false;
36790 function inputKeydownBind(evt) {
36791 if (evt.which === 27 && scope.isOpen) {
36792 evt.preventDefault();
36793 evt.stopPropagation();
36794 scope.$apply(function() {
36795 scope.isOpen = false;
36797 element[0].focus();
36798 } else if (evt.which === 40 && !scope.isOpen) {
36799 evt.preventDefault();
36800 evt.stopPropagation();
36801 scope.$apply(function() {
36802 scope.isOpen = true;
36808 .directive('uibDatepickerPopup', function() {
36810 require: ['ngModel', 'uibDatepickerPopup'],
36811 controller: 'UibDatepickerPopupController',
36820 link: function(scope, element, attrs, ctrls) {
36821 var ngModel = ctrls[0],
36824 ctrl.init(ngModel);
36829 .directive('uibDatepickerPopupWrap', function() {
36833 templateUrl: function(element, attrs) {
36834 return attrs.templateUrl || 'uib/template/datepicker/popup.html';
36839 angular.module('ui.bootstrap.debounce', [])
36841 * A helper, internal service that debounces a function
36843 .factory('$$debounce', ['$timeout', function($timeout) {
36844 return function(callback, debounceTime) {
36845 var timeoutPromise;
36847 return function() {
36849 var args = Array.prototype.slice.call(arguments);
36850 if (timeoutPromise) {
36851 $timeout.cancel(timeoutPromise);
36854 timeoutPromise = $timeout(function() {
36855 callback.apply(self, args);
36861 angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
36863 .constant('uibDropdownConfig', {
36864 appendToOpenClass: 'uib-dropdown-open',
36868 .service('uibDropdownService', ['$document', '$rootScope', function($document, $rootScope) {
36869 var openScope = null;
36871 this.open = function(dropdownScope) {
36873 $document.on('click', closeDropdown);
36874 $document.on('keydown', keybindFilter);
36877 if (openScope && openScope !== dropdownScope) {
36878 openScope.isOpen = false;
36881 openScope = dropdownScope;
36884 this.close = function(dropdownScope) {
36885 if (openScope === dropdownScope) {
36887 $document.off('click', closeDropdown);
36888 $document.off('keydown', keybindFilter);
36892 var closeDropdown = function(evt) {
36893 // This method may still be called during the same mouse event that
36894 // unbound this event handler. So check openScope before proceeding.
36895 if (!openScope) { return; }
36897 if (evt && openScope.getAutoClose() === 'disabled') { return; }
36899 if (evt && evt.which === 3) { return; }
36901 var toggleElement = openScope.getToggleElement();
36902 if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
36906 var dropdownElement = openScope.getDropdownElement();
36907 if (evt && openScope.getAutoClose() === 'outsideClick' &&
36908 dropdownElement && dropdownElement[0].contains(evt.target)) {
36912 openScope.isOpen = false;
36914 if (!$rootScope.$$phase) {
36915 openScope.$apply();
36919 var keybindFilter = function(evt) {
36920 if (evt.which === 27) {
36921 openScope.focusToggleElement();
36923 } else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen) {
36924 evt.preventDefault();
36925 evt.stopPropagation();
36926 openScope.focusDropdownEntry(evt.which);
36931 .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) {
36933 scope = $scope.$new(), // create a child scope so we are not polluting original one
36935 appendToOpenClass = dropdownConfig.appendToOpenClass,
36936 openClass = dropdownConfig.openClass,
36938 setIsOpen = angular.noop,
36939 toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
36940 appendToBody = false,
36942 keynavEnabled = false,
36943 selectedOption = null,
36944 body = $document.find('body');
36946 $element.addClass('dropdown');
36948 this.init = function() {
36949 if ($attrs.isOpen) {
36950 getIsOpen = $parse($attrs.isOpen);
36951 setIsOpen = getIsOpen.assign;
36953 $scope.$watch(getIsOpen, function(value) {
36954 scope.isOpen = !!value;
36958 if (angular.isDefined($attrs.dropdownAppendTo)) {
36959 var appendToEl = $parse($attrs.dropdownAppendTo)(scope);
36961 appendTo = angular.element(appendToEl);
36965 appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
36966 keynavEnabled = angular.isDefined($attrs.keyboardNav);
36968 if (appendToBody && !appendTo) {
36972 if (appendTo && self.dropdownMenu) {
36973 appendTo.append(self.dropdownMenu);
36974 $element.on('$destroy', function handleDestroyEvent() {
36975 self.dropdownMenu.remove();
36980 this.toggle = function(open) {
36981 return scope.isOpen = arguments.length ? !!open : !scope.isOpen;
36984 // Allow other directives to watch status
36985 this.isOpen = function() {
36986 return scope.isOpen;
36989 scope.getToggleElement = function() {
36990 return self.toggleElement;
36993 scope.getAutoClose = function() {
36994 return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled'
36997 scope.getElement = function() {
37001 scope.isKeynavEnabled = function() {
37002 return keynavEnabled;
37005 scope.focusDropdownEntry = function(keyCode) {
37006 var elems = self.dropdownMenu ? //If append to body is used.
37007 angular.element(self.dropdownMenu).find('a') :
37008 $element.find('ul').eq(0).find('a');
37012 if (!angular.isNumber(self.selectedOption)) {
37013 self.selectedOption = 0;
37015 self.selectedOption = self.selectedOption === elems.length - 1 ?
37016 self.selectedOption :
37017 self.selectedOption + 1;
37022 if (!angular.isNumber(self.selectedOption)) {
37023 self.selectedOption = elems.length - 1;
37025 self.selectedOption = self.selectedOption === 0 ?
37026 0 : self.selectedOption - 1;
37031 elems[self.selectedOption].focus();
37034 scope.getDropdownElement = function() {
37035 return self.dropdownMenu;
37038 scope.focusToggleElement = function() {
37039 if (self.toggleElement) {
37040 self.toggleElement[0].focus();
37044 scope.$watch('isOpen', function(isOpen, wasOpen) {
37045 if (appendTo && self.dropdownMenu) {
37046 var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true),
37051 top: pos.top + 'px',
37052 display: isOpen ? 'block' : 'none'
37055 rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
37057 css.left = pos.left + 'px';
37058 css.right = 'auto';
37061 css.right = window.innerWidth -
37062 (pos.left + $element.prop('offsetWidth')) + 'px';
37065 // Need to adjust our positioning to be relative to the appendTo container
37066 // if it's not the body element
37067 if (!appendToBody) {
37068 var appendOffset = $position.offset(appendTo);
37070 css.top = pos.top - appendOffset.top + 'px';
37073 css.left = pos.left - appendOffset.left + 'px';
37075 css.right = window.innerWidth -
37076 (pos.left - appendOffset.left + $element.prop('offsetWidth')) + 'px';
37080 self.dropdownMenu.css(css);
37083 var openContainer = appendTo ? appendTo : $element;
37085 $animate[isOpen ? 'addClass' : 'removeClass'](openContainer, appendTo ? appendToOpenClass : openClass).then(function() {
37086 if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
37087 toggleInvoker($scope, { open: !!isOpen });
37092 if (self.dropdownMenuTemplateUrl) {
37093 $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) {
37094 templateScope = scope.$new();
37095 $compile(tplContent.trim())(templateScope, function(dropdownElement) {
37096 var newEl = dropdownElement;
37097 self.dropdownMenu.replaceWith(newEl);
37098 self.dropdownMenu = newEl;
37103 scope.focusToggleElement();
37104 uibDropdownService.open(scope);
37106 if (self.dropdownMenuTemplateUrl) {
37107 if (templateScope) {
37108 templateScope.$destroy();
37110 var newEl = angular.element('<ul class="dropdown-menu"></ul>');
37111 self.dropdownMenu.replaceWith(newEl);
37112 self.dropdownMenu = newEl;
37115 uibDropdownService.close(scope);
37116 self.selectedOption = null;
37119 if (angular.isFunction(setIsOpen)) {
37120 setIsOpen($scope, isOpen);
37124 $scope.$on('$locationChangeSuccess', function() {
37125 if (scope.getAutoClose() !== 'disabled') {
37126 scope.isOpen = false;
37131 .directive('uibDropdown', function() {
37133 controller: 'UibDropdownController',
37134 link: function(scope, element, attrs, dropdownCtrl) {
37135 dropdownCtrl.init();
37140 .directive('uibDropdownMenu', function() {
37143 require: '?^uibDropdown',
37144 link: function(scope, element, attrs, dropdownCtrl) {
37145 if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {
37149 element.addClass('dropdown-menu');
37151 var tplUrl = attrs.templateUrl;
37153 dropdownCtrl.dropdownMenuTemplateUrl = tplUrl;
37156 if (!dropdownCtrl.dropdownMenu) {
37157 dropdownCtrl.dropdownMenu = element;
37163 .directive('uibDropdownToggle', function() {
37165 require: '?^uibDropdown',
37166 link: function(scope, element, attrs, dropdownCtrl) {
37167 if (!dropdownCtrl) {
37171 element.addClass('dropdown-toggle');
37173 dropdownCtrl.toggleElement = element;
37175 var toggleDropdown = function(event) {
37176 event.preventDefault();
37178 if (!element.hasClass('disabled') && !attrs.disabled) {
37179 scope.$apply(function() {
37180 dropdownCtrl.toggle();
37185 element.bind('click', toggleDropdown);
37188 element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
37189 scope.$watch(dropdownCtrl.isOpen, function(isOpen) {
37190 element.attr('aria-expanded', !!isOpen);
37193 scope.$on('$destroy', function() {
37194 element.unbind('click', toggleDropdown);
37200 angular.module('ui.bootstrap.stackedMap', [])
37202 * A helper, internal data structure that acts as a map but also allows getting / removing
37203 * elements in the LIFO order
37205 .factory('$$stackedMap', function() {
37207 createNew: function() {
37211 add: function(key, value) {
37217 get: function(key) {
37218 for (var i = 0; i < stack.length; i++) {
37219 if (key === stack[i].key) {
37226 for (var i = 0; i < stack.length; i++) {
37227 keys.push(stack[i].key);
37232 return stack[stack.length - 1];
37234 remove: function(key) {
37236 for (var i = 0; i < stack.length; i++) {
37237 if (key === stack[i].key) {
37242 return stack.splice(idx, 1)[0];
37244 removeTop: function() {
37245 return stack.splice(stack.length - 1, 1)[0];
37247 length: function() {
37248 return stack.length;
37254 angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
37256 * A helper, internal data structure that stores all references attached to key
37258 .factory('$$multiMap', function() {
37260 createNew: function() {
37264 entries: function() {
37265 return Object.keys(map).map(function(key) {
37272 get: function(key) {
37275 hasKey: function(key) {
37279 return Object.keys(map);
37281 put: function(key, value) {
37286 map[key].push(value);
37288 remove: function(key, value) {
37289 var values = map[key];
37295 var idx = values.indexOf(value);
37298 values.splice(idx, 1);
37301 if (!values.length) {
37311 * Pluggable resolve mechanism for the modal resolve resolution
37312 * Supports UI Router's $resolve service
37314 .provider('$uibResolve', function() {
37315 var resolve = this;
37316 this.resolver = null;
37318 this.setResolver = function(resolver) {
37319 this.resolver = resolver;
37322 this.$get = ['$injector', '$q', function($injector, $q) {
37323 var resolver = resolve.resolver ? $injector.get(resolve.resolver) : null;
37325 resolve: function(invocables, locals, parent, self) {
37327 return resolver.resolve(invocables, locals, parent, self);
37332 angular.forEach(invocables, function(value) {
37333 if (angular.isFunction(value) || angular.isArray(value)) {
37334 promises.push($q.resolve($injector.invoke(value)));
37335 } else if (angular.isString(value)) {
37336 promises.push($q.resolve($injector.get(value)));
37338 promises.push($q.resolve(value));
37342 return $q.all(promises).then(function(resolves) {
37343 var resolveObj = {};
37344 var resolveIter = 0;
37345 angular.forEach(invocables, function(value, key) {
37346 resolveObj[key] = resolves[resolveIter++];
37357 * A helper directive for the $modal service. It creates a backdrop element.
37359 .directive('uibModalBackdrop', ['$animateCss', '$injector', '$uibModalStack',
37360 function($animateCss, $injector, $modalStack) {
37363 templateUrl: 'uib/template/modal/backdrop.html',
37364 compile: function(tElement, tAttrs) {
37365 tElement.addClass(tAttrs.backdropClass);
37370 function linkFn(scope, element, attrs) {
37371 if (attrs.modalInClass) {
37372 $animateCss(element, {
37373 addClass: attrs.modalInClass
37376 scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
37377 var done = setIsAsync();
37378 if (scope.modalOptions.animation) {
37379 $animateCss(element, {
37380 removeClass: attrs.modalInClass
37381 }).start().then(done);
37390 .directive('uibModalWindow', ['$uibModalStack', '$q', '$animate', '$animateCss', '$document',
37391 function($modalStack, $q, $animate, $animateCss, $document) {
37398 templateUrl: function(tElement, tAttrs) {
37399 return tAttrs.templateUrl || 'uib/template/modal/window.html';
37401 link: function(scope, element, attrs) {
37402 element.addClass(attrs.windowClass || '');
37403 element.addClass(attrs.windowTopClass || '');
37404 scope.size = attrs.size;
37406 scope.close = function(evt) {
37407 var modal = $modalStack.getTop();
37408 if (modal && modal.value.backdrop &&
37409 modal.value.backdrop !== 'static' &&
37410 evt.target === evt.currentTarget) {
37411 evt.preventDefault();
37412 evt.stopPropagation();
37413 $modalStack.dismiss(modal.key, 'backdrop click');
37417 // moved from template to fix issue #2280
37418 element.on('click', scope.close);
37420 // This property is only added to the scope for the purpose of detecting when this directive is rendered.
37421 // We can detect that by using this property in the template associated with this directive and then use
37422 // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
37423 scope.$isRendered = true;
37425 // Deferred object that will be resolved when this modal is render.
37426 var modalRenderDeferObj = $q.defer();
37427 // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready.
37428 // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template.
37429 attrs.$observe('modalRender', function(value) {
37430 if (value === 'true') {
37431 modalRenderDeferObj.resolve();
37435 modalRenderDeferObj.promise.then(function() {
37436 var animationPromise = null;
37438 if (attrs.modalInClass) {
37439 animationPromise = $animateCss(element, {
37440 addClass: attrs.modalInClass
37443 scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
37444 var done = setIsAsync();
37446 $animateCss(element, {
37447 removeClass: attrs.modalInClass
37448 }).start().then(done);
37450 $animate.removeClass(element, attrs.modalInClass).then(done);
37456 $q.when(animationPromise).then(function() {
37458 * If something within the freshly-opened modal already has focus (perhaps via a
37459 * directive that causes focus). then no need to try and focus anything.
37461 if (!($document[0].activeElement && element[0].contains($document[0].activeElement))) {
37462 var inputWithAutofocus = element[0].querySelector('[autofocus]');
37464 * Auto-focusing of a freshly-opened modal element causes any child elements
37465 * with the autofocus attribute to lose focus. This is an issue on touch
37466 * based devices which will show and then hide the onscreen keyboard.
37467 * Attempts to refocus the autofocus element via JavaScript will not reopen
37468 * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
37469 * the modal element if the modal does not contain an autofocus element.
37471 if (inputWithAutofocus) {
37472 inputWithAutofocus.focus();
37474 element[0].focus();
37479 // Notify {@link $modalStack} that modal is rendered.
37480 var modal = $modalStack.getTop();
37482 $modalStack.modalRendered(modal.key);
37489 .directive('uibModalAnimationClass', function() {
37491 compile: function(tElement, tAttrs) {
37492 if (tAttrs.modalAnimation) {
37493 tElement.addClass(tAttrs.uibModalAnimationClass);
37499 .directive('uibModalTransclude', function() {
37501 link: function(scope, element, attrs, controller, transclude) {
37502 transclude(scope.$parent, function(clone) {
37504 element.append(clone);
37510 .factory('$uibModalStack', ['$animate', '$animateCss', '$document',
37511 '$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap',
37512 function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap) {
37513 var OPENED_MODAL_CLASS = 'modal-open';
37515 var backdropDomEl, backdropScope;
37516 var openedWindows = $$stackedMap.createNew();
37517 var openedClasses = $$multiMap.createNew();
37518 var $modalStack = {
37519 NOW_CLOSING_EVENT: 'modal.stack.now-closing'
37522 //Modal focus behavior
37523 var focusableElementList;
37524 var focusIndex = 0;
37525 var tababbleSelector = 'a[href], area[href], input:not([disabled]), ' +
37526 'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' +
37527 'iframe, object, embed, *[tabindex], *[contenteditable=true]';
37529 function backdropIndex() {
37530 var topBackdropIndex = -1;
37531 var opened = openedWindows.keys();
37532 for (var i = 0; i < opened.length; i++) {
37533 if (openedWindows.get(opened[i]).value.backdrop) {
37534 topBackdropIndex = i;
37537 return topBackdropIndex;
37540 $rootScope.$watch(backdropIndex, function(newBackdropIndex) {
37541 if (backdropScope) {
37542 backdropScope.index = newBackdropIndex;
37546 function removeModalWindow(modalInstance, elementToReceiveFocus) {
37547 var modalWindow = openedWindows.get(modalInstance).value;
37548 var appendToElement = modalWindow.appendTo;
37550 //clean up the stack
37551 openedWindows.remove(modalInstance);
37553 removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() {
37554 var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS;
37555 openedClasses.remove(modalBodyClass, modalInstance);
37556 appendToElement.toggleClass(modalBodyClass, openedClasses.hasKey(modalBodyClass));
37557 toggleTopWindowClass(true);
37559 checkRemoveBackdrop();
37561 //move focus to specified element if available, or else to body
37562 if (elementToReceiveFocus && elementToReceiveFocus.focus) {
37563 elementToReceiveFocus.focus();
37564 } else if (appendToElement.focus) {
37565 appendToElement.focus();
37569 // Add or remove "windowTopClass" from the top window in the stack
37570 function toggleTopWindowClass(toggleSwitch) {
37573 if (openedWindows.length() > 0) {
37574 modalWindow = openedWindows.top().value;
37575 modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch);
37579 function checkRemoveBackdrop() {
37580 //remove backdrop if no longer needed
37581 if (backdropDomEl && backdropIndex() === -1) {
37582 var backdropScopeRef = backdropScope;
37583 removeAfterAnimate(backdropDomEl, backdropScope, function() {
37584 backdropScopeRef = null;
37586 backdropDomEl = undefined;
37587 backdropScope = undefined;
37591 function removeAfterAnimate(domEl, scope, done, closedDeferred) {
37593 var asyncPromise = null;
37594 var setIsAsync = function() {
37595 if (!asyncDeferred) {
37596 asyncDeferred = $q.defer();
37597 asyncPromise = asyncDeferred.promise;
37600 return function asyncDone() {
37601 asyncDeferred.resolve();
37604 scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync);
37606 // Note that it's intentional that asyncPromise might be null.
37607 // That's when setIsAsync has not been called during the
37608 // NOW_CLOSING_EVENT broadcast.
37609 return $q.when(asyncPromise).then(afterAnimating);
37611 function afterAnimating() {
37612 if (afterAnimating.done) {
37615 afterAnimating.done = true;
37617 $animateCss(domEl, {
37619 }).start().then(function() {
37621 if (closedDeferred) {
37622 closedDeferred.resolve();
37633 $document.on('keydown', keydownListener);
37635 $rootScope.$on('$destroy', function() {
37636 $document.off('keydown', keydownListener);
37639 function keydownListener(evt) {
37640 if (evt.isDefaultPrevented()) {
37644 var modal = openedWindows.top();
37646 switch (evt.which) {
37648 if (modal.value.keyboard) {
37649 evt.preventDefault();
37650 $rootScope.$apply(function() {
37651 $modalStack.dismiss(modal.key, 'escape key press');
37657 $modalStack.loadFocusElementList(modal);
37658 var focusChanged = false;
37659 if (evt.shiftKey) {
37660 if ($modalStack.isFocusInFirstItem(evt)) {
37661 focusChanged = $modalStack.focusLastFocusableElement();
37664 if ($modalStack.isFocusInLastItem(evt)) {
37665 focusChanged = $modalStack.focusFirstFocusableElement();
37669 if (focusChanged) {
37670 evt.preventDefault();
37671 evt.stopPropagation();
37679 $modalStack.open = function(modalInstance, modal) {
37680 var modalOpener = $document[0].activeElement,
37681 modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS;
37683 toggleTopWindowClass(false);
37685 openedWindows.add(modalInstance, {
37686 deferred: modal.deferred,
37687 renderDeferred: modal.renderDeferred,
37688 closedDeferred: modal.closedDeferred,
37689 modalScope: modal.scope,
37690 backdrop: modal.backdrop,
37691 keyboard: modal.keyboard,
37692 openedClass: modal.openedClass,
37693 windowTopClass: modal.windowTopClass,
37694 animation: modal.animation,
37695 appendTo: modal.appendTo
37698 openedClasses.put(modalBodyClass, modalInstance);
37700 var appendToElement = modal.appendTo,
37701 currBackdropIndex = backdropIndex();
37703 if (!appendToElement.length) {
37704 throw new Error('appendTo element not found. Make sure that the element passed is in DOM.');
37707 if (currBackdropIndex >= 0 && !backdropDomEl) {
37708 backdropScope = $rootScope.$new(true);
37709 backdropScope.modalOptions = modal;
37710 backdropScope.index = currBackdropIndex;
37711 backdropDomEl = angular.element('<div uib-modal-backdrop="modal-backdrop"></div>');
37712 backdropDomEl.attr('backdrop-class', modal.backdropClass);
37713 if (modal.animation) {
37714 backdropDomEl.attr('modal-animation', 'true');
37716 $compile(backdropDomEl)(backdropScope);
37717 $animate.enter(backdropDomEl, appendToElement);
37720 var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>');
37721 angularDomEl.attr({
37722 'template-url': modal.windowTemplateUrl,
37723 'window-class': modal.windowClass,
37724 'window-top-class': modal.windowTopClass,
37725 'size': modal.size,
37726 'index': openedWindows.length() - 1,
37727 'animate': 'animate'
37728 }).html(modal.content);
37729 if (modal.animation) {
37730 angularDomEl.attr('modal-animation', 'true');
37733 $animate.enter(angularDomEl, appendToElement)
37735 $compile(angularDomEl)(modal.scope);
37736 $animate.addClass(appendToElement, modalBodyClass);
37739 openedWindows.top().value.modalDomEl = angularDomEl;
37740 openedWindows.top().value.modalOpener = modalOpener;
37742 $modalStack.clearFocusListCache();
37745 function broadcastClosing(modalWindow, resultOrReason, closing) {
37746 return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented;
37749 $modalStack.close = function(modalInstance, result) {
37750 var modalWindow = openedWindows.get(modalInstance);
37751 if (modalWindow && broadcastClosing(modalWindow, result, true)) {
37752 modalWindow.value.modalScope.$$uibDestructionScheduled = true;
37753 modalWindow.value.deferred.resolve(result);
37754 removeModalWindow(modalInstance, modalWindow.value.modalOpener);
37757 return !modalWindow;
37760 $modalStack.dismiss = function(modalInstance, reason) {
37761 var modalWindow = openedWindows.get(modalInstance);
37762 if (modalWindow && broadcastClosing(modalWindow, reason, false)) {
37763 modalWindow.value.modalScope.$$uibDestructionScheduled = true;
37764 modalWindow.value.deferred.reject(reason);
37765 removeModalWindow(modalInstance, modalWindow.value.modalOpener);
37768 return !modalWindow;
37771 $modalStack.dismissAll = function(reason) {
37772 var topModal = this.getTop();
37773 while (topModal && this.dismiss(topModal.key, reason)) {
37774 topModal = this.getTop();
37778 $modalStack.getTop = function() {
37779 return openedWindows.top();
37782 $modalStack.modalRendered = function(modalInstance) {
37783 var modalWindow = openedWindows.get(modalInstance);
37785 modalWindow.value.renderDeferred.resolve();
37789 $modalStack.focusFirstFocusableElement = function() {
37790 if (focusableElementList.length > 0) {
37791 focusableElementList[0].focus();
37796 $modalStack.focusLastFocusableElement = function() {
37797 if (focusableElementList.length > 0) {
37798 focusableElementList[focusableElementList.length - 1].focus();
37804 $modalStack.isFocusInFirstItem = function(evt) {
37805 if (focusableElementList.length > 0) {
37806 return (evt.target || evt.srcElement) === focusableElementList[0];
37811 $modalStack.isFocusInLastItem = function(evt) {
37812 if (focusableElementList.length > 0) {
37813 return (evt.target || evt.srcElement) === focusableElementList[focusableElementList.length - 1];
37818 $modalStack.clearFocusListCache = function() {
37819 focusableElementList = [];
37823 $modalStack.loadFocusElementList = function(modalWindow) {
37824 if (focusableElementList === undefined || !focusableElementList.length) {
37826 var modalDomE1 = modalWindow.value.modalDomEl;
37827 if (modalDomE1 && modalDomE1.length) {
37828 focusableElementList = modalDomE1[0].querySelectorAll(tababbleSelector);
37834 return $modalStack;
37837 .provider('$uibModal', function() {
37838 var $modalProvider = {
37841 backdrop: true, //can also be false or 'static'
37844 $get: ['$rootScope', '$q', '$document', '$templateRequest', '$controller', '$uibResolve', '$uibModalStack',
37845 function ($rootScope, $q, $document, $templateRequest, $controller, $uibResolve, $modalStack) {
37848 function getTemplatePromise(options) {
37849 return options.template ? $q.when(options.template) :
37850 $templateRequest(angular.isFunction(options.templateUrl) ?
37851 options.templateUrl() : options.templateUrl);
37854 var promiseChain = null;
37855 $modal.getPromiseChain = function() {
37856 return promiseChain;
37859 $modal.open = function(modalOptions) {
37860 var modalResultDeferred = $q.defer();
37861 var modalOpenedDeferred = $q.defer();
37862 var modalClosedDeferred = $q.defer();
37863 var modalRenderDeferred = $q.defer();
37865 //prepare an instance of a modal to be injected into controllers and returned to a caller
37866 var modalInstance = {
37867 result: modalResultDeferred.promise,
37868 opened: modalOpenedDeferred.promise,
37869 closed: modalClosedDeferred.promise,
37870 rendered: modalRenderDeferred.promise,
37871 close: function (result) {
37872 return $modalStack.close(modalInstance, result);
37874 dismiss: function (reason) {
37875 return $modalStack.dismiss(modalInstance, reason);
37879 //merge and clean up options
37880 modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
37881 modalOptions.resolve = modalOptions.resolve || {};
37882 modalOptions.appendTo = modalOptions.appendTo || $document.find('body').eq(0);
37885 if (!modalOptions.template && !modalOptions.templateUrl) {
37886 throw new Error('One of template or templateUrl options is required.');
37889 var templateAndResolvePromise =
37890 $q.all([getTemplatePromise(modalOptions), $uibResolve.resolve(modalOptions.resolve, {}, null, null)]);
37892 function resolveWithTemplate() {
37893 return templateAndResolvePromise;
37896 // Wait for the resolution of the existing promise chain.
37897 // Then switch to our own combined promise dependency (regardless of how the previous modal fared).
37898 // Then add to $modalStack and resolve opened.
37899 // Finally clean up the chain variable if no subsequent modal has overwritten it.
37901 samePromise = promiseChain = $q.all([promiseChain])
37902 .then(resolveWithTemplate, resolveWithTemplate)
37903 .then(function resolveSuccess(tplAndVars) {
37904 var providedScope = modalOptions.scope || $rootScope;
37906 var modalScope = providedScope.$new();
37907 modalScope.$close = modalInstance.close;
37908 modalScope.$dismiss = modalInstance.dismiss;
37910 modalScope.$on('$destroy', function() {
37911 if (!modalScope.$$uibDestructionScheduled) {
37912 modalScope.$dismiss('$uibUnscheduledDestruction');
37916 var ctrlInstance, ctrlLocals = {};
37919 if (modalOptions.controller) {
37920 ctrlLocals.$scope = modalScope;
37921 ctrlLocals.$uibModalInstance = modalInstance;
37922 angular.forEach(tplAndVars[1], function(value, key) {
37923 ctrlLocals[key] = value;
37926 ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
37927 if (modalOptions.controllerAs) {
37928 if (modalOptions.bindToController) {
37929 ctrlInstance.$close = modalScope.$close;
37930 ctrlInstance.$dismiss = modalScope.$dismiss;
37931 angular.extend(ctrlInstance, providedScope);
37934 modalScope[modalOptions.controllerAs] = ctrlInstance;
37938 $modalStack.open(modalInstance, {
37940 deferred: modalResultDeferred,
37941 renderDeferred: modalRenderDeferred,
37942 closedDeferred: modalClosedDeferred,
37943 content: tplAndVars[0],
37944 animation: modalOptions.animation,
37945 backdrop: modalOptions.backdrop,
37946 keyboard: modalOptions.keyboard,
37947 backdropClass: modalOptions.backdropClass,
37948 windowTopClass: modalOptions.windowTopClass,
37949 windowClass: modalOptions.windowClass,
37950 windowTemplateUrl: modalOptions.windowTemplateUrl,
37951 size: modalOptions.size,
37952 openedClass: modalOptions.openedClass,
37953 appendTo: modalOptions.appendTo
37955 modalOpenedDeferred.resolve(true);
37957 }, function resolveError(reason) {
37958 modalOpenedDeferred.reject(reason);
37959 modalResultDeferred.reject(reason);
37960 })['finally'](function() {
37961 if (promiseChain === samePromise) {
37962 promiseChain = null;
37966 return modalInstance;
37974 return $modalProvider;
37977 angular.module('ui.bootstrap.paging', [])
37979 * Helper internal service for generating common controller code between the
37980 * pager and pagination components
37982 .factory('uibPaging', ['$parse', function($parse) {
37984 create: function(ctrl, $scope, $attrs) {
37985 ctrl.setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
37986 ctrl.ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl
37988 ctrl.init = function(ngModelCtrl, config) {
37989 ctrl.ngModelCtrl = ngModelCtrl;
37990 ctrl.config = config;
37992 ngModelCtrl.$render = function() {
37996 if ($attrs.itemsPerPage) {
37997 $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) {
37998 ctrl.itemsPerPage = parseInt(value, 10);
37999 $scope.totalPages = ctrl.calculateTotalPages();
38003 ctrl.itemsPerPage = config.itemsPerPage;
38006 $scope.$watch('totalItems', function(newTotal, oldTotal) {
38007 if (angular.isDefined(newTotal) || newTotal !== oldTotal) {
38008 $scope.totalPages = ctrl.calculateTotalPages();
38014 ctrl.calculateTotalPages = function() {
38015 var totalPages = ctrl.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / ctrl.itemsPerPage);
38016 return Math.max(totalPages || 0, 1);
38019 ctrl.render = function() {
38020 $scope.page = parseInt(ctrl.ngModelCtrl.$viewValue, 10) || 1;
38023 $scope.selectPage = function(page, evt) {
38025 evt.preventDefault();
38028 var clickAllowed = !$scope.ngDisabled || !evt;
38029 if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) {
38030 if (evt && evt.target) {
38033 ctrl.ngModelCtrl.$setViewValue(page);
38034 ctrl.ngModelCtrl.$render();
38038 $scope.getText = function(key) {
38039 return $scope[key + 'Text'] || ctrl.config[key + 'Text'];
38042 $scope.noPrevious = function() {
38043 return $scope.page === 1;
38046 $scope.noNext = function() {
38047 return $scope.page === $scope.totalPages;
38050 ctrl.updatePage = function() {
38051 ctrl.setNumPages($scope.$parent, $scope.totalPages); // Readonly variable
38053 if ($scope.page > $scope.totalPages) {
38054 $scope.selectPage($scope.totalPages);
38056 ctrl.ngModelCtrl.$render();
38063 angular.module('ui.bootstrap.pager', ['ui.bootstrap.paging'])
38065 .controller('UibPagerController', ['$scope', '$attrs', 'uibPaging', 'uibPagerConfig', function($scope, $attrs, uibPaging, uibPagerConfig) {
38066 $scope.align = angular.isDefined($attrs.align) ? $scope.$parent.$eval($attrs.align) : uibPagerConfig.align;
38068 uibPaging.create(this, $scope, $attrs);
38071 .constant('uibPagerConfig', {
38073 previousText: '« Previous',
38074 nextText: 'Next »',
38078 .directive('uibPager', ['uibPagerConfig', function(uibPagerConfig) {
38086 require: ['uibPager', '?ngModel'],
38087 controller: 'UibPagerController',
38088 controllerAs: 'pager',
38089 templateUrl: function(element, attrs) {
38090 return attrs.templateUrl || 'uib/template/pager/pager.html';
38093 link: function(scope, element, attrs, ctrls) {
38094 var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
38096 if (!ngModelCtrl) {
38097 return; // do nothing if no ng-model
38100 paginationCtrl.init(ngModelCtrl, uibPagerConfig);
38105 angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging'])
38106 .controller('UibPaginationController', ['$scope', '$attrs', '$parse', 'uibPaging', 'uibPaginationConfig', function($scope, $attrs, $parse, uibPaging, uibPaginationConfig) {
38108 // Setup configuration parameters
38109 var maxSize = angular.isDefined($attrs.maxSize) ? $scope.$parent.$eval($attrs.maxSize) : uibPaginationConfig.maxSize,
38110 rotate = angular.isDefined($attrs.rotate) ? $scope.$parent.$eval($attrs.rotate) : uibPaginationConfig.rotate,
38111 forceEllipses = angular.isDefined($attrs.forceEllipses) ? $scope.$parent.$eval($attrs.forceEllipses) : uibPaginationConfig.forceEllipses,
38112 boundaryLinkNumbers = angular.isDefined($attrs.boundaryLinkNumbers) ? $scope.$parent.$eval($attrs.boundaryLinkNumbers) : uibPaginationConfig.boundaryLinkNumbers;
38113 $scope.boundaryLinks = angular.isDefined($attrs.boundaryLinks) ? $scope.$parent.$eval($attrs.boundaryLinks) : uibPaginationConfig.boundaryLinks;
38114 $scope.directionLinks = angular.isDefined($attrs.directionLinks) ? $scope.$parent.$eval($attrs.directionLinks) : uibPaginationConfig.directionLinks;
38116 uibPaging.create(this, $scope, $attrs);
38118 if ($attrs.maxSize) {
38119 $scope.$parent.$watch($parse($attrs.maxSize), function(value) {
38120 maxSize = parseInt(value, 10);
38125 // Create page object used in template
38126 function makePage(number, text, isActive) {
38134 function getPages(currentPage, totalPages) {
38137 // Default page limits
38138 var startPage = 1, endPage = totalPages;
38139 var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages;
38141 // recompute if maxSize
38144 // Current page is displayed in the middle of the visible ones
38145 startPage = Math.max(currentPage - Math.floor(maxSize / 2), 1);
38146 endPage = startPage + maxSize - 1;
38148 // Adjust if limit is exceeded
38149 if (endPage > totalPages) {
38150 endPage = totalPages;
38151 startPage = endPage - maxSize + 1;
38154 // Visible pages are paginated with maxSize
38155 startPage = (Math.ceil(currentPage / maxSize) - 1) * maxSize + 1;
38157 // Adjust last page if limit is exceeded
38158 endPage = Math.min(startPage + maxSize - 1, totalPages);
38162 // Add page number links
38163 for (var number = startPage; number <= endPage; number++) {
38164 var page = makePage(number, number, number === currentPage);
38168 // Add links to move between page sets
38169 if (isMaxSized && maxSize > 0 && (!rotate || forceEllipses || boundaryLinkNumbers)) {
38170 if (startPage > 1) {
38171 if (!boundaryLinkNumbers || startPage > 3) { //need ellipsis for all options unless range is too close to beginning
38172 var previousPageSet = makePage(startPage - 1, '...', false);
38173 pages.unshift(previousPageSet);
38175 if (boundaryLinkNumbers) {
38176 if (startPage === 3) { //need to replace ellipsis when the buttons would be sequential
38177 var secondPageLink = makePage(2, '2', false);
38178 pages.unshift(secondPageLink);
38180 //add the first page
38181 var firstPageLink = makePage(1, '1', false);
38182 pages.unshift(firstPageLink);
38186 if (endPage < totalPages) {
38187 if (!boundaryLinkNumbers || endPage < totalPages - 2) { //need ellipsis for all options unless range is too close to end
38188 var nextPageSet = makePage(endPage + 1, '...', false);
38189 pages.push(nextPageSet);
38191 if (boundaryLinkNumbers) {
38192 if (endPage === totalPages - 2) { //need to replace ellipsis when the buttons would be sequential
38193 var secondToLastPageLink = makePage(totalPages - 1, totalPages - 1, false);
38194 pages.push(secondToLastPageLink);
38196 //add the last page
38197 var lastPageLink = makePage(totalPages, totalPages, false);
38198 pages.push(lastPageLink);
38205 var originalRender = this.render;
38206 this.render = function() {
38208 if ($scope.page > 0 && $scope.page <= $scope.totalPages) {
38209 $scope.pages = getPages($scope.page, $scope.totalPages);
38214 .constant('uibPaginationConfig', {
38216 boundaryLinks: false,
38217 boundaryLinkNumbers: false,
38218 directionLinks: true,
38219 firstText: 'First',
38220 previousText: 'Previous',
38224 forceEllipses: false
38227 .directive('uibPagination', ['$parse', 'uibPaginationConfig', function($parse, uibPaginationConfig) {
38237 require: ['uibPagination', '?ngModel'],
38238 controller: 'UibPaginationController',
38239 controllerAs: 'pagination',
38240 templateUrl: function(element, attrs) {
38241 return attrs.templateUrl || 'uib/template/pagination/pagination.html';
38244 link: function(scope, element, attrs, ctrls) {
38245 var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
38247 if (!ngModelCtrl) {
38248 return; // do nothing if no ng-model
38251 paginationCtrl.init(ngModelCtrl, uibPaginationConfig);
38257 * The following features are still outstanding: animation as a
38258 * function, placement as a function, inside, support for more triggers than
38259 * just mouse enter/leave, html tooltips, and selector delegation.
38261 angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.stackedMap'])
38264 * The $tooltip service creates tooltip- and popover-like directives as well as
38265 * houses global options for them.
38267 .provider('$uibTooltip', function() {
38268 // The default options tooltip and popover.
38269 var defaultOptions = {
38271 placementClassPrefix: '',
38274 popupCloseDelay: 0,
38275 useContentExp: false
38278 // Default hide triggers for each show trigger
38280 'mouseenter': 'mouseleave',
38282 'outsideClick': 'outsideClick',
38287 // The options specified to the provider globally.
38288 var globalOptions = {};
38291 * `options({})` allows global configuration of all tooltips in the
38294 * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
38295 * // place tooltips left instead of top by default
38296 * $tooltipProvider.options( { placement: 'left' } );
38299 this.options = function(value) {
38300 angular.extend(globalOptions, value);
38304 * This allows you to extend the set of trigger mappings available. E.g.:
38306 * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
38308 this.setTriggers = function setTriggers(triggers) {
38309 angular.extend(triggerMap, triggers);
38313 * This is a helper function for translating camel-case to snake_case.
38315 function snake_case(name) {
38316 var regexp = /[A-Z]/g;
38317 var separator = '-';
38318 return name.replace(regexp, function(letter, pos) {
38319 return (pos ? separator : '') + letter.toLowerCase();
38324 * Returns the actual instance of the $tooltip service.
38325 * TODO support multiple triggers
38327 this.$get = ['$window', '$compile', '$timeout', '$document', '$uibPosition', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) {
38328 var openedTooltips = $$stackedMap.createNew();
38329 $document.on('keypress', keypressListener);
38331 $rootScope.$on('$destroy', function() {
38332 $document.off('keypress', keypressListener);
38335 function keypressListener(e) {
38336 if (e.which === 27) {
38337 var last = openedTooltips.top();
38339 last.value.close();
38340 openedTooltips.removeTop();
38346 return function $tooltip(ttType, prefix, defaultTriggerShow, options) {
38347 options = angular.extend({}, defaultOptions, globalOptions, options);
38350 * Returns an object of show and hide triggers.
38352 * If a trigger is supplied,
38353 * it is used to show the tooltip; otherwise, it will use the `trigger`
38354 * option passed to the `$tooltipProvider.options` method; else it will
38355 * default to the trigger supplied to this directive factory.
38357 * The hide trigger is based on the show trigger. If the `trigger` option
38358 * was passed to the `$tooltipProvider.options` method, it will use the
38359 * mapped trigger from `triggerMap` or the passed trigger if the map is
38360 * undefined; otherwise, it uses the `triggerMap` value of the show
38361 * trigger; else it will just use the show trigger.
38363 function getTriggers(trigger) {
38364 var show = (trigger || options.trigger || defaultTriggerShow).split(' ');
38365 var hide = show.map(function(trigger) {
38366 return triggerMap[trigger] || trigger;
38374 var directiveName = snake_case(ttType);
38376 var startSym = $interpolate.startSymbol();
38377 var endSym = $interpolate.endSymbol();
38379 '<div '+ directiveName + '-popup '+
38380 'title="' + startSym + 'title' + endSym + '" '+
38381 (options.useContentExp ?
38382 'content-exp="contentExp()" ' :
38383 'content="' + startSym + 'content' + endSym + '" ') +
38384 'placement="' + startSym + 'placement' + endSym + '" '+
38385 'popup-class="' + startSym + 'popupClass' + endSym + '" '+
38386 'animation="animation" ' +
38387 'is-open="isOpen"' +
38388 'origin-scope="origScope" ' +
38389 'style="visibility: hidden; display: block; top: -9999px; left: -9999px;"' +
38394 compile: function(tElem, tAttrs) {
38395 var tooltipLinker = $compile(template);
38397 return function link(scope, element, attrs, tooltipCtrl) {
38399 var tooltipLinkedScope;
38400 var transitionTimeout;
38403 var positionTimeout;
38404 var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false;
38405 var triggers = getTriggers(undefined);
38406 var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']);
38407 var ttScope = scope.$new(true);
38408 var repositionScheduled = false;
38409 var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false;
38410 var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false;
38411 var observers = [];
38413 var positionTooltip = function() {
38414 // check if tooltip exists and is not empty
38415 if (!tooltip || !tooltip.html()) { return; }
38417 if (!positionTimeout) {
38418 positionTimeout = $timeout(function() {
38419 // Reset the positioning.
38420 tooltip.css({ top: 0, left: 0 });
38422 // Now set the calculated positioning.
38423 var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);
38424 tooltip.css({ top: ttPosition.top + 'px', left: ttPosition.left + 'px', visibility: 'visible' });
38426 // If the placement class is prefixed, still need
38427 // to remove the TWBS standard class.
38428 if (options.placementClassPrefix) {
38429 tooltip.removeClass('top bottom left right');
38432 tooltip.removeClass(
38433 options.placementClassPrefix + 'top ' +
38434 options.placementClassPrefix + 'top-left ' +
38435 options.placementClassPrefix + 'top-right ' +
38436 options.placementClassPrefix + 'bottom ' +
38437 options.placementClassPrefix + 'bottom-left ' +
38438 options.placementClassPrefix + 'bottom-right ' +
38439 options.placementClassPrefix + 'left ' +
38440 options.placementClassPrefix + 'left-top ' +
38441 options.placementClassPrefix + 'left-bottom ' +
38442 options.placementClassPrefix + 'right ' +
38443 options.placementClassPrefix + 'right-top ' +
38444 options.placementClassPrefix + 'right-bottom');
38446 var placement = ttPosition.placement.split('-');
38447 tooltip.addClass(placement[0], options.placementClassPrefix + ttPosition.placement);
38448 $position.positionArrow(tooltip, ttPosition.placement);
38450 positionTimeout = null;
38455 // Set up the correct scope to allow transclusion later
38456 ttScope.origScope = scope;
38458 // By default, the tooltip is not open.
38459 // TODO add ability to start tooltip opened
38460 ttScope.isOpen = false;
38461 openedTooltips.add(ttScope, {
38465 function toggleTooltipBind() {
38466 if (!ttScope.isOpen) {
38473 // Show the tooltip with delay if specified, otherwise show it immediately
38474 function showTooltipBind() {
38475 if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) {
38482 if (ttScope.popupDelay) {
38483 // Do nothing if the tooltip was already scheduled to pop-up.
38484 // This happens if show is triggered multiple times before any hide is triggered.
38485 if (!showTimeout) {
38486 showTimeout = $timeout(show, ttScope.popupDelay, false);
38493 function hideTooltipBind() {
38496 if (ttScope.popupCloseDelay) {
38497 if (!hideTimeout) {
38498 hideTimeout = $timeout(hide, ttScope.popupCloseDelay, false);
38505 // Show the tooltip popup element.
38510 // Don't show empty tooltips.
38511 if (!ttScope.content) {
38512 return angular.noop;
38517 // And show the tooltip.
38518 ttScope.$evalAsync(function() {
38519 ttScope.isOpen = true;
38520 assignIsOpen(true);
38525 function cancelShow() {
38527 $timeout.cancel(showTimeout);
38528 showTimeout = null;
38531 if (positionTimeout) {
38532 $timeout.cancel(positionTimeout);
38533 positionTimeout = null;
38537 // Hide the tooltip popup element.
38543 // First things first: we don't show it anymore.
38544 ttScope.$evalAsync(function() {
38545 ttScope.isOpen = false;
38546 assignIsOpen(false);
38547 // And now we remove it from the DOM. However, if we have animation, we
38548 // need to wait for it to expire beforehand.
38549 // FIXME: this is a placeholder for a port of the transitions library.
38550 // The fade transition in TWBS is 150ms.
38551 if (ttScope.animation) {
38552 if (!transitionTimeout) {
38553 transitionTimeout = $timeout(removeTooltip, 150, false);
38561 function cancelHide() {
38563 $timeout.cancel(hideTimeout);
38564 hideTimeout = null;
38566 if (transitionTimeout) {
38567 $timeout.cancel(transitionTimeout);
38568 transitionTimeout = null;
38572 function createTooltip() {
38573 // There can only be one tooltip element per directive shown at once.
38578 tooltipLinkedScope = ttScope.$new();
38579 tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) {
38580 if (appendToBody) {
38581 $document.find('body').append(tooltip);
38583 element.after(tooltip);
38590 function removeTooltip() {
38593 unregisterObservers();
38599 if (tooltipLinkedScope) {
38600 tooltipLinkedScope.$destroy();
38601 tooltipLinkedScope = null;
38606 * Set the initial scope values. Once
38607 * the tooltip is created, the observers
38608 * will be added to keep things in sync.
38610 function prepareTooltip() {
38611 ttScope.title = attrs[prefix + 'Title'];
38612 if (contentParse) {
38613 ttScope.content = contentParse(scope);
38615 ttScope.content = attrs[ttType];
38618 ttScope.popupClass = attrs[prefix + 'Class'];
38619 ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement;
38621 var delay = parseInt(attrs[prefix + 'PopupDelay'], 10);
38622 var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10);
38623 ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay;
38624 ttScope.popupCloseDelay = !isNaN(closeDelay) ? closeDelay : options.popupCloseDelay;
38627 function assignIsOpen(isOpen) {
38628 if (isOpenParse && angular.isFunction(isOpenParse.assign)) {
38629 isOpenParse.assign(scope, isOpen);
38633 ttScope.contentExp = function() {
38634 return ttScope.content;
38638 * Observe the relevant attributes.
38640 attrs.$observe('disabled', function(val) {
38645 if (val && ttScope.isOpen) {
38651 scope.$watch(isOpenParse, function(val) {
38652 if (ttScope && !val === ttScope.isOpen) {
38653 toggleTooltipBind();
38658 function prepObservers() {
38659 observers.length = 0;
38661 if (contentParse) {
38663 scope.$watch(contentParse, function(val) {
38664 ttScope.content = val;
38665 if (!val && ttScope.isOpen) {
38672 tooltipLinkedScope.$watch(function() {
38673 if (!repositionScheduled) {
38674 repositionScheduled = true;
38675 tooltipLinkedScope.$$postDigest(function() {
38676 repositionScheduled = false;
38677 if (ttScope && ttScope.isOpen) {
38686 attrs.$observe(ttType, function(val) {
38687 ttScope.content = val;
38688 if (!val && ttScope.isOpen) {
38698 attrs.$observe(prefix + 'Title', function(val) {
38699 ttScope.title = val;
38700 if (ttScope.isOpen) {
38707 attrs.$observe(prefix + 'Placement', function(val) {
38708 ttScope.placement = val ? val : options.placement;
38709 if (ttScope.isOpen) {
38716 function unregisterObservers() {
38717 if (observers.length) {
38718 angular.forEach(observers, function(observer) {
38721 observers.length = 0;
38725 // hide tooltips/popovers for outsideClick trigger
38726 function bodyHideTooltipBind(e) {
38727 if (!ttScope || !ttScope.isOpen || !tooltip) {
38730 // make sure the tooltip/popover link or tool tooltip/popover itself were not clicked
38731 if (!element[0].contains(e.target) && !tooltip[0].contains(e.target)) {
38736 var unregisterTriggers = function() {
38737 triggers.show.forEach(function(trigger) {
38738 if (trigger === 'outsideClick') {
38739 element.off('click', toggleTooltipBind);
38741 element.off(trigger, showTooltipBind);
38742 element.off(trigger, toggleTooltipBind);
38745 triggers.hide.forEach(function(trigger) {
38746 if (trigger === 'outsideClick') {
38747 $document.off('click', bodyHideTooltipBind);
38749 element.off(trigger, hideTooltipBind);
38754 function prepTriggers() {
38755 var val = attrs[prefix + 'Trigger'];
38756 unregisterTriggers();
38758 triggers = getTriggers(val);
38760 if (triggers.show !== 'none') {
38761 triggers.show.forEach(function(trigger, idx) {
38762 if (trigger === 'outsideClick') {
38763 element.on('click', toggleTooltipBind);
38764 $document.on('click', bodyHideTooltipBind);
38765 } else if (trigger === triggers.hide[idx]) {
38766 element.on(trigger, toggleTooltipBind);
38767 } else if (trigger) {
38768 element.on(trigger, showTooltipBind);
38769 element.on(triggers.hide[idx], hideTooltipBind);
38772 element.on('keypress', function(e) {
38773 if (e.which === 27) {
38783 var animation = scope.$eval(attrs[prefix + 'Animation']);
38784 ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation;
38786 var appendToBodyVal;
38787 var appendKey = prefix + 'AppendToBody';
38788 if (appendKey in attrs && attrs[appendKey] === undefined) {
38789 appendToBodyVal = true;
38791 appendToBodyVal = scope.$eval(attrs[appendKey]);
38794 appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;
38796 // if a tooltip is attached to <body> we need to remove it on
38797 // location change as its parent scope will probably not be destroyed
38799 if (appendToBody) {
38800 scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess() {
38801 if (ttScope.isOpen) {
38807 // Make sure tooltip is destroyed and removed.
38808 scope.$on('$destroy', function onDestroyTooltip() {
38809 unregisterTriggers();
38811 openedTooltips.remove(ttScope);
38821 // This is mostly ngInclude code but with a custom scope
38822 .directive('uibTooltipTemplateTransclude', [
38823 '$animate', '$sce', '$compile', '$templateRequest',
38824 function ($animate, $sce, $compile, $templateRequest) {
38826 link: function(scope, elem, attrs) {
38827 var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope);
38829 var changeCounter = 0,
38834 var cleanupLastIncludeContent = function() {
38835 if (previousElement) {
38836 previousElement.remove();
38837 previousElement = null;
38840 if (currentScope) {
38841 currentScope.$destroy();
38842 currentScope = null;
38845 if (currentElement) {
38846 $animate.leave(currentElement).then(function() {
38847 previousElement = null;
38849 previousElement = currentElement;
38850 currentElement = null;
38854 scope.$watch($sce.parseAsResourceUrl(attrs.uibTooltipTemplateTransclude), function(src) {
38855 var thisChangeId = ++changeCounter;
38858 //set the 2nd param to true to ignore the template request error so that the inner
38859 //contents and scope can be cleaned up.
38860 $templateRequest(src, true).then(function(response) {
38861 if (thisChangeId !== changeCounter) { return; }
38862 var newScope = origScope.$new();
38863 var template = response;
38865 var clone = $compile(template)(newScope, function(clone) {
38866 cleanupLastIncludeContent();
38867 $animate.enter(clone, elem);
38870 currentScope = newScope;
38871 currentElement = clone;
38873 currentScope.$emit('$includeContentLoaded', src);
38875 if (thisChangeId === changeCounter) {
38876 cleanupLastIncludeContent();
38877 scope.$emit('$includeContentError', src);
38880 scope.$emit('$includeContentRequested', src);
38882 cleanupLastIncludeContent();
38886 scope.$on('$destroy', cleanupLastIncludeContent);
38892 * Note that it's intentional that these classes are *not* applied through $animate.
38893 * They must not be animated as they're expected to be present on the tooltip on
38896 .directive('uibTooltipClasses', ['$uibPosition', function($uibPosition) {
38899 link: function(scope, element, attrs) {
38900 // need to set the primary position so the
38901 // arrow has space during position measure.
38902 // tooltip.positionTooltip()
38903 if (scope.placement) {
38904 // // There are no top-left etc... classes
38905 // // in TWBS, so we need the primary position.
38906 var position = $uibPosition.parsePlacement(scope.placement);
38907 element.addClass(position[0]);
38909 element.addClass('top');
38912 if (scope.popupClass) {
38913 element.addClass(scope.popupClass);
38916 if (scope.animation()) {
38917 element.addClass(attrs.tooltipAnimationClass);
38923 .directive('uibTooltipPopup', function() {
38926 scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
38927 templateUrl: 'uib/template/tooltip/tooltip-popup.html'
38931 .directive('uibTooltip', [ '$uibTooltip', function($uibTooltip) {
38932 return $uibTooltip('uibTooltip', 'tooltip', 'mouseenter');
38935 .directive('uibTooltipTemplatePopup', function() {
38938 scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
38939 originScope: '&' },
38940 templateUrl: 'uib/template/tooltip/tooltip-template-popup.html'
38944 .directive('uibTooltipTemplate', ['$uibTooltip', function($uibTooltip) {
38945 return $uibTooltip('uibTooltipTemplate', 'tooltip', 'mouseenter', {
38946 useContentExp: true
38950 .directive('uibTooltipHtmlPopup', function() {
38953 scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
38954 templateUrl: 'uib/template/tooltip/tooltip-html-popup.html'
38958 .directive('uibTooltipHtml', ['$uibTooltip', function($uibTooltip) {
38959 return $uibTooltip('uibTooltipHtml', 'tooltip', 'mouseenter', {
38960 useContentExp: true
38965 * The following features are still outstanding: popup delay, animation as a
38966 * function, placement as a function, inside, support for more triggers than
38967 * just mouse enter/leave, and selector delegatation.
38969 angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip'])
38971 .directive('uibPopoverTemplatePopup', function() {
38974 scope: { title: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
38975 originScope: '&' },
38976 templateUrl: 'uib/template/popover/popover-template.html'
38980 .directive('uibPopoverTemplate', ['$uibTooltip', function($uibTooltip) {
38981 return $uibTooltip('uibPopoverTemplate', 'popover', 'click', {
38982 useContentExp: true
38986 .directive('uibPopoverHtmlPopup', function() {
38989 scope: { contentExp: '&', title: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
38990 templateUrl: 'uib/template/popover/popover-html.html'
38994 .directive('uibPopoverHtml', ['$uibTooltip', function($uibTooltip) {
38995 return $uibTooltip('uibPopoverHtml', 'popover', 'click', {
38996 useContentExp: true
39000 .directive('uibPopoverPopup', function() {
39003 scope: { title: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
39004 templateUrl: 'uib/template/popover/popover.html'
39008 .directive('uibPopover', ['$uibTooltip', function($uibTooltip) {
39009 return $uibTooltip('uibPopover', 'popover', 'click');
39012 angular.module('ui.bootstrap.progressbar', [])
39014 .constant('uibProgressConfig', {
39019 .controller('UibProgressController', ['$scope', '$attrs', 'uibProgressConfig', function($scope, $attrs, progressConfig) {
39021 animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
39024 $scope.max = angular.isDefined($scope.max) ? $scope.max : progressConfig.max;
39026 this.addBar = function(bar, element, attrs) {
39028 element.css({'transition': 'none'});
39031 this.bars.push(bar);
39033 bar.max = $scope.max;
39034 bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar';
39036 bar.$watch('value', function(value) {
39037 bar.recalculatePercentage();
39040 bar.recalculatePercentage = function() {
39041 var totalPercentage = self.bars.reduce(function(total, bar) {
39042 bar.percent = +(100 * bar.value / bar.max).toFixed(2);
39043 return total + bar.percent;
39046 if (totalPercentage > 100) {
39047 bar.percent -= totalPercentage - 100;
39051 bar.$on('$destroy', function() {
39053 self.removeBar(bar);
39057 this.removeBar = function(bar) {
39058 this.bars.splice(this.bars.indexOf(bar), 1);
39059 this.bars.forEach(function (bar) {
39060 bar.recalculatePercentage();
39064 $scope.$watch('max', function(max) {
39065 self.bars.forEach(function(bar) {
39066 bar.max = $scope.max;
39067 bar.recalculatePercentage();
39072 .directive('uibProgress', function() {
39076 controller: 'UibProgressController',
39077 require: 'uibProgress',
39081 templateUrl: 'uib/template/progressbar/progress.html'
39085 .directive('uibBar', function() {
39089 require: '^uibProgress',
39094 templateUrl: 'uib/template/progressbar/bar.html',
39095 link: function(scope, element, attrs, progressCtrl) {
39096 progressCtrl.addBar(scope, element, attrs);
39101 .directive('uibProgressbar', function() {
39105 controller: 'UibProgressController',
39111 templateUrl: 'uib/template/progressbar/progressbar.html',
39112 link: function(scope, element, attrs, progressCtrl) {
39113 progressCtrl.addBar(scope, angular.element(element.children()[0]), {title: attrs.title});
39118 angular.module('ui.bootstrap.rating', [])
39120 .constant('uibRatingConfig', {
39124 titles : ['one', 'two', 'three', 'four', 'five']
39127 .controller('UibRatingController', ['$scope', '$attrs', 'uibRatingConfig', function($scope, $attrs, ratingConfig) {
39128 var ngModelCtrl = { $setViewValue: angular.noop };
39130 this.init = function(ngModelCtrl_) {
39131 ngModelCtrl = ngModelCtrl_;
39132 ngModelCtrl.$render = this.render;
39134 ngModelCtrl.$formatters.push(function(value) {
39135 if (angular.isNumber(value) && value << 0 !== value) {
39136 value = Math.round(value);
39142 this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
39143 this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
39144 var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles ;
39145 this.titles = angular.isArray(tmpTitles) && tmpTitles.length > 0 ?
39146 tmpTitles : ratingConfig.titles;
39148 var ratingStates = angular.isDefined($attrs.ratingStates) ?
39149 $scope.$parent.$eval($attrs.ratingStates) :
39150 new Array(angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max);
39151 $scope.range = this.buildTemplateObjects(ratingStates);
39154 this.buildTemplateObjects = function(states) {
39155 for (var i = 0, n = states.length; i < n; i++) {
39156 states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff, title: this.getTitle(i) }, states[i]);
39161 this.getTitle = function(index) {
39162 if (index >= this.titles.length) {
39166 return this.titles[index];
39169 $scope.rate = function(value) {
39170 if (!$scope.readonly && value >= 0 && value <= $scope.range.length) {
39171 ngModelCtrl.$setViewValue(ngModelCtrl.$viewValue === value ? 0 : value);
39172 ngModelCtrl.$render();
39176 $scope.enter = function(value) {
39177 if (!$scope.readonly) {
39178 $scope.value = value;
39180 $scope.onHover({value: value});
39183 $scope.reset = function() {
39184 $scope.value = ngModelCtrl.$viewValue;
39188 $scope.onKeydown = function(evt) {
39189 if (/(37|38|39|40)/.test(evt.which)) {
39190 evt.preventDefault();
39191 evt.stopPropagation();
39192 $scope.rate($scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1));
39196 this.render = function() {
39197 $scope.value = ngModelCtrl.$viewValue;
39201 .directive('uibRating', function() {
39203 require: ['uibRating', 'ngModel'],
39209 controller: 'UibRatingController',
39210 templateUrl: 'uib/template/rating/rating.html',
39212 link: function(scope, element, attrs, ctrls) {
39213 var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];
39214 ratingCtrl.init(ngModelCtrl);
39219 angular.module('ui.bootstrap.tabs', [])
39221 .controller('UibTabsetController', ['$scope', function ($scope) {
39223 tabs = ctrl.tabs = $scope.tabs = [];
39225 ctrl.select = function(selectedTab) {
39226 angular.forEach(tabs, function(tab) {
39227 if (tab.active && tab !== selectedTab) {
39228 tab.active = false;
39230 selectedTab.selectCalled = false;
39233 selectedTab.active = true;
39234 // only call select if it has not already been called
39235 if (!selectedTab.selectCalled) {
39236 selectedTab.onSelect();
39237 selectedTab.selectCalled = true;
39241 ctrl.addTab = function addTab(tab) {
39243 // we can't run the select function on the first tab
39244 // since that would select it twice
39245 if (tabs.length === 1 && tab.active !== false) {
39247 } else if (tab.active) {
39250 tab.active = false;
39254 ctrl.removeTab = function removeTab(tab) {
39255 var index = tabs.indexOf(tab);
39256 //Select a new tab if the tab to be removed is selected and not destroyed
39257 if (tab.active && tabs.length > 1 && !destroyed) {
39258 //If this is the last tab, select the previous tab. else, the next tab.
39259 var newActiveIndex = index === tabs.length - 1 ? index - 1 : index + 1;
39260 ctrl.select(tabs[newActiveIndex]);
39262 tabs.splice(index, 1);
39266 $scope.$on('$destroy', function() {
39271 .directive('uibTabset', function() {
39278 controller: 'UibTabsetController',
39279 templateUrl: 'uib/template/tabs/tabset.html',
39280 link: function(scope, element, attrs) {
39281 scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
39282 scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false;
39287 .directive('uibTab', ['$parse', function($parse) {
39289 require: '^uibTabset',
39291 templateUrl: 'uib/template/tabs/tab.html',
39296 onSelect: '&select', //This callback is called in contentHeadingTransclude
39297 //once it inserts the tab's content into the dom
39298 onDeselect: '&deselect'
39300 controller: function() {
39301 //Empty controller so other directives can require being 'under' a tab
39303 controllerAs: 'tab',
39304 link: function(scope, elm, attrs, tabsetCtrl, transclude) {
39305 scope.$watch('active', function(active) {
39307 tabsetCtrl.select(scope);
39311 scope.disabled = false;
39312 if (attrs.disable) {
39313 scope.$parent.$watch($parse(attrs.disable), function(value) {
39314 scope.disabled = !! value;
39318 scope.select = function() {
39319 if (!scope.disabled) {
39320 scope.active = true;
39324 tabsetCtrl.addTab(scope);
39325 scope.$on('$destroy', function() {
39326 tabsetCtrl.removeTab(scope);
39329 //We need to transclude later, once the content container is ready.
39330 //when this link happens, we're inside a tab heading.
39331 scope.$transcludeFn = transclude;
39336 .directive('uibTabHeadingTransclude', function() {
39339 require: '^uibTab',
39340 link: function(scope, elm) {
39341 scope.$watch('headingElement', function updateHeadingElement(heading) {
39344 elm.append(heading);
39351 .directive('uibTabContentTransclude', function() {
39354 require: '^uibTabset',
39355 link: function(scope, elm, attrs) {
39356 var tab = scope.$eval(attrs.uibTabContentTransclude);
39358 //Now our tab is ready to be transcluded: both the tab heading area
39359 //and the tab content area are loaded. Transclude 'em both.
39360 tab.$transcludeFn(tab.$parent, function(contents) {
39361 angular.forEach(contents, function(node) {
39362 if (isTabHeading(node)) {
39363 //Let tabHeadingTransclude know.
39364 tab.headingElement = node;
39373 function isTabHeading(node) {
39374 return node.tagName && (
39375 node.hasAttribute('uib-tab-heading') ||
39376 node.hasAttribute('data-uib-tab-heading') ||
39377 node.hasAttribute('x-uib-tab-heading') ||
39378 node.tagName.toLowerCase() === 'uib-tab-heading' ||
39379 node.tagName.toLowerCase() === 'data-uib-tab-heading' ||
39380 node.tagName.toLowerCase() === 'x-uib-tab-heading'
39385 angular.module('ui.bootstrap.timepicker', [])
39387 .constant('uibTimepickerConfig', {
39391 showMeridian: true,
39392 showSeconds: false,
39394 readonlyInput: false,
39400 .controller('UibTimepickerController', ['$scope', '$element', '$attrs', '$parse', '$log', '$locale', 'uibTimepickerConfig', function($scope, $element, $attrs, $parse, $log, $locale, timepickerConfig) {
39401 var selected = new Date(),
39402 ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
39403 meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS;
39405 $scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0;
39406 $element.removeAttr('tabindex');
39408 this.init = function(ngModelCtrl_, inputs) {
39409 ngModelCtrl = ngModelCtrl_;
39410 ngModelCtrl.$render = this.render;
39412 ngModelCtrl.$formatters.unshift(function(modelValue) {
39413 return modelValue ? new Date(modelValue) : null;
39416 var hoursInputEl = inputs.eq(0),
39417 minutesInputEl = inputs.eq(1),
39418 secondsInputEl = inputs.eq(2);
39420 var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel;
39423 this.setupMousewheelEvents(hoursInputEl, minutesInputEl, secondsInputEl);
39426 var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys;
39428 this.setupArrowkeyEvents(hoursInputEl, minutesInputEl, secondsInputEl);
39431 $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput;
39432 this.setupInputEvents(hoursInputEl, minutesInputEl, secondsInputEl);
39435 var hourStep = timepickerConfig.hourStep;
39436 if ($attrs.hourStep) {
39437 $scope.$parent.$watch($parse($attrs.hourStep), function(value) {
39438 hourStep = parseInt(value, 10);
39442 var minuteStep = timepickerConfig.minuteStep;
39443 if ($attrs.minuteStep) {
39444 $scope.$parent.$watch($parse($attrs.minuteStep), function(value) {
39445 minuteStep = parseInt(value, 10);
39450 $scope.$parent.$watch($parse($attrs.min), function(value) {
39451 var dt = new Date(value);
39452 min = isNaN(dt) ? undefined : dt;
39456 $scope.$parent.$watch($parse($attrs.max), function(value) {
39457 var dt = new Date(value);
39458 max = isNaN(dt) ? undefined : dt;
39461 var disabled = false;
39462 if ($attrs.ngDisabled) {
39463 $scope.$parent.$watch($parse($attrs.ngDisabled), function(value) {
39468 $scope.noIncrementHours = function() {
39469 var incrementedSelected = addMinutes(selected, hourStep * 60);
39470 return disabled || incrementedSelected > max ||
39471 incrementedSelected < selected && incrementedSelected < min;
39474 $scope.noDecrementHours = function() {
39475 var decrementedSelected = addMinutes(selected, -hourStep * 60);
39476 return disabled || decrementedSelected < min ||
39477 decrementedSelected > selected && decrementedSelected > max;
39480 $scope.noIncrementMinutes = function() {
39481 var incrementedSelected = addMinutes(selected, minuteStep);
39482 return disabled || incrementedSelected > max ||
39483 incrementedSelected < selected && incrementedSelected < min;
39486 $scope.noDecrementMinutes = function() {
39487 var decrementedSelected = addMinutes(selected, -minuteStep);
39488 return disabled || decrementedSelected < min ||
39489 decrementedSelected > selected && decrementedSelected > max;
39492 $scope.noIncrementSeconds = function() {
39493 var incrementedSelected = addSeconds(selected, secondStep);
39494 return disabled || incrementedSelected > max ||
39495 incrementedSelected < selected && incrementedSelected < min;
39498 $scope.noDecrementSeconds = function() {
39499 var decrementedSelected = addSeconds(selected, -secondStep);
39500 return disabled || decrementedSelected < min ||
39501 decrementedSelected > selected && decrementedSelected > max;
39504 $scope.noToggleMeridian = function() {
39505 if (selected.getHours() < 12) {
39506 return disabled || addMinutes(selected, 12 * 60) > max;
39509 return disabled || addMinutes(selected, -12 * 60) < min;
39512 var secondStep = timepickerConfig.secondStep;
39513 if ($attrs.secondStep) {
39514 $scope.$parent.$watch($parse($attrs.secondStep), function(value) {
39515 secondStep = parseInt(value, 10);
39519 $scope.showSeconds = timepickerConfig.showSeconds;
39520 if ($attrs.showSeconds) {
39521 $scope.$parent.$watch($parse($attrs.showSeconds), function(value) {
39522 $scope.showSeconds = !!value;
39527 $scope.showMeridian = timepickerConfig.showMeridian;
39528 if ($attrs.showMeridian) {
39529 $scope.$parent.$watch($parse($attrs.showMeridian), function(value) {
39530 $scope.showMeridian = !!value;
39532 if (ngModelCtrl.$error.time) {
39533 // Evaluate from template
39534 var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();
39535 if (angular.isDefined(hours) && angular.isDefined(minutes)) {
39536 selected.setHours(hours);
39545 // Get $scope.hours in 24H mode if valid
39546 function getHoursFromTemplate() {
39547 var hours = parseInt($scope.hours, 10);
39548 var valid = $scope.showMeridian ? hours > 0 && hours < 13 :
39549 hours >= 0 && hours < 24;
39554 if ($scope.showMeridian) {
39555 if (hours === 12) {
39558 if ($scope.meridian === meridians[1]) {
39559 hours = hours + 12;
39565 function getMinutesFromTemplate() {
39566 var minutes = parseInt($scope.minutes, 10);
39567 return minutes >= 0 && minutes < 60 ? minutes : undefined;
39570 function getSecondsFromTemplate() {
39571 var seconds = parseInt($scope.seconds, 10);
39572 return seconds >= 0 && seconds < 60 ? seconds : undefined;
39575 function pad(value) {
39576 if (value === null) {
39580 return angular.isDefined(value) && value.toString().length < 2 ?
39581 '0' + value : value.toString();
39584 // Respond on mousewheel spin
39585 this.setupMousewheelEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
39586 var isScrollingUp = function(e) {
39587 if (e.originalEvent) {
39588 e = e.originalEvent;
39590 //pick correct delta variable depending on event
39591 var delta = e.wheelDelta ? e.wheelDelta : -e.deltaY;
39592 return e.detail || delta > 0;
39595 hoursInputEl.bind('mousewheel wheel', function(e) {
39597 $scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours());
39599 e.preventDefault();
39602 minutesInputEl.bind('mousewheel wheel', function(e) {
39604 $scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes());
39606 e.preventDefault();
39609 secondsInputEl.bind('mousewheel wheel', function(e) {
39611 $scope.$apply(isScrollingUp(e) ? $scope.incrementSeconds() : $scope.decrementSeconds());
39613 e.preventDefault();
39617 // Respond on up/down arrowkeys
39618 this.setupArrowkeyEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
39619 hoursInputEl.bind('keydown', function(e) {
39621 if (e.which === 38) { // up
39622 e.preventDefault();
39623 $scope.incrementHours();
39625 } else if (e.which === 40) { // down
39626 e.preventDefault();
39627 $scope.decrementHours();
39633 minutesInputEl.bind('keydown', function(e) {
39635 if (e.which === 38) { // up
39636 e.preventDefault();
39637 $scope.incrementMinutes();
39639 } else if (e.which === 40) { // down
39640 e.preventDefault();
39641 $scope.decrementMinutes();
39647 secondsInputEl.bind('keydown', function(e) {
39649 if (e.which === 38) { // up
39650 e.preventDefault();
39651 $scope.incrementSeconds();
39653 } else if (e.which === 40) { // down
39654 e.preventDefault();
39655 $scope.decrementSeconds();
39662 this.setupInputEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
39663 if ($scope.readonlyInput) {
39664 $scope.updateHours = angular.noop;
39665 $scope.updateMinutes = angular.noop;
39666 $scope.updateSeconds = angular.noop;
39670 var invalidate = function(invalidHours, invalidMinutes, invalidSeconds) {
39671 ngModelCtrl.$setViewValue(null);
39672 ngModelCtrl.$setValidity('time', false);
39673 if (angular.isDefined(invalidHours)) {
39674 $scope.invalidHours = invalidHours;
39677 if (angular.isDefined(invalidMinutes)) {
39678 $scope.invalidMinutes = invalidMinutes;
39681 if (angular.isDefined(invalidSeconds)) {
39682 $scope.invalidSeconds = invalidSeconds;
39686 $scope.updateHours = function() {
39687 var hours = getHoursFromTemplate(),
39688 minutes = getMinutesFromTemplate();
39690 ngModelCtrl.$setDirty();
39692 if (angular.isDefined(hours) && angular.isDefined(minutes)) {
39693 selected.setHours(hours);
39694 selected.setMinutes(minutes);
39695 if (selected < min || selected > max) {
39705 hoursInputEl.bind('blur', function(e) {
39706 ngModelCtrl.$setTouched();
39707 if ($scope.hours === null || $scope.hours === '') {
39709 } else if (!$scope.invalidHours && $scope.hours < 10) {
39710 $scope.$apply(function() {
39711 $scope.hours = pad($scope.hours);
39716 $scope.updateMinutes = function() {
39717 var minutes = getMinutesFromTemplate(),
39718 hours = getHoursFromTemplate();
39720 ngModelCtrl.$setDirty();
39722 if (angular.isDefined(minutes) && angular.isDefined(hours)) {
39723 selected.setHours(hours);
39724 selected.setMinutes(minutes);
39725 if (selected < min || selected > max) {
39726 invalidate(undefined, true);
39731 invalidate(undefined, true);
39735 minutesInputEl.bind('blur', function(e) {
39736 ngModelCtrl.$setTouched();
39737 if ($scope.minutes === null) {
39738 invalidate(undefined, true);
39739 } else if (!$scope.invalidMinutes && $scope.minutes < 10) {
39740 $scope.$apply(function() {
39741 $scope.minutes = pad($scope.minutes);
39746 $scope.updateSeconds = function() {
39747 var seconds = getSecondsFromTemplate();
39749 ngModelCtrl.$setDirty();
39751 if (angular.isDefined(seconds)) {
39752 selected.setSeconds(seconds);
39755 invalidate(undefined, undefined, true);
39759 secondsInputEl.bind('blur', function(e) {
39760 if (!$scope.invalidSeconds && $scope.seconds < 10) {
39761 $scope.$apply( function() {
39762 $scope.seconds = pad($scope.seconds);
39769 this.render = function() {
39770 var date = ngModelCtrl.$viewValue;
39773 ngModelCtrl.$setValidity('time', false);
39774 $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.');
39780 if (selected < min || selected > max) {
39781 ngModelCtrl.$setValidity('time', false);
39782 $scope.invalidHours = true;
39783 $scope.invalidMinutes = true;
39791 // Call internally when we know that model is valid.
39792 function refresh(keyboardChange) {
39794 ngModelCtrl.$setViewValue(new Date(selected));
39795 updateTemplate(keyboardChange);
39798 function makeValid() {
39799 ngModelCtrl.$setValidity('time', true);
39800 $scope.invalidHours = false;
39801 $scope.invalidMinutes = false;
39802 $scope.invalidSeconds = false;
39805 function updateTemplate(keyboardChange) {
39806 if (!ngModelCtrl.$modelValue) {
39807 $scope.hours = null;
39808 $scope.minutes = null;
39809 $scope.seconds = null;
39810 $scope.meridian = meridians[0];
39812 var hours = selected.getHours(),
39813 minutes = selected.getMinutes(),
39814 seconds = selected.getSeconds();
39816 if ($scope.showMeridian) {
39817 hours = hours === 0 || hours === 12 ? 12 : hours % 12; // Convert 24 to 12 hour system
39820 $scope.hours = keyboardChange === 'h' ? hours : pad(hours);
39821 if (keyboardChange !== 'm') {
39822 $scope.minutes = pad(minutes);
39824 $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
39826 if (keyboardChange !== 's') {
39827 $scope.seconds = pad(seconds);
39829 $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
39833 function addSecondsToSelected(seconds) {
39834 selected = addSeconds(selected, seconds);
39838 function addMinutes(selected, minutes) {
39839 return addSeconds(selected, minutes*60);
39842 function addSeconds(date, seconds) {
39843 var dt = new Date(date.getTime() + seconds * 1000);
39844 var newDate = new Date(date);
39845 newDate.setHours(dt.getHours(), dt.getMinutes(), dt.getSeconds());
39849 $scope.showSpinners = angular.isDefined($attrs.showSpinners) ?
39850 $scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners;
39852 $scope.incrementHours = function() {
39853 if (!$scope.noIncrementHours()) {
39854 addSecondsToSelected(hourStep * 60 * 60);
39858 $scope.decrementHours = function() {
39859 if (!$scope.noDecrementHours()) {
39860 addSecondsToSelected(-hourStep * 60 * 60);
39864 $scope.incrementMinutes = function() {
39865 if (!$scope.noIncrementMinutes()) {
39866 addSecondsToSelected(minuteStep * 60);
39870 $scope.decrementMinutes = function() {
39871 if (!$scope.noDecrementMinutes()) {
39872 addSecondsToSelected(-minuteStep * 60);
39876 $scope.incrementSeconds = function() {
39877 if (!$scope.noIncrementSeconds()) {
39878 addSecondsToSelected(secondStep);
39882 $scope.decrementSeconds = function() {
39883 if (!$scope.noDecrementSeconds()) {
39884 addSecondsToSelected(-secondStep);
39888 $scope.toggleMeridian = function() {
39889 var minutes = getMinutesFromTemplate(),
39890 hours = getHoursFromTemplate();
39892 if (!$scope.noToggleMeridian()) {
39893 if (angular.isDefined(minutes) && angular.isDefined(hours)) {
39894 addSecondsToSelected(12 * 60 * (selected.getHours() < 12 ? 60 : -60));
39896 $scope.meridian = $scope.meridian === meridians[0] ? meridians[1] : meridians[0];
39901 $scope.blur = function() {
39902 ngModelCtrl.$setTouched();
39906 .directive('uibTimepicker', function() {
39908 require: ['uibTimepicker', '?^ngModel'],
39909 controller: 'UibTimepickerController',
39910 controllerAs: 'timepicker',
39913 templateUrl: function(element, attrs) {
39914 return attrs.templateUrl || 'uib/template/timepicker/timepicker.html';
39916 link: function(scope, element, attrs, ctrls) {
39917 var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
39920 timepickerCtrl.init(ngModelCtrl, element.find('input'));
39926 angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap.position'])
39929 * A helper service that can parse typeahead's syntax (string provided by users)
39930 * Extracted to a separate service for ease of unit testing
39932 .factory('uibTypeaheadParser', ['$parse', function($parse) {
39933 // 00000111000000000000022200000000000000003333333333333330000000000044000
39934 var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
39936 parse: function(input) {
39937 var match = input.match(TYPEAHEAD_REGEXP);
39940 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
39941 ' but got "' + input + '".');
39945 itemName: match[3],
39946 source: $parse(match[4]),
39947 viewMapper: $parse(match[2] || match[1]),
39948 modelMapper: $parse(match[1])
39954 .controller('UibTypeaheadController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$$debounce', '$uibPosition', 'uibTypeaheadParser',
39955 function(originalScope, element, attrs, $compile, $parse, $q, $timeout, $document, $window, $rootScope, $$debounce, $position, typeaheadParser) {
39956 var HOT_KEYS = [9, 13, 27, 38, 40];
39957 var eventDebounceTime = 200;
39958 var modelCtrl, ngModelOptions;
39959 //SUPPORTED ATTRIBUTES (OPTIONS)
39961 //minimal no of characters that needs to be entered before typeahead kicks-in
39962 var minLength = originalScope.$eval(attrs.typeaheadMinLength);
39963 if (!minLength && minLength !== 0) {
39967 //minimal wait time after last character typed before typeahead kicks-in
39968 var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
39970 //should it restrict model values to the ones selected from the popup only?
39971 var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
39972 originalScope.$watch(attrs.typeaheadEditable, function (newVal) {
39973 isEditable = newVal !== false;
39976 //binding to a variable that indicates if matches are being retrieved asynchronously
39977 var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
39979 //a callback executed when a match is selected
39980 var onSelectCallback = $parse(attrs.typeaheadOnSelect);
39982 //should it select highlighted popup value when losing focus?
39983 var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false;
39985 //binding to a variable that indicates if there were no results after the query is completed
39986 var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop;
39988 var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
39990 var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;
39992 var appendTo = attrs.typeaheadAppendTo ?
39993 originalScope.$eval(attrs.typeaheadAppendTo) : null;
39995 var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;
39997 //If input matches an item of the list exactly, select it automatically
39998 var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false;
40000 //binding to a variable that indicates if dropdown is open
40001 var isOpenSetter = $parse(attrs.typeaheadIsOpen).assign || angular.noop;
40003 var showHint = originalScope.$eval(attrs.typeaheadShowHint) || false;
40005 //INTERNAL VARIABLES
40007 //model setter executed upon match selection
40008 var parsedModel = $parse(attrs.ngModel);
40009 var invokeModelSetter = $parse(attrs.ngModel + '($$$p)');
40010 var $setModelValue = function(scope, newValue) {
40011 if (angular.isFunction(parsedModel(originalScope)) &&
40012 ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) {
40013 return invokeModelSetter(scope, {$$$p: newValue});
40016 return parsedModel.assign(scope, newValue);
40019 //expressions used by typeahead
40020 var parserResult = typeaheadParser.parse(attrs.uibTypeahead);
40024 //Used to avoid bug in iOS webview where iOS keyboard does not fire
40025 //mousedown & mouseup events
40029 //create a child scope for the typeahead directive so we are not polluting original scope
40030 //with typeahead-specific data (matches, query etc.)
40031 var scope = originalScope.$new();
40032 var offDestroy = originalScope.$on('$destroy', function() {
40035 scope.$on('$destroy', offDestroy);
40038 var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
40040 'aria-autocomplete': 'list',
40041 'aria-expanded': false,
40042 'aria-owns': popupId
40045 var inputsContainer, hintInputElem;
40046 //add read-only input to show hint
40048 inputsContainer = angular.element('<div></div>');
40049 inputsContainer.css('position', 'relative');
40050 element.after(inputsContainer);
40051 hintInputElem = element.clone();
40052 hintInputElem.attr('placeholder', '');
40053 hintInputElem.val('');
40054 hintInputElem.css({
40055 'position': 'absolute',
40058 'border-color': 'transparent',
40059 'box-shadow': 'none',
40061 'background': 'none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)',
40065 'position': 'relative',
40066 'vertical-align': 'top',
40067 'background-color': 'transparent'
40069 inputsContainer.append(hintInputElem);
40070 hintInputElem.after(element);
40073 //pop-up element used to display matches
40074 var popUpEl = angular.element('<div uib-typeahead-popup></div>');
40077 matches: 'matches',
40078 active: 'activeIdx',
40079 select: 'select(activeIdx, evt)',
40080 'move-in-progress': 'moveInProgress',
40082 position: 'position',
40083 'assign-is-open': 'assignIsOpen(isOpen)',
40084 debounce: 'debounceUpdate'
40086 //custom item template
40087 if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
40088 popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
40091 if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) {
40092 popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl);
40095 var resetHint = function() {
40097 hintInputElem.val('');
40101 var resetMatches = function() {
40102 scope.matches = [];
40103 scope.activeIdx = -1;
40104 element.attr('aria-expanded', false);
40108 var getMatchId = function(index) {
40109 return popupId + '-option-' + index;
40112 // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
40113 // This attribute is added or removed automatically when the `activeIdx` changes.
40114 scope.$watch('activeIdx', function(index) {
40116 element.removeAttr('aria-activedescendant');
40118 element.attr('aria-activedescendant', getMatchId(index));
40122 var inputIsExactMatch = function(inputValue, index) {
40123 if (scope.matches.length > index && inputValue) {
40124 return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase();
40130 var getMatchesAsync = function(inputValue, evt) {
40131 var locals = {$viewValue: inputValue};
40132 isLoadingSetter(originalScope, true);
40133 isNoResultsSetter(originalScope, false);
40134 $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
40135 //it might happen that several async queries were in progress if a user were typing fast
40136 //but we are interested only in responses that correspond to the current view value
40137 var onCurrentRequest = inputValue === modelCtrl.$viewValue;
40138 if (onCurrentRequest && hasFocus) {
40139 if (matches && matches.length > 0) {
40140 scope.activeIdx = focusFirst ? 0 : -1;
40141 isNoResultsSetter(originalScope, false);
40142 scope.matches.length = 0;
40145 for (var i = 0; i < matches.length; i++) {
40146 locals[parserResult.itemName] = matches[i];
40147 scope.matches.push({
40149 label: parserResult.viewMapper(scope, locals),
40154 scope.query = inputValue;
40155 //position pop-up with matches - we need to re-calculate its position each time we are opening a window
40156 //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
40157 //due to other elements being rendered
40158 recalculatePosition();
40160 element.attr('aria-expanded', true);
40162 //Select the single remaining option if user input matches
40163 if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) {
40164 if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
40165 $$debounce(function() {
40166 scope.select(0, evt);
40167 }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
40169 scope.select(0, evt);
40174 var firstLabel = scope.matches[0].label;
40175 if (inputValue.length > 0 && firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) {
40176 hintInputElem.val(inputValue + firstLabel.slice(inputValue.length));
40179 hintInputElem.val('');
40184 isNoResultsSetter(originalScope, true);
40187 if (onCurrentRequest) {
40188 isLoadingSetter(originalScope, false);
40192 isLoadingSetter(originalScope, false);
40193 isNoResultsSetter(originalScope, true);
40197 // bind events only if appendToBody params exist - performance feature
40198 if (appendToBody) {
40199 angular.element($window).on('resize', fireRecalculating);
40200 $document.find('body').on('scroll', fireRecalculating);
40203 // Declare the debounced function outside recalculating for
40204 // proper debouncing
40205 var debouncedRecalculate = $$debounce(function() {
40206 // if popup is visible
40207 if (scope.matches.length) {
40208 recalculatePosition();
40211 scope.moveInProgress = false;
40212 }, eventDebounceTime);
40214 // Default progress type
40215 scope.moveInProgress = false;
40217 function fireRecalculating() {
40218 if (!scope.moveInProgress) {
40219 scope.moveInProgress = true;
40223 debouncedRecalculate();
40226 // recalculate actual position and set new values to scope
40227 // after digest loop is popup in right position
40228 function recalculatePosition() {
40229 scope.position = appendToBody ? $position.offset(element) : $position.position(element);
40230 scope.position.top += element.prop('offsetHeight');
40233 //we need to propagate user's query so we can higlight matches
40234 scope.query = undefined;
40236 //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
40237 var timeoutPromise;
40239 var scheduleSearchWithTimeout = function(inputValue) {
40240 timeoutPromise = $timeout(function() {
40241 getMatchesAsync(inputValue);
40245 var cancelPreviousTimeout = function() {
40246 if (timeoutPromise) {
40247 $timeout.cancel(timeoutPromise);
40253 scope.assignIsOpen = function (isOpen) {
40254 isOpenSetter(originalScope, isOpen);
40257 scope.select = function(activeIdx, evt) {
40258 //called from within the $digest() cycle
40263 locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
40264 model = parserResult.modelMapper(originalScope, locals);
40265 $setModelValue(originalScope, model);
40266 modelCtrl.$setValidity('editable', true);
40267 modelCtrl.$setValidity('parse', true);
40269 onSelectCallback(originalScope, {
40272 $label: parserResult.viewMapper(originalScope, locals),
40278 //return focus to the input element if a match was selected via a mouse click event
40279 // use timeout to avoid $rootScope:inprog error
40280 if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) {
40281 $timeout(function() { element[0].focus(); }, 0, false);
40285 //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
40286 element.on('keydown', function(evt) {
40287 //typeahead is open and an "interesting" key was pressed
40288 if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
40292 // if there's nothing selected (i.e. focusFirst) and enter or tab is hit, clear the results
40293 if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13)) {
40299 evt.preventDefault();
40301 switch (evt.which) {
40304 scope.$apply(function () {
40305 if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
40306 $$debounce(function() {
40307 scope.select(scope.activeIdx, evt);
40308 }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
40310 scope.select(scope.activeIdx, evt);
40315 evt.stopPropagation();
40321 scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
40323 popUpEl.find('li')[scope.activeIdx].scrollIntoView(false);
40326 scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
40328 popUpEl.find('li')[scope.activeIdx].scrollIntoView(false);
40333 element.bind('focus', function (evt) {
40335 if (minLength === 0 && !modelCtrl.$viewValue) {
40336 $timeout(function() {
40337 getMatchesAsync(modelCtrl.$viewValue, evt);
40342 element.bind('blur', function(evt) {
40343 if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) {
40345 scope.$apply(function() {
40346 if (angular.isObject(scope.debounceUpdate) && angular.isNumber(scope.debounceUpdate.blur)) {
40347 $$debounce(function() {
40348 scope.select(scope.activeIdx, evt);
40349 }, scope.debounceUpdate.blur);
40351 scope.select(scope.activeIdx, evt);
40355 if (!isEditable && modelCtrl.$error.editable) {
40356 modelCtrl.$viewValue = '';
40363 // Keep reference to click handler to unbind it.
40364 var dismissClickHandler = function(evt) {
40366 // Firefox treats right click as a click on document
40367 if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) {
40369 if (!$rootScope.$$phase) {
40375 $document.on('click', dismissClickHandler);
40377 originalScope.$on('$destroy', function() {
40378 $document.off('click', dismissClickHandler);
40379 if (appendToBody || appendTo) {
40383 if (appendToBody) {
40384 angular.element($window).off('resize', fireRecalculating);
40385 $document.find('body').off('scroll', fireRecalculating);
40387 // Prevent jQuery cache memory leak
40391 inputsContainer.remove();
40395 var $popup = $compile(popUpEl)(scope);
40397 if (appendToBody) {
40398 $document.find('body').append($popup);
40399 } else if (appendTo) {
40400 angular.element(appendTo).eq(0).append($popup);
40402 element.after($popup);
40405 this.init = function(_modelCtrl, _ngModelOptions) {
40406 modelCtrl = _modelCtrl;
40407 ngModelOptions = _ngModelOptions;
40409 scope.debounceUpdate = modelCtrl.$options && $parse(modelCtrl.$options.debounce)(originalScope);
40411 //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
40412 //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
40413 modelCtrl.$parsers.unshift(function(inputValue) {
40416 if (minLength === 0 || inputValue && inputValue.length >= minLength) {
40417 if (waitTime > 0) {
40418 cancelPreviousTimeout();
40419 scheduleSearchWithTimeout(inputValue);
40421 getMatchesAsync(inputValue);
40424 isLoadingSetter(originalScope, false);
40425 cancelPreviousTimeout();
40434 // Reset in case user had typed something previously.
40435 modelCtrl.$setValidity('editable', true);
40439 modelCtrl.$setValidity('editable', false);
40443 modelCtrl.$formatters.push(function(modelValue) {
40444 var candidateViewValue, emptyViewValue;
40447 // The validity may be set to false via $parsers (see above) if
40448 // the model is restricted to selected values. If the model
40449 // is set manually it is considered to be valid.
40451 modelCtrl.$setValidity('editable', true);
40454 if (inputFormatter) {
40455 locals.$model = modelValue;
40456 return inputFormatter(originalScope, locals);
40459 //it might happen that we don't have enough info to properly render input value
40460 //we need to check for this situation and simply return model value if we can't apply custom formatting
40461 locals[parserResult.itemName] = modelValue;
40462 candidateViewValue = parserResult.viewMapper(originalScope, locals);
40463 locals[parserResult.itemName] = undefined;
40464 emptyViewValue = parserResult.viewMapper(originalScope, locals);
40466 return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;
40471 .directive('uibTypeahead', function() {
40473 controller: 'UibTypeaheadController',
40474 require: ['ngModel', '^?ngModelOptions', 'uibTypeahead'],
40475 link: function(originalScope, element, attrs, ctrls) {
40476 ctrls[2].init(ctrls[0], ctrls[1]);
40481 .directive('uibTypeaheadPopup', ['$$debounce', function($$debounce) {
40488 moveInProgress: '=',
40494 templateUrl: function(element, attrs) {
40495 return attrs.popupTemplateUrl || 'uib/template/typeahead/typeahead-popup.html';
40497 link: function(scope, element, attrs) {
40498 scope.templateUrl = attrs.templateUrl;
40500 scope.isOpen = function() {
40501 var isDropdownOpen = scope.matches.length > 0;
40502 scope.assignIsOpen({ isOpen: isDropdownOpen });
40503 return isDropdownOpen;
40506 scope.isActive = function(matchIdx) {
40507 return scope.active === matchIdx;
40510 scope.selectActive = function(matchIdx) {
40511 scope.active = matchIdx;
40514 scope.selectMatch = function(activeIdx, evt) {
40515 var debounce = scope.debounce();
40516 if (angular.isNumber(debounce) || angular.isObject(debounce)) {
40517 $$debounce(function() {
40518 scope.select({activeIdx: activeIdx, evt: evt});
40519 }, angular.isNumber(debounce) ? debounce : debounce['default']);
40521 scope.select({activeIdx: activeIdx, evt: evt});
40528 .directive('uibTypeaheadMatch', ['$templateRequest', '$compile', '$parse', function($templateRequest, $compile, $parse) {
40535 link: function(scope, element, attrs) {
40536 var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'uib/template/typeahead/typeahead-match.html';
40537 $templateRequest(tplUrl).then(function(tplContent) {
40538 var tplEl = angular.element(tplContent.trim());
40539 element.replaceWith(tplEl);
40540 $compile(tplEl)(scope);
40546 .filter('uibTypeaheadHighlight', ['$sce', '$injector', '$log', function($sce, $injector, $log) {
40547 var isSanitizePresent;
40548 isSanitizePresent = $injector.has('$sanitize');
40550 function escapeRegexp(queryToEscape) {
40551 // Regex: capture the whole query string and replace it with the string that will be used to match
40552 // the results, for example if the capture is "a" the result will be \a
40553 return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
40556 function containsHtml(matchItem) {
40557 return /<.*>/g.test(matchItem);
40560 return function(matchItem, query) {
40561 if (!isSanitizePresent && containsHtml(matchItem)) {
40562 $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger
40564 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
40565 if (!isSanitizePresent) {
40566 matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive
40572 angular.module("uib/template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) {
40573 $templateCache.put("uib/template/accordion/accordion-group.html",
40574 "<div class=\"panel\" ng-class=\"panelClass || 'panel-default'\">\n" +
40575 " <div class=\"panel-heading\" ng-keypress=\"toggleOpen($event)\">\n" +
40576 " <h4 class=\"panel-title\">\n" +
40577 " <div tabindex=\"0\" class=\"accordion-toggle\" ng-click=\"toggleOpen()\" uib-accordion-transclude=\"heading\"><span ng-class=\"{'text-muted': isDisabled}\">{{heading}}</span></div>\n" +
40580 " <div class=\"panel-collapse collapse\" uib-collapse=\"!isOpen\">\n" +
40581 " <div class=\"panel-body\" ng-transclude></div>\n" +
40587 angular.module("uib/template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) {
40588 $templateCache.put("uib/template/accordion/accordion.html",
40589 "<div class=\"panel-group\" ng-transclude></div>");
40592 angular.module("uib/template/alert/alert.html", []).run(["$templateCache", function($templateCache) {
40593 $templateCache.put("uib/template/alert/alert.html",
40594 "<div class=\"alert\" ng-class=\"['alert-' + (type || 'warning'), closeable ? 'alert-dismissible' : null]\" role=\"alert\">\n" +
40595 " <button ng-show=\"closeable\" type=\"button\" class=\"close\" ng-click=\"close({$event: $event})\">\n" +
40596 " <span aria-hidden=\"true\">×</span>\n" +
40597 " <span class=\"sr-only\">Close</span>\n" +
40599 " <div ng-transclude></div>\n" +
40604 angular.module("uib/template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) {
40605 $templateCache.put("uib/template/carousel/carousel.html",
40606 "<div ng-mouseenter=\"pause()\" ng-mouseleave=\"play()\" class=\"carousel\" ng-swipe-right=\"prev()\" ng-swipe-left=\"next()\">\n" +
40607 " <div class=\"carousel-inner\" ng-transclude></div>\n" +
40608 " <a role=\"button\" href class=\"left carousel-control\" ng-click=\"prev()\" ng-show=\"slides.length > 1\">\n" +
40609 " <span aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-left\"></span>\n" +
40610 " <span class=\"sr-only\">previous</span>\n" +
40612 " <a role=\"button\" href class=\"right carousel-control\" ng-click=\"next()\" ng-show=\"slides.length > 1\">\n" +
40613 " <span aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-right\"></span>\n" +
40614 " <span class=\"sr-only\">next</span>\n" +
40616 " <ol class=\"carousel-indicators\" ng-show=\"slides.length > 1\">\n" +
40617 " <li ng-repeat=\"slide in slides | orderBy:indexOfSlide track by $index\" ng-class=\"{ active: isActive(slide) }\" ng-click=\"select(slide)\">\n" +
40618 " <span class=\"sr-only\">slide {{ $index + 1 }} of {{ slides.length }}<span ng-if=\"isActive(slide)\">, currently active</span></span>\n" +
40624 angular.module("uib/template/carousel/slide.html", []).run(["$templateCache", function($templateCache) {
40625 $templateCache.put("uib/template/carousel/slide.html",
40626 "<div ng-class=\"{\n" +
40627 " 'active': active\n" +
40628 " }\" class=\"item text-center\" ng-transclude></div>\n" +
40632 angular.module("uib/template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) {
40633 $templateCache.put("uib/template/datepicker/datepicker.html",
40634 "<div class=\"uib-datepicker\" ng-switch=\"datepickerMode\" role=\"application\" ng-keydown=\"keydown($event)\">\n" +
40635 " <uib-daypicker ng-switch-when=\"day\" tabindex=\"0\"></uib-daypicker>\n" +
40636 " <uib-monthpicker ng-switch-when=\"month\" tabindex=\"0\"></uib-monthpicker>\n" +
40637 " <uib-yearpicker ng-switch-when=\"year\" tabindex=\"0\"></uib-yearpicker>\n" +
40641 angular.module("uib/template/datepicker/day.html", []).run(["$templateCache", function($templateCache) {
40642 $templateCache.put("uib/template/datepicker/day.html",
40643 "<table class=\"uib-daypicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
40646 " <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" +
40647 " <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" +
40648 " <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" +
40651 " <th ng-if=\"showWeeks\" class=\"text-center\"></th>\n" +
40652 " <th ng-repeat=\"label in ::labels track by $index\" class=\"text-center\"><small aria-label=\"{{::label.full}}\">{{::label.abbr}}</small></th>\n" +
40656 " <tr class=\"uib-weeks\" ng-repeat=\"row in rows track by $index\">\n" +
40657 " <td ng-if=\"showWeeks\" class=\"text-center h6\"><em>{{ weekNumbers[$index] }}</em></td>\n" +
40658 " <td ng-repeat=\"dt in row\" class=\"uib-day text-center\" role=\"gridcell\"\n" +
40659 " id=\"{{::dt.uid}}\"\n" +
40660 " ng-class=\"::dt.customClass\">\n" +
40661 " <button type=\"button\" style=\"min-width:100%;\" class=\"btn btn-default btn-sm\"\n" +
40662 " uib-is-class=\"\n" +
40663 " 'btn-info' for selectedDt,\n" +
40664 " 'active' for activeDt\n" +
40666 " ng-click=\"select(dt.date)\"\n" +
40667 " ng-disabled=\"::dt.disabled\"\n" +
40668 " tabindex=\"-1\"><span ng-class=\"::{'text-muted': dt.secondary, 'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
40676 angular.module("uib/template/datepicker/month.html", []).run(["$templateCache", function($templateCache) {
40677 $templateCache.put("uib/template/datepicker/month.html",
40678 "<table class=\"uib-monthpicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
40681 " <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" +
40682 " <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" +
40683 " <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" +
40687 " <tr class=\"uib-months\" ng-repeat=\"row in rows track by $index\">\n" +
40688 " <td ng-repeat=\"dt in row\" class=\"uib-month text-center\" role=\"gridcell\"\n" +
40689 " id=\"{{::dt.uid}}\"\n" +
40690 " ng-class=\"::dt.customClass\">\n" +
40691 " <button type=\"button\" style=\"min-width:100%;\" class=\"btn btn-default\"\n" +
40692 " uib-is-class=\"\n" +
40693 " 'btn-info' for selectedDt,\n" +
40694 " 'active' for activeDt\n" +
40696 " ng-click=\"select(dt.date)\"\n" +
40697 " ng-disabled=\"::dt.disabled\"\n" +
40698 " tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
40706 angular.module("uib/template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) {
40707 $templateCache.put("uib/template/datepicker/popup.html",
40708 "<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" +
40709 " <li ng-transclude></li>\n" +
40710 " <li ng-if=\"showButtonBar\" style=\"padding:10px 9px 2px\" class=\"uib-button-bar\">\n" +
40711 " <span class=\"btn-group pull-left\">\n" +
40712 " <button type=\"button\" class=\"btn btn-sm btn-info uib-datepicker-current\" ng-click=\"select('today')\" ng-disabled=\"isDisabled('today')\">{{ getText('current') }}</button>\n" +
40713 " <button type=\"button\" class=\"btn btn-sm btn-danger uib-clear\" ng-click=\"select(null)\">{{ getText('clear') }}</button>\n" +
40715 " <button type=\"button\" class=\"btn btn-sm btn-success pull-right uib-close\" ng-click=\"close()\">{{ getText('close') }}</button>\n" +
40721 angular.module("uib/template/datepicker/year.html", []).run(["$templateCache", function($templateCache) {
40722 $templateCache.put("uib/template/datepicker/year.html",
40723 "<table class=\"uib-yearpicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
40726 " <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" +
40727 " <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" +
40728 " <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" +
40732 " <tr class=\"uib-years\" ng-repeat=\"row in rows track by $index\">\n" +
40733 " <td ng-repeat=\"dt in row\" class=\"uib-year text-center\" role=\"gridcell\"\n" +
40734 " id=\"{{::dt.uid}}\"\n" +
40735 " ng-class=\"::dt.customClass\">\n" +
40736 " <button type=\"button\" style=\"min-width:100%;\" class=\"btn btn-default\"\n" +
40737 " uib-is-class=\"\n" +
40738 " 'btn-info' for selectedDt,\n" +
40739 " 'active' for activeDt\n" +
40741 " ng-click=\"select(dt.date)\"\n" +
40742 " ng-disabled=\"::dt.disabled\"\n" +
40743 " tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
40751 angular.module("uib/template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) {
40752 $templateCache.put("uib/template/modal/backdrop.html",
40753 "<div class=\"modal-backdrop\"\n" +
40754 " uib-modal-animation-class=\"fade\"\n" +
40755 " modal-in-class=\"in\"\n" +
40756 " ng-style=\"{'z-index': 1040 + (index && 1 || 0) + index*10}\"\n" +
40761 angular.module("uib/template/modal/window.html", []).run(["$templateCache", function($templateCache) {
40762 $templateCache.put("uib/template/modal/window.html",
40763 "<div modal-render=\"{{$isRendered}}\" tabindex=\"-1\" role=\"dialog\" class=\"modal\"\n" +
40764 " uib-modal-animation-class=\"fade\"\n" +
40765 " modal-in-class=\"in\"\n" +
40766 " ng-style=\"{'z-index': 1050 + index*10, display: 'block'}\">\n" +
40767 " <div class=\"modal-dialog\" ng-class=\"size ? 'modal-' + size : ''\"><div class=\"modal-content\" uib-modal-transclude></div></div>\n" +
40772 angular.module("uib/template/pager/pager.html", []).run(["$templateCache", function($templateCache) {
40773 $templateCache.put("uib/template/pager/pager.html",
40774 "<ul class=\"pager\">\n" +
40775 " <li ng-class=\"{disabled: noPrevious()||ngDisabled, previous: align}\"><a href ng-click=\"selectPage(page - 1, $event)\">{{::getText('previous')}}</a></li>\n" +
40776 " <li ng-class=\"{disabled: noNext()||ngDisabled, next: align}\"><a href ng-click=\"selectPage(page + 1, $event)\">{{::getText('next')}}</a></li>\n" +
40781 angular.module("uib/template/pagination/pager.html", []).run(["$templateCache", function($templateCache) {
40782 $templateCache.put("uib/template/pagination/pager.html",
40783 "<ul class=\"pager\">\n" +
40784 " <li ng-class=\"{disabled: noPrevious()||ngDisabled, previous: align}\"><a href ng-click=\"selectPage(page - 1, $event)\">{{::getText('previous')}}</a></li>\n" +
40785 " <li ng-class=\"{disabled: noNext()||ngDisabled, next: align}\"><a href ng-click=\"selectPage(page + 1, $event)\">{{::getText('next')}}</a></li>\n" +
40790 angular.module("uib/template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) {
40791 $templateCache.put("uib/template/pagination/pagination.html",
40792 "<ul class=\"pagination\">\n" +
40793 " <li ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noPrevious()||ngDisabled}\" class=\"pagination-first\"><a href ng-click=\"selectPage(1, $event)\">{{::getText('first')}}</a></li>\n" +
40794 " <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" +
40795 " <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" +
40796 " <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" +
40797 " <li ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noNext()||ngDisabled}\" class=\"pagination-last\"><a href ng-click=\"selectPage(totalPages, $event)\">{{::getText('last')}}</a></li>\n" +
40802 angular.module("uib/template/tooltip/tooltip-html-popup.html", []).run(["$templateCache", function($templateCache) {
40803 $templateCache.put("uib/template/tooltip/tooltip-html-popup.html",
40804 "<div class=\"tooltip\"\n" +
40805 " tooltip-animation-class=\"fade\"\n" +
40806 " uib-tooltip-classes\n" +
40807 " ng-class=\"{ in: isOpen() }\">\n" +
40808 " <div class=\"tooltip-arrow\"></div>\n" +
40809 " <div class=\"tooltip-inner\" ng-bind-html=\"contentExp()\"></div>\n" +
40814 angular.module("uib/template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) {
40815 $templateCache.put("uib/template/tooltip/tooltip-popup.html",
40816 "<div class=\"tooltip\"\n" +
40817 " tooltip-animation-class=\"fade\"\n" +
40818 " uib-tooltip-classes\n" +
40819 " ng-class=\"{ in: isOpen() }\">\n" +
40820 " <div class=\"tooltip-arrow\"></div>\n" +
40821 " <div class=\"tooltip-inner\" ng-bind=\"content\"></div>\n" +
40826 angular.module("uib/template/tooltip/tooltip-template-popup.html", []).run(["$templateCache", function($templateCache) {
40827 $templateCache.put("uib/template/tooltip/tooltip-template-popup.html",
40828 "<div class=\"tooltip\"\n" +
40829 " tooltip-animation-class=\"fade\"\n" +
40830 " uib-tooltip-classes\n" +
40831 " ng-class=\"{ in: isOpen() }\">\n" +
40832 " <div class=\"tooltip-arrow\"></div>\n" +
40833 " <div class=\"tooltip-inner\"\n" +
40834 " uib-tooltip-template-transclude=\"contentExp()\"\n" +
40835 " tooltip-template-transclude-scope=\"originScope()\"></div>\n" +
40840 angular.module("uib/template/popover/popover-html.html", []).run(["$templateCache", function($templateCache) {
40841 $templateCache.put("uib/template/popover/popover-html.html",
40842 "<div class=\"popover\"\n" +
40843 " tooltip-animation-class=\"fade\"\n" +
40844 " uib-tooltip-classes\n" +
40845 " ng-class=\"{ in: isOpen() }\">\n" +
40846 " <div class=\"arrow\"></div>\n" +
40848 " <div class=\"popover-inner\">\n" +
40849 " <h3 class=\"popover-title\" ng-bind=\"title\" ng-if=\"title\"></h3>\n" +
40850 " <div class=\"popover-content\" ng-bind-html=\"contentExp()\"></div>\n" +
40856 angular.module("uib/template/popover/popover-template.html", []).run(["$templateCache", function($templateCache) {
40857 $templateCache.put("uib/template/popover/popover-template.html",
40858 "<div class=\"popover\"\n" +
40859 " tooltip-animation-class=\"fade\"\n" +
40860 " uib-tooltip-classes\n" +
40861 " ng-class=\"{ in: isOpen() }\">\n" +
40862 " <div class=\"arrow\"></div>\n" +
40864 " <div class=\"popover-inner\">\n" +
40865 " <h3 class=\"popover-title\" ng-bind=\"title\" ng-if=\"title\"></h3>\n" +
40866 " <div class=\"popover-content\"\n" +
40867 " uib-tooltip-template-transclude=\"contentExp()\"\n" +
40868 " tooltip-template-transclude-scope=\"originScope()\"></div>\n" +
40874 angular.module("uib/template/popover/popover.html", []).run(["$templateCache", function($templateCache) {
40875 $templateCache.put("uib/template/popover/popover.html",
40876 "<div class=\"popover\"\n" +
40877 " tooltip-animation-class=\"fade\"\n" +
40878 " uib-tooltip-classes\n" +
40879 " ng-class=\"{ in: isOpen() }\">\n" +
40880 " <div class=\"arrow\"></div>\n" +
40882 " <div class=\"popover-inner\">\n" +
40883 " <h3 class=\"popover-title\" ng-bind=\"title\" ng-if=\"title\"></h3>\n" +
40884 " <div class=\"popover-content\" ng-bind=\"content\"></div>\n" +
40890 angular.module("uib/template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) {
40891 $templateCache.put("uib/template/progressbar/bar.html",
40892 "<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" +
40896 angular.module("uib/template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) {
40897 $templateCache.put("uib/template/progressbar/progress.html",
40898 "<div class=\"progress\" ng-transclude aria-labelledby=\"{{::title}}\"></div>");
40901 angular.module("uib/template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) {
40902 $templateCache.put("uib/template/progressbar/progressbar.html",
40903 "<div class=\"progress\">\n" +
40904 " <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" +
40909 angular.module("uib/template/rating/rating.html", []).run(["$templateCache", function($templateCache) {
40910 $templateCache.put("uib/template/rating/rating.html",
40911 "<span ng-mouseleave=\"reset()\" ng-keydown=\"onKeydown($event)\" tabindex=\"0\" role=\"slider\" aria-valuemin=\"0\" aria-valuemax=\"{{range.length}}\" aria-valuenow=\"{{value}}\">\n" +
40912 " <span ng-repeat-start=\"r in range track by $index\" class=\"sr-only\">({{ $index < value ? '*' : ' ' }})</span>\n" +
40913 " <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" +
40918 angular.module("uib/template/tabs/tab.html", []).run(["$templateCache", function($templateCache) {
40919 $templateCache.put("uib/template/tabs/tab.html",
40920 "<li ng-class=\"{active: active, disabled: disabled}\" class=\"uib-tab\">\n" +
40921 " <div ng-click=\"select()\" uib-tab-heading-transclude>{{heading}}</div>\n" +
40926 angular.module("uib/template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) {
40927 $templateCache.put("uib/template/tabs/tabset.html",
40929 " <ul class=\"nav nav-{{type || 'tabs'}}\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" +
40930 " <div class=\"tab-content\">\n" +
40931 " <div class=\"tab-pane\" \n" +
40932 " ng-repeat=\"tab in tabs\" \n" +
40933 " ng-class=\"{active: tab.active}\"\n" +
40934 " uib-tab-content-transclude=\"tab\">\n" +
40941 angular.module("uib/template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) {
40942 $templateCache.put("uib/template/timepicker/timepicker.html",
40943 "<table class=\"uib-timepicker\">\n" +
40945 " <tr class=\"text-center\" ng-show=\"::showSpinners\">\n" +
40946 " <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" +
40947 " <td> </td>\n" +
40948 " <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" +
40949 " <td ng-show=\"showSeconds\"> </td>\n" +
40950 " <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" +
40951 " <td ng-show=\"showMeridian\"></td>\n" +
40954 " <td class=\"form-group uib-time hours\" ng-class=\"{'has-error': invalidHours}\">\n" +
40955 " <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" +
40957 " <td class=\"uib-separator\">:</td>\n" +
40958 " <td class=\"form-group uib-time minutes\" ng-class=\"{'has-error': invalidMinutes}\">\n" +
40959 " <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" +
40961 " <td ng-show=\"showSeconds\" class=\"uib-separator\">:</td>\n" +
40962 " <td class=\"form-group uib-time seconds\" ng-class=\"{'has-error': invalidSeconds}\" ng-show=\"showSeconds\">\n" +
40963 " <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" +
40965 " <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" +
40967 " <tr class=\"text-center\" ng-show=\"::showSpinners\">\n" +
40968 " <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" +
40969 " <td> </td>\n" +
40970 " <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" +
40971 " <td ng-show=\"showSeconds\"> </td>\n" +
40972 " <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" +
40973 " <td ng-show=\"showMeridian\"></td>\n" +
40980 angular.module("uib/template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) {
40981 $templateCache.put("uib/template/typeahead/typeahead-match.html",
40982 "<a href tabindex=\"-1\" ng-bind-html=\"match.label | uibTypeaheadHighlight:query\"></a>\n" +
40986 angular.module("uib/template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) {
40987 $templateCache.put("uib/template/typeahead/typeahead-popup.html",
40988 "<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" +
40989 " <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" +
40990 " <div uib-typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>\n" +
40995 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>'); })
40999 /***/ function(module, exports) {
41004 (function (declares) {
41005 var CommandInfo = (function () {
41006 function CommandInfo(name) {
41009 return CommandInfo;
41011 declares.CommandInfo = CommandInfo;
41012 })(declares = app.declares || (app.declares = {}));
41013 })(app || (app = {}));
41017 (function (services) {
41018 var APIEndPoint = (function () {
41019 function APIEndPoint($resource) {
41020 this.$resource = $resource;
41022 APIEndPoint.prototype.resource = function (endPoint) {
41023 var customAction = {
41027 return this.$resource(endPoint, {}, { customAction: customAction });
41029 APIEndPoint.prototype.getOptionControlFile = function (command) {
41030 var endPoint = '/api/optionControlFile/' + command;
41031 return this.resource(endPoint).get();
41033 APIEndPoint.prototype.getFiles = function (fileId) {
41034 var endPoint = '/api/v1/workspace';
41036 endPoint += '/' + fileId;
41038 return this.resource(endPoint).get();
41040 APIEndPoint.prototype.getDirectories = function () {
41041 var endPoint = '/spi/v1/workspace/';
41042 return this.resource(endPoint).get();
41044 return APIEndPoint;
41046 services.APIEndPoint = APIEndPoint;
41047 })(services = app.services || (app.services = {}));
41048 })(app || (app = {}));
41052 (function (services) {
41053 var MyModal = (function () {
41054 function MyModal($uibModal) {
41055 this.$uibModal = $uibModal;
41056 this.modalOption = {
41063 MyModal.prototype.open = function (modalName) {
41064 if (modalName === 'SelectCommand') {
41065 this.modalOption.templateUrl = 'templates/select-command.html';
41066 this.modalOption.size = 'lg';
41068 return this.$uibModal.open(this.modalOption);
41072 services.MyModal = MyModal;
41073 })(services = app.services || (app.services = {}));
41074 })(app || (app = {}));
41078 (function (directives) {
41079 var Command = (function () {
41080 function Command() {
41081 this.restrict = 'E';
41082 this.replace = true;
41084 this.controller = 'commandController';
41085 this.controllerAs = 'ctrl';
41086 this.bindToController = {
41092 this.templateUrl = 'templates/command.html';
41094 Command.Factory = function () {
41095 var directive = function () {
41096 return new Command();
41098 directive.$inject = [];
41103 directives.Command = Command;
41104 var CommandController = (function () {
41105 function CommandController(APIEndPoint, $scope) {
41106 this.APIEndPoint = APIEndPoint;
41107 this.$scope = $scope;
41108 var controller = this;
41109 console.log(this.list);
41111 .getOptionControlFile('dcdFilePrint')
41113 .then(function (result) {
41114 console.log(result);
41115 controller.options = result.info;
41117 this.files = ['a.file', 'b.file', 'c.file'];
41118 this.heading = "[" + this.index + "]: dcdFilePring";
41119 this.isOpen = true;
41120 this.$scope.$on('close', function () {
41121 controller.isOpen = false;
41124 CommandController.prototype.submit = function () {
41126 angular.forEach(this.options, function (option) {
41128 angular.forEach(option.arg, function (arg) {
41130 inputs.push(arg.input);
41133 if (inputs.length > 0) {
41134 params[option.option] = inputs;
41137 console.log(params);
41139 CommandController.prototype.removeMySelf = function (index) {
41140 this.remove()(index, this.list);
41142 CommandController.$inject = ['APIEndPoint', '$scope'];
41143 return CommandController;
41145 directives.CommandController = CommandController;
41146 })(directives = app.directives || (app.directives = {}));
41147 })(app || (app = {}));
41151 (function (directives) {
41152 var HeaderMenu = (function () {
41153 function HeaderMenu() {
41154 this.restrict = 'E';
41155 this.replace = true;
41156 this.templateUrl = 'templates/header-menu.html';
41158 HeaderMenu.Factory = function () {
41159 var directive = function () {
41160 return new HeaderMenu();
41166 directives.HeaderMenu = HeaderMenu;
41167 })(directives = app.directives || (app.directives = {}));
41168 })(app || (app = {}));
41172 (function (directives) {
41173 var Option = (function () {
41174 function Option() {
41175 this.restrict = 'E';
41176 this.replace = true;
41177 this.controller = 'optionController';
41178 this.bindToController = {
41183 this.templateUrl = 'templates/option.html';
41184 this.controllerAs = 'ctrl';
41186 Option.Factory = function () {
41187 var directive = function () {
41188 return new Option();
41190 directive.$inject = [];
41195 directives.Option = Option;
41196 var OptionController = (function () {
41197 function OptionController() {
41198 var controller = this;
41199 angular.forEach(controller.info.arg, function (arg) {
41200 if (arg.initialValue) {
41201 if (arg.formType === 'number') {
41202 arg.input = parseInt(arg.initialValue);
41205 arg.input = arg.initialValue;
41210 OptionController.$inject = [];
41211 return OptionController;
41213 directives.OptionController = OptionController;
41214 })(directives = app.directives || (app.directives = {}));
41215 })(app || (app = {}));
41219 (function (directives) {
41220 var Directory = (function () {
41221 function Directory() {
41222 this.restrict = 'E';
41223 this.replace = true;
41224 this.controller = 'directoryController';
41225 this.controllerAs = 'ctrl';
41226 this.bindToController = {
41232 this.templateUrl = 'templates/directory.html';
41234 Directory.Factory = function () {
41235 var directive = function () {
41236 return new Directory();
41242 directives.Directory = Directory;
41243 var DirectoryController = (function () {
41244 function DirectoryController(APIEndPoint, $scope) {
41245 this.APIEndPoint = APIEndPoint;
41246 this.$scope = $scope;
41247 var controller = this;
41249 .getFiles(this.info.fileId)
41251 .then(function (result) {
41252 if (result.status === 'success') {
41253 controller.files = result.info;
41254 angular.forEach(result.info, function (file) {
41255 if (file.fileType === '0') {
41257 if (controller.info.path === '/') {
41258 o.path = '/' + file.name;
41261 o.path = controller.info.path + '/' + file.name;
41263 controller.add()(o, controller.list);
41270 DirectoryController.$inject = ['APIEndPoint', '$scope'];
41271 return DirectoryController;
41273 directives.DirectoryController = DirectoryController;
41274 })(directives = app.directives || (app.directives = {}));
41275 })(app || (app = {}));
41279 (function (controllers) {
41280 var Execution = (function () {
41281 function Execution(MyModal, $scope) {
41282 this.MyModal = MyModal;
41283 this.$scope = $scope;
41284 this.commandInfoList = [];
41287 Execution.prototype.add = function () {
41288 this.$scope.$broadcast('close');
41289 this.commandInfoList.push(new app.declares.CommandInfo('dcdFilePrint'));
41291 Execution.prototype.open = function () {
41292 var result = this.MyModal.open('SelectCommand');
41293 console.log(result);
41295 Execution.prototype.remove = function (index, list) {
41296 list.splice(index, 1);
41298 Execution.prototype.close = function () {
41299 console.log("close");
41301 Execution.$inject = ['MyModal', '$scope'];
41304 controllers.Execution = Execution;
41305 })(controllers = app.controllers || (app.controllers = {}));
41306 })(app || (app = {}));
41310 (function (controllers) {
41311 var Workspace = (function () {
41312 function Workspace($scope, APIEndPoint) {
41313 this.$scope = $scope;
41314 this.APIEndPoint = APIEndPoint;
41315 this.directoryList = [];
41316 var controller = this;
41317 var directoryList = this.directoryList;
41319 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
41327 directoryList.push(o);
41329 Workspace.prototype.addDirectory = function (info, directoryList) {
41330 directoryList.push(info);
41332 Workspace.$inject = ['$scope', 'APIEndPoint'];
41335 controllers.Workspace = Workspace;
41336 })(controllers = app.controllers || (app.controllers = {}));
41337 })(app || (app = {}));
41341 (function (controllers) {
41342 var History = (function () {
41343 function History($scope) {
41344 this.page = "History";
41346 History.$inject = ['$scope'];
41349 controllers.History = History;
41350 })(controllers = app.controllers || (app.controllers = {}));
41351 })(app || (app = {}));
41355 var appName = 'zephyr';
41356 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
41357 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
41358 $urlRouterProvider.otherwise('/execution');
41359 $locationProvider.html5Mode({
41364 .state('execution', {
41366 templateUrl: 'templates/execution.html',
41367 controller: 'executionController',
41370 .state('workspace', {
41372 templateUrl: 'templates/workspace.html',
41373 controller: 'workspaceController',
41376 .state('history', {
41378 templateUrl: 'templates/history.html',
41379 controller: 'historyController',
41383 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
41384 app.zephyr.service('MyModal', app.services.MyModal);
41385 app.zephyr.controller('executionController', app.controllers.Execution);
41386 app.zephyr.controller('workspaceController', app.controllers.Workspace);
41387 app.zephyr.controller('historyController', app.controllers.History);
41388 app.zephyr.controller('commandController', app.directives.CommandController);
41389 app.zephyr.controller('optionController', app.directives.OptionController);
41390 app.zephyr.controller('directoryController', app.directives.DirectoryController);
41391 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
41392 app.zephyr.directive('command', app.directives.Command.Factory());
41393 app.zephyr.directive('option', app.directives.Option.Factory());
41394 app.zephyr.directive('directory', app.directives.Directory.Factory());
41395 })(app || (app = {}));
41400 /***/ function(module, exports) {
41405 (function (declares) {
41406 var CommandInfo = (function () {
41407 function CommandInfo(name) {
41410 return CommandInfo;
41412 declares.CommandInfo = CommandInfo;
41413 })(declares = app.declares || (app.declares = {}));
41414 })(app || (app = {}));
41418 (function (services) {
41419 var APIEndPoint = (function () {
41420 function APIEndPoint($resource) {
41421 this.$resource = $resource;
41423 APIEndPoint.prototype.resource = function (endPoint) {
41424 var customAction = {
41428 return this.$resource(endPoint, {}, { customAction: customAction });
41430 APIEndPoint.prototype.getOptionControlFile = function (command) {
41431 var endPoint = '/api/optionControlFile/' + command;
41432 return this.resource(endPoint).get();
41434 APIEndPoint.prototype.getFiles = function (fileId) {
41435 var endPoint = '/api/v1/workspace';
41437 endPoint += '/' + fileId;
41439 return this.resource(endPoint).get();
41441 APIEndPoint.prototype.getDirectories = function () {
41442 var endPoint = '/spi/v1/workspace/';
41443 return this.resource(endPoint).get();
41445 return APIEndPoint;
41447 services.APIEndPoint = APIEndPoint;
41448 })(services = app.services || (app.services = {}));
41449 })(app || (app = {}));
41453 (function (services) {
41454 var MyModal = (function () {
41455 function MyModal($uibModal) {
41456 this.$uibModal = $uibModal;
41457 this.modalOption = {
41464 MyModal.prototype.open = function (modalName) {
41465 if (modalName === 'SelectCommand') {
41466 this.modalOption.templateUrl = 'templates/select-command.html';
41467 this.modalOption.size = 'lg';
41469 return this.$uibModal.open(this.modalOption);
41473 services.MyModal = MyModal;
41474 })(services = app.services || (app.services = {}));
41475 })(app || (app = {}));
41479 (function (directives) {
41480 var Command = (function () {
41481 function Command() {
41482 this.restrict = 'E';
41483 this.replace = true;
41485 this.controller = 'commandController';
41486 this.controllerAs = 'ctrl';
41487 this.bindToController = {
41493 this.templateUrl = 'templates/command.html';
41495 Command.Factory = function () {
41496 var directive = function () {
41497 return new Command();
41499 directive.$inject = [];
41504 directives.Command = Command;
41505 var CommandController = (function () {
41506 function CommandController(APIEndPoint, $scope) {
41507 this.APIEndPoint = APIEndPoint;
41508 this.$scope = $scope;
41509 var controller = this;
41510 console.log(this.list);
41512 .getOptionControlFile('dcdFilePrint')
41514 .then(function (result) {
41515 console.log(result);
41516 controller.options = result.info;
41518 this.files = ['a.file', 'b.file', 'c.file'];
41519 this.heading = "[" + this.index + "]: dcdFilePring";
41520 this.isOpen = true;
41521 this.$scope.$on('close', function () {
41522 controller.isOpen = false;
41525 CommandController.prototype.submit = function () {
41527 angular.forEach(this.options, function (option) {
41529 angular.forEach(option.arg, function (arg) {
41531 inputs.push(arg.input);
41534 if (inputs.length > 0) {
41535 params[option.option] = inputs;
41538 console.log(params);
41540 CommandController.prototype.removeMySelf = function (index) {
41541 this.remove()(index, this.list);
41543 CommandController.$inject = ['APIEndPoint', '$scope'];
41544 return CommandController;
41546 directives.CommandController = CommandController;
41547 })(directives = app.directives || (app.directives = {}));
41548 })(app || (app = {}));
41552 (function (directives) {
41553 var HeaderMenu = (function () {
41554 function HeaderMenu() {
41555 this.restrict = 'E';
41556 this.replace = true;
41557 this.templateUrl = 'templates/header-menu.html';
41559 HeaderMenu.Factory = function () {
41560 var directive = function () {
41561 return new HeaderMenu();
41567 directives.HeaderMenu = HeaderMenu;
41568 })(directives = app.directives || (app.directives = {}));
41569 })(app || (app = {}));
41573 (function (directives) {
41574 var Option = (function () {
41575 function Option() {
41576 this.restrict = 'E';
41577 this.replace = true;
41578 this.controller = 'optionController';
41579 this.bindToController = {
41584 this.templateUrl = 'templates/option.html';
41585 this.controllerAs = 'ctrl';
41587 Option.Factory = function () {
41588 var directive = function () {
41589 return new Option();
41591 directive.$inject = [];
41596 directives.Option = Option;
41597 var OptionController = (function () {
41598 function OptionController() {
41599 var controller = this;
41600 angular.forEach(controller.info.arg, function (arg) {
41601 if (arg.initialValue) {
41602 if (arg.formType === 'number') {
41603 arg.input = parseInt(arg.initialValue);
41606 arg.input = arg.initialValue;
41611 OptionController.$inject = [];
41612 return OptionController;
41614 directives.OptionController = OptionController;
41615 })(directives = app.directives || (app.directives = {}));
41616 })(app || (app = {}));
41620 (function (directives) {
41621 var Directory = (function () {
41622 function Directory() {
41623 this.restrict = 'E';
41624 this.replace = true;
41625 this.controller = 'directoryController';
41626 this.controllerAs = 'ctrl';
41627 this.bindToController = {
41633 this.templateUrl = 'templates/directory.html';
41635 Directory.Factory = function () {
41636 var directive = function () {
41637 return new Directory();
41643 directives.Directory = Directory;
41644 var DirectoryController = (function () {
41645 function DirectoryController(APIEndPoint, $scope) {
41646 this.APIEndPoint = APIEndPoint;
41647 this.$scope = $scope;
41648 var controller = this;
41650 .getFiles(this.info.fileId)
41652 .then(function (result) {
41653 if (result.status === 'success') {
41654 controller.files = result.info;
41655 angular.forEach(result.info, function (file) {
41656 if (file.fileType === '0') {
41658 if (controller.info.path === '/') {
41659 o.path = '/' + file.name;
41662 o.path = controller.info.path + '/' + file.name;
41664 controller.add()(o, controller.list);
41671 DirectoryController.$inject = ['APIEndPoint', '$scope'];
41672 return DirectoryController;
41674 directives.DirectoryController = DirectoryController;
41675 })(directives = app.directives || (app.directives = {}));
41676 })(app || (app = {}));
41680 (function (controllers) {
41681 var Execution = (function () {
41682 function Execution(MyModal, $scope) {
41683 this.MyModal = MyModal;
41684 this.$scope = $scope;
41685 this.commandInfoList = [];
41688 Execution.prototype.add = function () {
41689 this.$scope.$broadcast('close');
41690 this.commandInfoList.push(new app.declares.CommandInfo('dcdFilePrint'));
41692 Execution.prototype.open = function () {
41693 var result = this.MyModal.open('SelectCommand');
41694 console.log(result);
41696 Execution.prototype.remove = function (index, list) {
41697 list.splice(index, 1);
41699 Execution.prototype.close = function () {
41700 console.log("close");
41702 Execution.$inject = ['MyModal', '$scope'];
41705 controllers.Execution = Execution;
41706 })(controllers = app.controllers || (app.controllers = {}));
41707 })(app || (app = {}));
41711 (function (controllers) {
41712 var Workspace = (function () {
41713 function Workspace($scope, APIEndPoint) {
41714 this.$scope = $scope;
41715 this.APIEndPoint = APIEndPoint;
41716 this.directoryList = [];
41717 var controller = this;
41718 var directoryList = this.directoryList;
41720 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
41728 directoryList.push(o);
41730 Workspace.prototype.addDirectory = function (info, directoryList) {
41731 directoryList.push(info);
41733 Workspace.$inject = ['$scope', 'APIEndPoint'];
41736 controllers.Workspace = Workspace;
41737 })(controllers = app.controllers || (app.controllers = {}));
41738 })(app || (app = {}));
41742 (function (controllers) {
41743 var History = (function () {
41744 function History($scope) {
41745 this.page = "History";
41747 History.$inject = ['$scope'];
41750 controllers.History = History;
41751 })(controllers = app.controllers || (app.controllers = {}));
41752 })(app || (app = {}));
41756 var appName = 'zephyr';
41757 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
41758 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
41759 $urlRouterProvider.otherwise('/execution');
41760 $locationProvider.html5Mode({
41765 .state('execution', {
41767 templateUrl: 'templates/execution.html',
41768 controller: 'executionController',
41771 .state('workspace', {
41773 templateUrl: 'templates/workspace.html',
41774 controller: 'workspaceController',
41777 .state('history', {
41779 templateUrl: 'templates/history.html',
41780 controller: 'historyController',
41784 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
41785 app.zephyr.service('MyModal', app.services.MyModal);
41786 app.zephyr.controller('executionController', app.controllers.Execution);
41787 app.zephyr.controller('workspaceController', app.controllers.Workspace);
41788 app.zephyr.controller('historyController', app.controllers.History);
41789 app.zephyr.controller('commandController', app.directives.CommandController);
41790 app.zephyr.controller('optionController', app.directives.OptionController);
41791 app.zephyr.controller('directoryController', app.directives.DirectoryController);
41792 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
41793 app.zephyr.directive('command', app.directives.Command.Factory());
41794 app.zephyr.directive('option', app.directives.Option.Factory());
41795 app.zephyr.directive('directory', app.directives.Directory.Factory());
41796 })(app || (app = {}));
41801 /***/ function(module, exports) {
41806 (function (declares) {
41807 var CommandInfo = (function () {
41808 function CommandInfo(name) {
41811 return CommandInfo;
41813 declares.CommandInfo = CommandInfo;
41814 })(declares = app.declares || (app.declares = {}));
41815 })(app || (app = {}));
41819 (function (services) {
41820 var APIEndPoint = (function () {
41821 function APIEndPoint($resource) {
41822 this.$resource = $resource;
41824 APIEndPoint.prototype.resource = function (endPoint) {
41825 var customAction = {
41829 return this.$resource(endPoint, {}, { customAction: customAction });
41831 APIEndPoint.prototype.getOptionControlFile = function (command) {
41832 var endPoint = '/api/optionControlFile/' + command;
41833 return this.resource(endPoint).get();
41835 APIEndPoint.prototype.getFiles = function (fileId) {
41836 var endPoint = '/api/v1/workspace';
41838 endPoint += '/' + fileId;
41840 return this.resource(endPoint).get();
41842 APIEndPoint.prototype.getDirectories = function () {
41843 var endPoint = '/spi/v1/workspace/';
41844 return this.resource(endPoint).get();
41846 return APIEndPoint;
41848 services.APIEndPoint = APIEndPoint;
41849 })(services = app.services || (app.services = {}));
41850 })(app || (app = {}));
41854 (function (services) {
41855 var MyModal = (function () {
41856 function MyModal($uibModal) {
41857 this.$uibModal = $uibModal;
41858 this.modalOption = {
41865 MyModal.prototype.open = function (modalName) {
41866 if (modalName === 'SelectCommand') {
41867 this.modalOption.templateUrl = 'templates/select-command.html';
41868 this.modalOption.size = 'lg';
41870 return this.$uibModal.open(this.modalOption);
41874 services.MyModal = MyModal;
41875 })(services = app.services || (app.services = {}));
41876 })(app || (app = {}));
41880 (function (directives) {
41881 var Command = (function () {
41882 function Command() {
41883 this.restrict = 'E';
41884 this.replace = true;
41886 this.controller = 'commandController';
41887 this.controllerAs = 'ctrl';
41888 this.bindToController = {
41894 this.templateUrl = 'templates/command.html';
41896 Command.Factory = function () {
41897 var directive = function () {
41898 return new Command();
41900 directive.$inject = [];
41905 directives.Command = Command;
41906 var CommandController = (function () {
41907 function CommandController(APIEndPoint, $scope) {
41908 this.APIEndPoint = APIEndPoint;
41909 this.$scope = $scope;
41910 var controller = this;
41911 console.log(this.list);
41913 .getOptionControlFile('dcdFilePrint')
41915 .then(function (result) {
41916 console.log(result);
41917 controller.options = result.info;
41919 this.files = ['a.file', 'b.file', 'c.file'];
41920 this.heading = "[" + this.index + "]: dcdFilePring";
41921 this.isOpen = true;
41922 this.$scope.$on('close', function () {
41923 controller.isOpen = false;
41926 CommandController.prototype.submit = function () {
41928 angular.forEach(this.options, function (option) {
41930 angular.forEach(option.arg, function (arg) {
41932 inputs.push(arg.input);
41935 if (inputs.length > 0) {
41936 params[option.option] = inputs;
41939 console.log(params);
41941 CommandController.prototype.removeMySelf = function (index) {
41942 this.remove()(index, this.list);
41944 CommandController.$inject = ['APIEndPoint', '$scope'];
41945 return CommandController;
41947 directives.CommandController = CommandController;
41948 })(directives = app.directives || (app.directives = {}));
41949 })(app || (app = {}));
41953 (function (directives) {
41954 var HeaderMenu = (function () {
41955 function HeaderMenu() {
41956 this.restrict = 'E';
41957 this.replace = true;
41958 this.templateUrl = 'templates/header-menu.html';
41960 HeaderMenu.Factory = function () {
41961 var directive = function () {
41962 return new HeaderMenu();
41968 directives.HeaderMenu = HeaderMenu;
41969 })(directives = app.directives || (app.directives = {}));
41970 })(app || (app = {}));
41974 (function (directives) {
41975 var Option = (function () {
41976 function Option() {
41977 this.restrict = 'E';
41978 this.replace = true;
41979 this.controller = 'optionController';
41980 this.bindToController = {
41985 this.templateUrl = 'templates/option.html';
41986 this.controllerAs = 'ctrl';
41988 Option.Factory = function () {
41989 var directive = function () {
41990 return new Option();
41992 directive.$inject = [];
41997 directives.Option = Option;
41998 var OptionController = (function () {
41999 function OptionController() {
42000 var controller = this;
42001 angular.forEach(controller.info.arg, function (arg) {
42002 if (arg.initialValue) {
42003 if (arg.formType === 'number') {
42004 arg.input = parseInt(arg.initialValue);
42007 arg.input = arg.initialValue;
42012 OptionController.$inject = [];
42013 return OptionController;
42015 directives.OptionController = OptionController;
42016 })(directives = app.directives || (app.directives = {}));
42017 })(app || (app = {}));
42021 (function (directives) {
42022 var Directory = (function () {
42023 function Directory() {
42024 this.restrict = 'E';
42025 this.replace = true;
42026 this.controller = 'directoryController';
42027 this.controllerAs = 'ctrl';
42028 this.bindToController = {
42034 this.templateUrl = 'templates/directory.html';
42036 Directory.Factory = function () {
42037 var directive = function () {
42038 return new Directory();
42044 directives.Directory = Directory;
42045 var DirectoryController = (function () {
42046 function DirectoryController(APIEndPoint, $scope) {
42047 this.APIEndPoint = APIEndPoint;
42048 this.$scope = $scope;
42049 var controller = this;
42051 .getFiles(this.info.fileId)
42053 .then(function (result) {
42054 if (result.status === 'success') {
42055 controller.files = result.info;
42056 angular.forEach(result.info, function (file) {
42057 if (file.fileType === '0') {
42059 if (controller.info.path === '/') {
42060 o.path = '/' + file.name;
42063 o.path = controller.info.path + '/' + file.name;
42065 controller.add()(o, controller.list);
42072 DirectoryController.$inject = ['APIEndPoint', '$scope'];
42073 return DirectoryController;
42075 directives.DirectoryController = DirectoryController;
42076 })(directives = app.directives || (app.directives = {}));
42077 })(app || (app = {}));
42081 (function (controllers) {
42082 var Execution = (function () {
42083 function Execution(MyModal, $scope) {
42084 this.MyModal = MyModal;
42085 this.$scope = $scope;
42086 this.commandInfoList = [];
42089 Execution.prototype.add = function () {
42090 this.$scope.$broadcast('close');
42091 this.commandInfoList.push(new app.declares.CommandInfo('dcdFilePrint'));
42093 Execution.prototype.open = function () {
42094 var result = this.MyModal.open('SelectCommand');
42095 console.log(result);
42097 Execution.prototype.remove = function (index, list) {
42098 list.splice(index, 1);
42100 Execution.prototype.close = function () {
42101 console.log("close");
42103 Execution.$inject = ['MyModal', '$scope'];
42106 controllers.Execution = Execution;
42107 })(controllers = app.controllers || (app.controllers = {}));
42108 })(app || (app = {}));
42112 (function (controllers) {
42113 var Workspace = (function () {
42114 function Workspace($scope, APIEndPoint) {
42115 this.$scope = $scope;
42116 this.APIEndPoint = APIEndPoint;
42117 this.directoryList = [];
42118 var controller = this;
42119 var directoryList = this.directoryList;
42121 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
42129 directoryList.push(o);
42131 Workspace.prototype.addDirectory = function (info, directoryList) {
42132 directoryList.push(info);
42134 Workspace.$inject = ['$scope', 'APIEndPoint'];
42137 controllers.Workspace = Workspace;
42138 })(controllers = app.controllers || (app.controllers = {}));
42139 })(app || (app = {}));
42143 (function (controllers) {
42144 var History = (function () {
42145 function History($scope) {
42146 this.page = "History";
42148 History.$inject = ['$scope'];
42151 controllers.History = History;
42152 })(controllers = app.controllers || (app.controllers = {}));
42153 })(app || (app = {}));
42157 var appName = 'zephyr';
42158 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
42159 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
42160 $urlRouterProvider.otherwise('/execution');
42161 $locationProvider.html5Mode({
42166 .state('execution', {
42168 templateUrl: 'templates/execution.html',
42169 controller: 'executionController',
42172 .state('workspace', {
42174 templateUrl: 'templates/workspace.html',
42175 controller: 'workspaceController',
42178 .state('history', {
42180 templateUrl: 'templates/history.html',
42181 controller: 'historyController',
42185 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
42186 app.zephyr.service('MyModal', app.services.MyModal);
42187 app.zephyr.controller('executionController', app.controllers.Execution);
42188 app.zephyr.controller('workspaceController', app.controllers.Workspace);
42189 app.zephyr.controller('historyController', app.controllers.History);
42190 app.zephyr.controller('commandController', app.directives.CommandController);
42191 app.zephyr.controller('optionController', app.directives.OptionController);
42192 app.zephyr.controller('directoryController', app.directives.DirectoryController);
42193 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
42194 app.zephyr.directive('command', app.directives.Command.Factory());
42195 app.zephyr.directive('option', app.directives.Option.Factory());
42196 app.zephyr.directive('directory', app.directives.Directory.Factory());
42197 })(app || (app = {}));
42202 /***/ function(module, exports) {
42207 (function (declares) {
42208 var CommandInfo = (function () {
42209 function CommandInfo(name) {
42212 return CommandInfo;
42214 declares.CommandInfo = CommandInfo;
42215 })(declares = app.declares || (app.declares = {}));
42216 })(app || (app = {}));
42220 (function (services) {
42221 var APIEndPoint = (function () {
42222 function APIEndPoint($resource) {
42223 this.$resource = $resource;
42225 APIEndPoint.prototype.resource = function (endPoint) {
42226 var customAction = {
42230 return this.$resource(endPoint, {}, { customAction: customAction });
42232 APIEndPoint.prototype.getOptionControlFile = function (command) {
42233 var endPoint = '/api/optionControlFile/' + command;
42234 return this.resource(endPoint).get();
42236 APIEndPoint.prototype.getFiles = function (fileId) {
42237 var endPoint = '/api/v1/workspace';
42239 endPoint += '/' + fileId;
42241 return this.resource(endPoint).get();
42243 APIEndPoint.prototype.getDirectories = function () {
42244 var endPoint = '/spi/v1/workspace/';
42245 return this.resource(endPoint).get();
42247 return APIEndPoint;
42249 services.APIEndPoint = APIEndPoint;
42250 })(services = app.services || (app.services = {}));
42251 })(app || (app = {}));
42255 (function (services) {
42256 var MyModal = (function () {
42257 function MyModal($uibModal) {
42258 this.$uibModal = $uibModal;
42259 this.modalOption = {
42266 MyModal.prototype.open = function (modalName) {
42267 if (modalName === 'SelectCommand') {
42268 this.modalOption.templateUrl = 'templates/select-command.html';
42269 this.modalOption.size = 'lg';
42271 return this.$uibModal.open(this.modalOption);
42275 services.MyModal = MyModal;
42276 })(services = app.services || (app.services = {}));
42277 })(app || (app = {}));
42281 (function (directives) {
42282 var Command = (function () {
42283 function Command() {
42284 this.restrict = 'E';
42285 this.replace = true;
42287 this.controller = 'commandController';
42288 this.controllerAs = 'ctrl';
42289 this.bindToController = {
42295 this.templateUrl = 'templates/command.html';
42297 Command.Factory = function () {
42298 var directive = function () {
42299 return new Command();
42301 directive.$inject = [];
42306 directives.Command = Command;
42307 var CommandController = (function () {
42308 function CommandController(APIEndPoint, $scope) {
42309 this.APIEndPoint = APIEndPoint;
42310 this.$scope = $scope;
42311 var controller = this;
42312 console.log(this.list);
42314 .getOptionControlFile('dcdFilePrint')
42316 .then(function (result) {
42317 console.log(result);
42318 controller.options = result.info;
42320 this.files = ['a.file', 'b.file', 'c.file'];
42321 this.heading = "[" + this.index + "]: dcdFilePring";
42322 this.isOpen = true;
42323 this.$scope.$on('close', function () {
42324 controller.isOpen = false;
42327 CommandController.prototype.submit = function () {
42329 angular.forEach(this.options, function (option) {
42331 angular.forEach(option.arg, function (arg) {
42333 inputs.push(arg.input);
42336 if (inputs.length > 0) {
42337 params[option.option] = inputs;
42340 console.log(params);
42342 CommandController.prototype.removeMySelf = function (index) {
42343 this.remove()(index, this.list);
42345 CommandController.$inject = ['APIEndPoint', '$scope'];
42346 return CommandController;
42348 directives.CommandController = CommandController;
42349 })(directives = app.directives || (app.directives = {}));
42350 })(app || (app = {}));
42354 (function (directives) {
42355 var HeaderMenu = (function () {
42356 function HeaderMenu() {
42357 this.restrict = 'E';
42358 this.replace = true;
42359 this.templateUrl = 'templates/header-menu.html';
42361 HeaderMenu.Factory = function () {
42362 var directive = function () {
42363 return new HeaderMenu();
42369 directives.HeaderMenu = HeaderMenu;
42370 })(directives = app.directives || (app.directives = {}));
42371 })(app || (app = {}));
42375 (function (directives) {
42376 var Option = (function () {
42377 function Option() {
42378 this.restrict = 'E';
42379 this.replace = true;
42380 this.controller = 'optionController';
42381 this.bindToController = {
42386 this.templateUrl = 'templates/option.html';
42387 this.controllerAs = 'ctrl';
42389 Option.Factory = function () {
42390 var directive = function () {
42391 return new Option();
42393 directive.$inject = [];
42398 directives.Option = Option;
42399 var OptionController = (function () {
42400 function OptionController() {
42401 var controller = this;
42402 angular.forEach(controller.info.arg, function (arg) {
42403 if (arg.initialValue) {
42404 if (arg.formType === 'number') {
42405 arg.input = parseInt(arg.initialValue);
42408 arg.input = arg.initialValue;
42413 OptionController.$inject = [];
42414 return OptionController;
42416 directives.OptionController = OptionController;
42417 })(directives = app.directives || (app.directives = {}));
42418 })(app || (app = {}));
42422 (function (directives) {
42423 var Directory = (function () {
42424 function Directory() {
42425 this.restrict = 'E';
42426 this.replace = true;
42427 this.controller = 'directoryController';
42428 this.controllerAs = 'ctrl';
42429 this.bindToController = {
42435 this.templateUrl = 'templates/directory.html';
42437 Directory.Factory = function () {
42438 var directive = function () {
42439 return new Directory();
42445 directives.Directory = Directory;
42446 var DirectoryController = (function () {
42447 function DirectoryController(APIEndPoint, $scope) {
42448 this.APIEndPoint = APIEndPoint;
42449 this.$scope = $scope;
42450 var controller = this;
42452 .getFiles(this.info.fileId)
42454 .then(function (result) {
42455 if (result.status === 'success') {
42456 controller.files = result.info;
42457 angular.forEach(result.info, function (file) {
42458 if (file.fileType === '0') {
42460 if (controller.info.path === '/') {
42461 o.path = '/' + file.name;
42464 o.path = controller.info.path + '/' + file.name;
42466 controller.add()(o, controller.list);
42473 DirectoryController.$inject = ['APIEndPoint', '$scope'];
42474 return DirectoryController;
42476 directives.DirectoryController = DirectoryController;
42477 })(directives = app.directives || (app.directives = {}));
42478 })(app || (app = {}));
42482 (function (controllers) {
42483 var Execution = (function () {
42484 function Execution(MyModal, $scope) {
42485 this.MyModal = MyModal;
42486 this.$scope = $scope;
42487 this.commandInfoList = [];
42490 Execution.prototype.add = function () {
42491 this.$scope.$broadcast('close');
42492 this.commandInfoList.push(new app.declares.CommandInfo('dcdFilePrint'));
42494 Execution.prototype.open = function () {
42495 var result = this.MyModal.open('SelectCommand');
42496 console.log(result);
42498 Execution.prototype.remove = function (index, list) {
42499 list.splice(index, 1);
42501 Execution.prototype.close = function () {
42502 console.log("close");
42504 Execution.$inject = ['MyModal', '$scope'];
42507 controllers.Execution = Execution;
42508 })(controllers = app.controllers || (app.controllers = {}));
42509 })(app || (app = {}));
42513 (function (controllers) {
42514 var Workspace = (function () {
42515 function Workspace($scope, APIEndPoint) {
42516 this.$scope = $scope;
42517 this.APIEndPoint = APIEndPoint;
42518 this.directoryList = [];
42519 var controller = this;
42520 var directoryList = this.directoryList;
42522 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
42530 directoryList.push(o);
42532 Workspace.prototype.addDirectory = function (info, directoryList) {
42533 directoryList.push(info);
42535 Workspace.$inject = ['$scope', 'APIEndPoint'];
42538 controllers.Workspace = Workspace;
42539 })(controllers = app.controllers || (app.controllers = {}));
42540 })(app || (app = {}));
42544 (function (controllers) {
42545 var History = (function () {
42546 function History($scope) {
42547 this.page = "History";
42549 History.$inject = ['$scope'];
42552 controllers.History = History;
42553 })(controllers = app.controllers || (app.controllers = {}));
42554 })(app || (app = {}));
42558 var appName = 'zephyr';
42559 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
42560 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
42561 $urlRouterProvider.otherwise('/execution');
42562 $locationProvider.html5Mode({
42567 .state('execution', {
42569 templateUrl: 'templates/execution.html',
42570 controller: 'executionController',
42573 .state('workspace', {
42575 templateUrl: 'templates/workspace.html',
42576 controller: 'workspaceController',
42579 .state('history', {
42581 templateUrl: 'templates/history.html',
42582 controller: 'historyController',
42586 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
42587 app.zephyr.service('MyModal', app.services.MyModal);
42588 app.zephyr.controller('executionController', app.controllers.Execution);
42589 app.zephyr.controller('workspaceController', app.controllers.Workspace);
42590 app.zephyr.controller('historyController', app.controllers.History);
42591 app.zephyr.controller('commandController', app.directives.CommandController);
42592 app.zephyr.controller('optionController', app.directives.OptionController);
42593 app.zephyr.controller('directoryController', app.directives.DirectoryController);
42594 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
42595 app.zephyr.directive('command', app.directives.Command.Factory());
42596 app.zephyr.directive('option', app.directives.Option.Factory());
42597 app.zephyr.directive('directory', app.directives.Directory.Factory());
42598 })(app || (app = {}));
42603 /***/ function(module, exports) {
42608 (function (declares) {
42609 var CommandInfo = (function () {
42610 function CommandInfo(name) {
42613 return CommandInfo;
42615 declares.CommandInfo = CommandInfo;
42616 })(declares = app.declares || (app.declares = {}));
42617 })(app || (app = {}));
42621 (function (services) {
42622 var APIEndPoint = (function () {
42623 function APIEndPoint($resource) {
42624 this.$resource = $resource;
42626 APIEndPoint.prototype.resource = function (endPoint) {
42627 var customAction = {
42631 return this.$resource(endPoint, {}, { customAction: customAction });
42633 APIEndPoint.prototype.getOptionControlFile = function (command) {
42634 var endPoint = '/api/optionControlFile/' + command;
42635 return this.resource(endPoint).get();
42637 APIEndPoint.prototype.getFiles = function (fileId) {
42638 var endPoint = '/api/v1/workspace';
42640 endPoint += '/' + fileId;
42642 return this.resource(endPoint).get();
42644 APIEndPoint.prototype.getDirectories = function () {
42645 var endPoint = '/spi/v1/workspace/';
42646 return this.resource(endPoint).get();
42648 return APIEndPoint;
42650 services.APIEndPoint = APIEndPoint;
42651 })(services = app.services || (app.services = {}));
42652 })(app || (app = {}));
42656 (function (services) {
42657 var MyModal = (function () {
42658 function MyModal($uibModal) {
42659 this.$uibModal = $uibModal;
42660 this.modalOption = {
42667 MyModal.prototype.open = function (modalName) {
42668 if (modalName === 'SelectCommand') {
42669 this.modalOption.templateUrl = 'templates/select-command.html';
42670 this.modalOption.size = 'lg';
42672 return this.$uibModal.open(this.modalOption);
42676 services.MyModal = MyModal;
42677 })(services = app.services || (app.services = {}));
42678 })(app || (app = {}));
42682 (function (directives) {
42683 var Command = (function () {
42684 function Command() {
42685 this.restrict = 'E';
42686 this.replace = true;
42688 this.controller = 'commandController';
42689 this.controllerAs = 'ctrl';
42690 this.bindToController = {
42696 this.templateUrl = 'templates/command.html';
42698 Command.Factory = function () {
42699 var directive = function () {
42700 return new Command();
42702 directive.$inject = [];
42707 directives.Command = Command;
42708 var CommandController = (function () {
42709 function CommandController(APIEndPoint, $scope) {
42710 this.APIEndPoint = APIEndPoint;
42711 this.$scope = $scope;
42712 var controller = this;
42713 console.log(this.list);
42715 .getOptionControlFile('dcdFilePrint')
42717 .then(function (result) {
42718 console.log(result);
42719 controller.options = result.info;
42721 this.files = ['a.file', 'b.file', 'c.file'];
42722 this.heading = "[" + this.index + "]: dcdFilePring";
42723 this.isOpen = true;
42724 this.$scope.$on('close', function () {
42725 controller.isOpen = false;
42728 CommandController.prototype.submit = function () {
42730 angular.forEach(this.options, function (option) {
42732 angular.forEach(option.arg, function (arg) {
42734 inputs.push(arg.input);
42737 if (inputs.length > 0) {
42738 params[option.option] = inputs;
42741 console.log(params);
42743 CommandController.prototype.removeMySelf = function (index) {
42744 this.remove()(index, this.list);
42746 CommandController.$inject = ['APIEndPoint', '$scope'];
42747 return CommandController;
42749 directives.CommandController = CommandController;
42750 })(directives = app.directives || (app.directives = {}));
42751 })(app || (app = {}));
42755 (function (directives) {
42756 var HeaderMenu = (function () {
42757 function HeaderMenu() {
42758 this.restrict = 'E';
42759 this.replace = true;
42760 this.templateUrl = 'templates/header-menu.html';
42762 HeaderMenu.Factory = function () {
42763 var directive = function () {
42764 return new HeaderMenu();
42770 directives.HeaderMenu = HeaderMenu;
42771 })(directives = app.directives || (app.directives = {}));
42772 })(app || (app = {}));
42776 (function (directives) {
42777 var Option = (function () {
42778 function Option() {
42779 this.restrict = 'E';
42780 this.replace = true;
42781 this.controller = 'optionController';
42782 this.bindToController = {
42787 this.templateUrl = 'templates/option.html';
42788 this.controllerAs = 'ctrl';
42790 Option.Factory = function () {
42791 var directive = function () {
42792 return new Option();
42794 directive.$inject = [];
42799 directives.Option = Option;
42800 var OptionController = (function () {
42801 function OptionController() {
42802 var controller = this;
42803 angular.forEach(controller.info.arg, function (arg) {
42804 if (arg.initialValue) {
42805 if (arg.formType === 'number') {
42806 arg.input = parseInt(arg.initialValue);
42809 arg.input = arg.initialValue;
42814 OptionController.$inject = [];
42815 return OptionController;
42817 directives.OptionController = OptionController;
42818 })(directives = app.directives || (app.directives = {}));
42819 })(app || (app = {}));
42823 (function (directives) {
42824 var Directory = (function () {
42825 function Directory() {
42826 this.restrict = 'E';
42827 this.replace = true;
42828 this.controller = 'directoryController';
42829 this.controllerAs = 'ctrl';
42830 this.bindToController = {
42836 this.templateUrl = 'templates/directory.html';
42838 Directory.Factory = function () {
42839 var directive = function () {
42840 return new Directory();
42846 directives.Directory = Directory;
42847 var DirectoryController = (function () {
42848 function DirectoryController(APIEndPoint, $scope) {
42849 this.APIEndPoint = APIEndPoint;
42850 this.$scope = $scope;
42851 var controller = this;
42853 .getFiles(this.info.fileId)
42855 .then(function (result) {
42856 if (result.status === 'success') {
42857 controller.files = result.info;
42858 angular.forEach(result.info, function (file) {
42859 if (file.fileType === '0') {
42861 if (controller.info.path === '/') {
42862 o.path = '/' + file.name;
42865 o.path = controller.info.path + '/' + file.name;
42867 controller.add()(o, controller.list);
42874 DirectoryController.$inject = ['APIEndPoint', '$scope'];
42875 return DirectoryController;
42877 directives.DirectoryController = DirectoryController;
42878 })(directives = app.directives || (app.directives = {}));
42879 })(app || (app = {}));
42883 (function (controllers) {
42884 var Execution = (function () {
42885 function Execution(MyModal, $scope) {
42886 this.MyModal = MyModal;
42887 this.$scope = $scope;
42888 this.commandInfoList = [];
42891 Execution.prototype.add = function () {
42892 this.$scope.$broadcast('close');
42893 this.commandInfoList.push(new app.declares.CommandInfo('dcdFilePrint'));
42895 Execution.prototype.open = function () {
42896 var result = this.MyModal.open('SelectCommand');
42897 console.log(result);
42899 Execution.prototype.remove = function (index, list) {
42900 list.splice(index, 1);
42902 Execution.prototype.close = function () {
42903 console.log("close");
42905 Execution.$inject = ['MyModal', '$scope'];
42908 controllers.Execution = Execution;
42909 })(controllers = app.controllers || (app.controllers = {}));
42910 })(app || (app = {}));
42914 (function (controllers) {
42915 var Workspace = (function () {
42916 function Workspace($scope, APIEndPoint) {
42917 this.$scope = $scope;
42918 this.APIEndPoint = APIEndPoint;
42919 this.directoryList = [];
42920 var controller = this;
42921 var directoryList = this.directoryList;
42923 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
42931 directoryList.push(o);
42933 Workspace.prototype.addDirectory = function (info, directoryList) {
42934 directoryList.push(info);
42936 Workspace.$inject = ['$scope', 'APIEndPoint'];
42939 controllers.Workspace = Workspace;
42940 })(controllers = app.controllers || (app.controllers = {}));
42941 })(app || (app = {}));
42945 (function (controllers) {
42946 var History = (function () {
42947 function History($scope) {
42948 this.page = "History";
42950 History.$inject = ['$scope'];
42953 controllers.History = History;
42954 })(controllers = app.controllers || (app.controllers = {}));
42955 })(app || (app = {}));
42959 var appName = 'zephyr';
42960 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
42961 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
42962 $urlRouterProvider.otherwise('/execution');
42963 $locationProvider.html5Mode({
42968 .state('execution', {
42970 templateUrl: 'templates/execution.html',
42971 controller: 'executionController',
42974 .state('workspace', {
42976 templateUrl: 'templates/workspace.html',
42977 controller: 'workspaceController',
42980 .state('history', {
42982 templateUrl: 'templates/history.html',
42983 controller: 'historyController',
42987 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
42988 app.zephyr.service('MyModal', app.services.MyModal);
42989 app.zephyr.controller('executionController', app.controllers.Execution);
42990 app.zephyr.controller('workspaceController', app.controllers.Workspace);
42991 app.zephyr.controller('historyController', app.controllers.History);
42992 app.zephyr.controller('commandController', app.directives.CommandController);
42993 app.zephyr.controller('optionController', app.directives.OptionController);
42994 app.zephyr.controller('directoryController', app.directives.DirectoryController);
42995 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
42996 app.zephyr.directive('command', app.directives.Command.Factory());
42997 app.zephyr.directive('option', app.directives.Option.Factory());
42998 app.zephyr.directive('directory', app.directives.Directory.Factory());
42999 })(app || (app = {}));
43004 /***/ function(module, exports) {
43009 (function (declares) {
43010 var CommandInfo = (function () {
43011 function CommandInfo(name) {
43014 return CommandInfo;
43016 declares.CommandInfo = CommandInfo;
43017 })(declares = app.declares || (app.declares = {}));
43018 })(app || (app = {}));
43022 (function (services) {
43023 var APIEndPoint = (function () {
43024 function APIEndPoint($resource) {
43025 this.$resource = $resource;
43027 APIEndPoint.prototype.resource = function (endPoint) {
43028 var customAction = {
43032 return this.$resource(endPoint, {}, { customAction: customAction });
43034 APIEndPoint.prototype.getOptionControlFile = function (command) {
43035 var endPoint = '/api/optionControlFile/' + command;
43036 return this.resource(endPoint).get();
43038 APIEndPoint.prototype.getFiles = function (fileId) {
43039 var endPoint = '/api/v1/workspace';
43041 endPoint += '/' + fileId;
43043 return this.resource(endPoint).get();
43045 APIEndPoint.prototype.getDirectories = function () {
43046 var endPoint = '/spi/v1/workspace/';
43047 return this.resource(endPoint).get();
43049 return APIEndPoint;
43051 services.APIEndPoint = APIEndPoint;
43052 })(services = app.services || (app.services = {}));
43053 })(app || (app = {}));
43057 (function (services) {
43058 var MyModal = (function () {
43059 function MyModal($uibModal) {
43060 this.$uibModal = $uibModal;
43061 this.modalOption = {
43068 MyModal.prototype.open = function (modalName) {
43069 if (modalName === 'SelectCommand') {
43070 this.modalOption.templateUrl = 'templates/select-command.html';
43071 this.modalOption.size = 'lg';
43073 return this.$uibModal.open(this.modalOption);
43077 services.MyModal = MyModal;
43078 })(services = app.services || (app.services = {}));
43079 })(app || (app = {}));
43083 (function (directives) {
43084 var Command = (function () {
43085 function Command() {
43086 this.restrict = 'E';
43087 this.replace = true;
43089 this.controller = 'commandController';
43090 this.controllerAs = 'ctrl';
43091 this.bindToController = {
43097 this.templateUrl = 'templates/command.html';
43099 Command.Factory = function () {
43100 var directive = function () {
43101 return new Command();
43103 directive.$inject = [];
43108 directives.Command = Command;
43109 var CommandController = (function () {
43110 function CommandController(APIEndPoint, $scope) {
43111 this.APIEndPoint = APIEndPoint;
43112 this.$scope = $scope;
43113 var controller = this;
43114 console.log(this.list);
43116 .getOptionControlFile('dcdFilePrint')
43118 .then(function (result) {
43119 console.log(result);
43120 controller.options = result.info;
43122 this.files = ['a.file', 'b.file', 'c.file'];
43123 this.heading = "[" + this.index + "]: dcdFilePring";
43124 this.isOpen = true;
43125 this.$scope.$on('close', function () {
43126 controller.isOpen = false;
43129 CommandController.prototype.submit = function () {
43131 angular.forEach(this.options, function (option) {
43133 angular.forEach(option.arg, function (arg) {
43135 inputs.push(arg.input);
43138 if (inputs.length > 0) {
43139 params[option.option] = inputs;
43142 console.log(params);
43144 CommandController.prototype.removeMySelf = function (index) {
43145 this.remove()(index, this.list);
43147 CommandController.$inject = ['APIEndPoint', '$scope'];
43148 return CommandController;
43150 directives.CommandController = CommandController;
43151 })(directives = app.directives || (app.directives = {}));
43152 })(app || (app = {}));
43156 (function (directives) {
43157 var HeaderMenu = (function () {
43158 function HeaderMenu() {
43159 this.restrict = 'E';
43160 this.replace = true;
43161 this.templateUrl = 'templates/header-menu.html';
43163 HeaderMenu.Factory = function () {
43164 var directive = function () {
43165 return new HeaderMenu();
43171 directives.HeaderMenu = HeaderMenu;
43172 })(directives = app.directives || (app.directives = {}));
43173 })(app || (app = {}));
43177 (function (directives) {
43178 var Option = (function () {
43179 function Option() {
43180 this.restrict = 'E';
43181 this.replace = true;
43182 this.controller = 'optionController';
43183 this.bindToController = {
43188 this.templateUrl = 'templates/option.html';
43189 this.controllerAs = 'ctrl';
43191 Option.Factory = function () {
43192 var directive = function () {
43193 return new Option();
43195 directive.$inject = [];
43200 directives.Option = Option;
43201 var OptionController = (function () {
43202 function OptionController() {
43203 var controller = this;
43204 angular.forEach(controller.info.arg, function (arg) {
43205 if (arg.initialValue) {
43206 if (arg.formType === 'number') {
43207 arg.input = parseInt(arg.initialValue);
43210 arg.input = arg.initialValue;
43215 OptionController.$inject = [];
43216 return OptionController;
43218 directives.OptionController = OptionController;
43219 })(directives = app.directives || (app.directives = {}));
43220 })(app || (app = {}));
43224 (function (directives) {
43225 var Directory = (function () {
43226 function Directory() {
43227 this.restrict = 'E';
43228 this.replace = true;
43229 this.controller = 'directoryController';
43230 this.controllerAs = 'ctrl';
43231 this.bindToController = {
43237 this.templateUrl = 'templates/directory.html';
43239 Directory.Factory = function () {
43240 var directive = function () {
43241 return new Directory();
43247 directives.Directory = Directory;
43248 var DirectoryController = (function () {
43249 function DirectoryController(APIEndPoint, $scope) {
43250 this.APIEndPoint = APIEndPoint;
43251 this.$scope = $scope;
43252 var controller = this;
43254 .getFiles(this.info.fileId)
43256 .then(function (result) {
43257 if (result.status === 'success') {
43258 controller.files = result.info;
43259 angular.forEach(result.info, function (file) {
43260 if (file.fileType === '0') {
43262 if (controller.info.path === '/') {
43263 o.path = '/' + file.name;
43266 o.path = controller.info.path + '/' + file.name;
43268 controller.add()(o, controller.list);
43275 DirectoryController.$inject = ['APIEndPoint', '$scope'];
43276 return DirectoryController;
43278 directives.DirectoryController = DirectoryController;
43279 })(directives = app.directives || (app.directives = {}));
43280 })(app || (app = {}));
43284 (function (controllers) {
43285 var Execution = (function () {
43286 function Execution(MyModal, $scope) {
43287 this.MyModal = MyModal;
43288 this.$scope = $scope;
43289 this.commandInfoList = [];
43292 Execution.prototype.add = function () {
43293 this.$scope.$broadcast('close');
43294 this.commandInfoList.push(new app.declares.CommandInfo('dcdFilePrint'));
43296 Execution.prototype.open = function () {
43297 var result = this.MyModal.open('SelectCommand');
43298 console.log(result);
43300 Execution.prototype.remove = function (index, list) {
43301 list.splice(index, 1);
43303 Execution.prototype.close = function () {
43304 console.log("close");
43306 Execution.$inject = ['MyModal', '$scope'];
43309 controllers.Execution = Execution;
43310 })(controllers = app.controllers || (app.controllers = {}));
43311 })(app || (app = {}));
43315 (function (controllers) {
43316 var Workspace = (function () {
43317 function Workspace($scope, APIEndPoint) {
43318 this.$scope = $scope;
43319 this.APIEndPoint = APIEndPoint;
43320 this.directoryList = [];
43321 var controller = this;
43322 var directoryList = this.directoryList;
43324 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
43332 directoryList.push(o);
43334 Workspace.prototype.addDirectory = function (info, directoryList) {
43335 directoryList.push(info);
43337 Workspace.$inject = ['$scope', 'APIEndPoint'];
43340 controllers.Workspace = Workspace;
43341 })(controllers = app.controllers || (app.controllers = {}));
43342 })(app || (app = {}));
43346 (function (controllers) {
43347 var History = (function () {
43348 function History($scope) {
43349 this.page = "History";
43351 History.$inject = ['$scope'];
43354 controllers.History = History;
43355 })(controllers = app.controllers || (app.controllers = {}));
43356 })(app || (app = {}));
43360 var appName = 'zephyr';
43361 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
43362 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
43363 $urlRouterProvider.otherwise('/execution');
43364 $locationProvider.html5Mode({
43369 .state('execution', {
43371 templateUrl: 'templates/execution.html',
43372 controller: 'executionController',
43375 .state('workspace', {
43377 templateUrl: 'templates/workspace.html',
43378 controller: 'workspaceController',
43381 .state('history', {
43383 templateUrl: 'templates/history.html',
43384 controller: 'historyController',
43388 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
43389 app.zephyr.service('MyModal', app.services.MyModal);
43390 app.zephyr.controller('executionController', app.controllers.Execution);
43391 app.zephyr.controller('workspaceController', app.controllers.Workspace);
43392 app.zephyr.controller('historyController', app.controllers.History);
43393 app.zephyr.controller('commandController', app.directives.CommandController);
43394 app.zephyr.controller('optionController', app.directives.OptionController);
43395 app.zephyr.controller('directoryController', app.directives.DirectoryController);
43396 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
43397 app.zephyr.directive('command', app.directives.Command.Factory());
43398 app.zephyr.directive('option', app.directives.Option.Factory());
43399 app.zephyr.directive('directory', app.directives.Directory.Factory());
43400 })(app || (app = {}));
43405 /***/ function(module, exports) {
43410 (function (declares) {
43411 var CommandInfo = (function () {
43412 function CommandInfo(name) {
43415 return CommandInfo;
43417 declares.CommandInfo = CommandInfo;
43418 })(declares = app.declares || (app.declares = {}));
43419 })(app || (app = {}));
43423 (function (services) {
43424 var APIEndPoint = (function () {
43425 function APIEndPoint($resource) {
43426 this.$resource = $resource;
43428 APIEndPoint.prototype.resource = function (endPoint) {
43429 var customAction = {
43433 return this.$resource(endPoint, {}, { customAction: customAction });
43435 APIEndPoint.prototype.getOptionControlFile = function (command) {
43436 var endPoint = '/api/optionControlFile/' + command;
43437 return this.resource(endPoint).get();
43439 APIEndPoint.prototype.getFiles = function (fileId) {
43440 var endPoint = '/api/v1/workspace';
43442 endPoint += '/' + fileId;
43444 return this.resource(endPoint).get();
43446 APIEndPoint.prototype.getDirectories = function () {
43447 var endPoint = '/spi/v1/workspace/';
43448 return this.resource(endPoint).get();
43450 return APIEndPoint;
43452 services.APIEndPoint = APIEndPoint;
43453 })(services = app.services || (app.services = {}));
43454 })(app || (app = {}));
43458 (function (services) {
43459 var MyModal = (function () {
43460 function MyModal($uibModal) {
43461 this.$uibModal = $uibModal;
43462 this.modalOption = {
43469 MyModal.prototype.open = function (modalName) {
43470 if (modalName === 'SelectCommand') {
43471 this.modalOption.templateUrl = 'templates/select-command.html';
43472 this.modalOption.size = 'lg';
43474 return this.$uibModal.open(this.modalOption);
43478 services.MyModal = MyModal;
43479 })(services = app.services || (app.services = {}));
43480 })(app || (app = {}));
43484 (function (directives) {
43485 var Command = (function () {
43486 function Command() {
43487 this.restrict = 'E';
43488 this.replace = true;
43490 this.controller = 'commandController';
43491 this.controllerAs = 'ctrl';
43492 this.bindToController = {
43498 this.templateUrl = 'templates/command.html';
43500 Command.Factory = function () {
43501 var directive = function () {
43502 return new Command();
43504 directive.$inject = [];
43509 directives.Command = Command;
43510 var CommandController = (function () {
43511 function CommandController(APIEndPoint, $scope) {
43512 this.APIEndPoint = APIEndPoint;
43513 this.$scope = $scope;
43514 var controller = this;
43515 console.log(this.list);
43517 .getOptionControlFile('dcdFilePrint')
43519 .then(function (result) {
43520 console.log(result);
43521 controller.options = result.info;
43523 this.files = ['a.file', 'b.file', 'c.file'];
43524 this.heading = "[" + this.index + "]: dcdFilePring";
43525 this.isOpen = true;
43526 this.$scope.$on('close', function () {
43527 controller.isOpen = false;
43530 CommandController.prototype.submit = function () {
43532 angular.forEach(this.options, function (option) {
43534 angular.forEach(option.arg, function (arg) {
43536 inputs.push(arg.input);
43539 if (inputs.length > 0) {
43540 params[option.option] = inputs;
43543 console.log(params);
43545 CommandController.prototype.removeMySelf = function (index) {
43546 this.remove()(index, this.list);
43548 CommandController.$inject = ['APIEndPoint', '$scope'];
43549 return CommandController;
43551 directives.CommandController = CommandController;
43552 })(directives = app.directives || (app.directives = {}));
43553 })(app || (app = {}));
43557 (function (directives) {
43558 var HeaderMenu = (function () {
43559 function HeaderMenu() {
43560 this.restrict = 'E';
43561 this.replace = true;
43562 this.templateUrl = 'templates/header-menu.html';
43564 HeaderMenu.Factory = function () {
43565 var directive = function () {
43566 return new HeaderMenu();
43572 directives.HeaderMenu = HeaderMenu;
43573 })(directives = app.directives || (app.directives = {}));
43574 })(app || (app = {}));
43578 (function (directives) {
43579 var Option = (function () {
43580 function Option() {
43581 this.restrict = 'E';
43582 this.replace = true;
43583 this.controller = 'optionController';
43584 this.bindToController = {
43589 this.templateUrl = 'templates/option.html';
43590 this.controllerAs = 'ctrl';
43592 Option.Factory = function () {
43593 var directive = function () {
43594 return new Option();
43596 directive.$inject = [];
43601 directives.Option = Option;
43602 var OptionController = (function () {
43603 function OptionController() {
43604 var controller = this;
43605 angular.forEach(controller.info.arg, function (arg) {
43606 if (arg.initialValue) {
43607 if (arg.formType === 'number') {
43608 arg.input = parseInt(arg.initialValue);
43611 arg.input = arg.initialValue;
43616 OptionController.$inject = [];
43617 return OptionController;
43619 directives.OptionController = OptionController;
43620 })(directives = app.directives || (app.directives = {}));
43621 })(app || (app = {}));
43625 (function (directives) {
43626 var Directory = (function () {
43627 function Directory() {
43628 this.restrict = 'E';
43629 this.replace = true;
43630 this.controller = 'directoryController';
43631 this.controllerAs = 'ctrl';
43632 this.bindToController = {
43638 this.templateUrl = 'templates/directory.html';
43640 Directory.Factory = function () {
43641 var directive = function () {
43642 return new Directory();
43648 directives.Directory = Directory;
43649 var DirectoryController = (function () {
43650 function DirectoryController(APIEndPoint, $scope) {
43651 this.APIEndPoint = APIEndPoint;
43652 this.$scope = $scope;
43653 var controller = this;
43655 .getFiles(this.info.fileId)
43657 .then(function (result) {
43658 if (result.status === 'success') {
43659 controller.files = result.info;
43660 angular.forEach(result.info, function (file) {
43661 if (file.fileType === '0') {
43663 if (controller.info.path === '/') {
43664 o.path = '/' + file.name;
43667 o.path = controller.info.path + '/' + file.name;
43669 controller.add()(o, controller.list);
43676 DirectoryController.$inject = ['APIEndPoint', '$scope'];
43677 return DirectoryController;
43679 directives.DirectoryController = DirectoryController;
43680 })(directives = app.directives || (app.directives = {}));
43681 })(app || (app = {}));
43685 (function (controllers) {
43686 var Execution = (function () {
43687 function Execution(MyModal, $scope) {
43688 this.MyModal = MyModal;
43689 this.$scope = $scope;
43690 this.commandInfoList = [];
43693 Execution.prototype.add = function () {
43694 this.$scope.$broadcast('close');
43695 this.commandInfoList.push(new app.declares.CommandInfo('dcdFilePrint'));
43697 Execution.prototype.open = function () {
43698 var result = this.MyModal.open('SelectCommand');
43699 console.log(result);
43701 Execution.prototype.remove = function (index, list) {
43702 list.splice(index, 1);
43704 Execution.prototype.close = function () {
43705 console.log("close");
43707 Execution.$inject = ['MyModal', '$scope'];
43710 controllers.Execution = Execution;
43711 })(controllers = app.controllers || (app.controllers = {}));
43712 })(app || (app = {}));
43716 (function (controllers) {
43717 var Workspace = (function () {
43718 function Workspace($scope, APIEndPoint) {
43719 this.$scope = $scope;
43720 this.APIEndPoint = APIEndPoint;
43721 this.directoryList = [];
43722 var controller = this;
43723 var directoryList = this.directoryList;
43725 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
43733 directoryList.push(o);
43735 Workspace.prototype.addDirectory = function (info, directoryList) {
43736 directoryList.push(info);
43738 Workspace.$inject = ['$scope', 'APIEndPoint'];
43741 controllers.Workspace = Workspace;
43742 })(controllers = app.controllers || (app.controllers = {}));
43743 })(app || (app = {}));
43747 (function (controllers) {
43748 var History = (function () {
43749 function History($scope) {
43750 this.page = "History";
43752 History.$inject = ['$scope'];
43755 controllers.History = History;
43756 })(controllers = app.controllers || (app.controllers = {}));
43757 })(app || (app = {}));
43761 var appName = 'zephyr';
43762 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
43763 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
43764 $urlRouterProvider.otherwise('/execution');
43765 $locationProvider.html5Mode({
43770 .state('execution', {
43772 templateUrl: 'templates/execution.html',
43773 controller: 'executionController',
43776 .state('workspace', {
43778 templateUrl: 'templates/workspace.html',
43779 controller: 'workspaceController',
43782 .state('history', {
43784 templateUrl: 'templates/history.html',
43785 controller: 'historyController',
43789 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
43790 app.zephyr.service('MyModal', app.services.MyModal);
43791 app.zephyr.controller('executionController', app.controllers.Execution);
43792 app.zephyr.controller('workspaceController', app.controllers.Workspace);
43793 app.zephyr.controller('historyController', app.controllers.History);
43794 app.zephyr.controller('commandController', app.directives.CommandController);
43795 app.zephyr.controller('optionController', app.directives.OptionController);
43796 app.zephyr.controller('directoryController', app.directives.DirectoryController);
43797 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
43798 app.zephyr.directive('command', app.directives.Command.Factory());
43799 app.zephyr.directive('option', app.directives.Option.Factory());
43800 app.zephyr.directive('directory', app.directives.Directory.Factory());
43801 })(app || (app = {}));
43806 /***/ function(module, exports) {
43811 (function (declares) {
43812 var CommandInfo = (function () {
43813 function CommandInfo(name) {
43816 return CommandInfo;
43818 declares.CommandInfo = CommandInfo;
43819 })(declares = app.declares || (app.declares = {}));
43820 })(app || (app = {}));
43824 (function (services) {
43825 var APIEndPoint = (function () {
43826 function APIEndPoint($resource) {
43827 this.$resource = $resource;
43829 APIEndPoint.prototype.resource = function (endPoint) {
43830 var customAction = {
43834 return this.$resource(endPoint, {}, { customAction: customAction });
43836 APIEndPoint.prototype.getOptionControlFile = function (command) {
43837 var endPoint = '/api/optionControlFile/' + command;
43838 return this.resource(endPoint).get();
43840 APIEndPoint.prototype.getFiles = function (fileId) {
43841 var endPoint = '/api/v1/workspace';
43843 endPoint += '/' + fileId;
43845 return this.resource(endPoint).get();
43847 APIEndPoint.prototype.getDirectories = function () {
43848 var endPoint = '/spi/v1/workspace/';
43849 return this.resource(endPoint).get();
43851 return APIEndPoint;
43853 services.APIEndPoint = APIEndPoint;
43854 })(services = app.services || (app.services = {}));
43855 })(app || (app = {}));
43859 (function (services) {
43860 var MyModal = (function () {
43861 function MyModal($uibModal) {
43862 this.$uibModal = $uibModal;
43863 this.modalOption = {
43870 MyModal.prototype.open = function (modalName) {
43871 if (modalName === 'SelectCommand') {
43872 this.modalOption.templateUrl = 'templates/select-command.html';
43873 this.modalOption.size = 'lg';
43875 return this.$uibModal.open(this.modalOption);
43879 services.MyModal = MyModal;
43880 })(services = app.services || (app.services = {}));
43881 })(app || (app = {}));
43885 (function (directives) {
43886 var Command = (function () {
43887 function Command() {
43888 this.restrict = 'E';
43889 this.replace = true;
43891 this.controller = 'commandController';
43892 this.controllerAs = 'ctrl';
43893 this.bindToController = {
43899 this.templateUrl = 'templates/command.html';
43901 Command.Factory = function () {
43902 var directive = function () {
43903 return new Command();
43905 directive.$inject = [];
43910 directives.Command = Command;
43911 var CommandController = (function () {
43912 function CommandController(APIEndPoint, $scope) {
43913 this.APIEndPoint = APIEndPoint;
43914 this.$scope = $scope;
43915 var controller = this;
43916 console.log(this.list);
43918 .getOptionControlFile('dcdFilePrint')
43920 .then(function (result) {
43921 console.log(result);
43922 controller.options = result.info;
43924 this.files = ['a.file', 'b.file', 'c.file'];
43925 this.heading = "[" + this.index + "]: dcdFilePring";
43926 this.isOpen = true;
43927 this.$scope.$on('close', function () {
43928 controller.isOpen = false;
43931 CommandController.prototype.submit = function () {
43933 angular.forEach(this.options, function (option) {
43935 angular.forEach(option.arg, function (arg) {
43937 inputs.push(arg.input);
43940 if (inputs.length > 0) {
43941 params[option.option] = inputs;
43944 console.log(params);
43946 CommandController.prototype.removeMySelf = function (index) {
43947 this.remove()(index, this.list);
43949 CommandController.$inject = ['APIEndPoint', '$scope'];
43950 return CommandController;
43952 directives.CommandController = CommandController;
43953 })(directives = app.directives || (app.directives = {}));
43954 })(app || (app = {}));
43958 (function (directives) {
43959 var HeaderMenu = (function () {
43960 function HeaderMenu() {
43961 this.restrict = 'E';
43962 this.replace = true;
43963 this.templateUrl = 'templates/header-menu.html';
43965 HeaderMenu.Factory = function () {
43966 var directive = function () {
43967 return new HeaderMenu();
43973 directives.HeaderMenu = HeaderMenu;
43974 })(directives = app.directives || (app.directives = {}));
43975 })(app || (app = {}));
43979 (function (directives) {
43980 var Option = (function () {
43981 function Option() {
43982 this.restrict = 'E';
43983 this.replace = true;
43984 this.controller = 'optionController';
43985 this.bindToController = {
43990 this.templateUrl = 'templates/option.html';
43991 this.controllerAs = 'ctrl';
43993 Option.Factory = function () {
43994 var directive = function () {
43995 return new Option();
43997 directive.$inject = [];
44002 directives.Option = Option;
44003 var OptionController = (function () {
44004 function OptionController() {
44005 var controller = this;
44006 angular.forEach(controller.info.arg, function (arg) {
44007 if (arg.initialValue) {
44008 if (arg.formType === 'number') {
44009 arg.input = parseInt(arg.initialValue);
44012 arg.input = arg.initialValue;
44017 OptionController.$inject = [];
44018 return OptionController;
44020 directives.OptionController = OptionController;
44021 })(directives = app.directives || (app.directives = {}));
44022 })(app || (app = {}));
44026 (function (directives) {
44027 var Directory = (function () {
44028 function Directory() {
44029 this.restrict = 'E';
44030 this.replace = true;
44031 this.controller = 'directoryController';
44032 this.controllerAs = 'ctrl';
44033 this.bindToController = {
44039 this.templateUrl = 'templates/directory.html';
44041 Directory.Factory = function () {
44042 var directive = function () {
44043 return new Directory();
44049 directives.Directory = Directory;
44050 var DirectoryController = (function () {
44051 function DirectoryController(APIEndPoint, $scope) {
44052 this.APIEndPoint = APIEndPoint;
44053 this.$scope = $scope;
44054 var controller = this;
44056 .getFiles(this.info.fileId)
44058 .then(function (result) {
44059 if (result.status === 'success') {
44060 controller.files = result.info;
44061 angular.forEach(result.info, function (file) {
44062 if (file.fileType === '0') {
44064 if (controller.info.path === '/') {
44065 o.path = '/' + file.name;
44068 o.path = controller.info.path + '/' + file.name;
44070 controller.add()(o, controller.list);
44077 DirectoryController.$inject = ['APIEndPoint', '$scope'];
44078 return DirectoryController;
44080 directives.DirectoryController = DirectoryController;
44081 })(directives = app.directives || (app.directives = {}));
44082 })(app || (app = {}));
44086 (function (controllers) {
44087 var Execution = (function () {
44088 function Execution(MyModal, $scope) {
44089 this.MyModal = MyModal;
44090 this.$scope = $scope;
44091 this.commandInfoList = [];
44094 Execution.prototype.add = function () {
44095 this.$scope.$broadcast('close');
44096 this.commandInfoList.push(new app.declares.CommandInfo('dcdFilePrint'));
44098 Execution.prototype.open = function () {
44099 var result = this.MyModal.open('SelectCommand');
44100 console.log(result);
44102 Execution.prototype.remove = function (index, list) {
44103 list.splice(index, 1);
44105 Execution.prototype.close = function () {
44106 console.log("close");
44108 Execution.$inject = ['MyModal', '$scope'];
44111 controllers.Execution = Execution;
44112 })(controllers = app.controllers || (app.controllers = {}));
44113 })(app || (app = {}));
44117 (function (controllers) {
44118 var Workspace = (function () {
44119 function Workspace($scope, APIEndPoint) {
44120 this.$scope = $scope;
44121 this.APIEndPoint = APIEndPoint;
44122 this.directoryList = [];
44123 var controller = this;
44124 var directoryList = this.directoryList;
44126 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
44134 directoryList.push(o);
44136 Workspace.prototype.addDirectory = function (info, directoryList) {
44137 directoryList.push(info);
44139 Workspace.$inject = ['$scope', 'APIEndPoint'];
44142 controllers.Workspace = Workspace;
44143 })(controllers = app.controllers || (app.controllers = {}));
44144 })(app || (app = {}));
44148 (function (controllers) {
44149 var History = (function () {
44150 function History($scope) {
44151 this.page = "History";
44153 History.$inject = ['$scope'];
44156 controllers.History = History;
44157 })(controllers = app.controllers || (app.controllers = {}));
44158 })(app || (app = {}));
44162 var appName = 'zephyr';
44163 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
44164 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
44165 $urlRouterProvider.otherwise('/execution');
44166 $locationProvider.html5Mode({
44171 .state('execution', {
44173 templateUrl: 'templates/execution.html',
44174 controller: 'executionController',
44177 .state('workspace', {
44179 templateUrl: 'templates/workspace.html',
44180 controller: 'workspaceController',
44183 .state('history', {
44185 templateUrl: 'templates/history.html',
44186 controller: 'historyController',
44190 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
44191 app.zephyr.service('MyModal', app.services.MyModal);
44192 app.zephyr.controller('executionController', app.controllers.Execution);
44193 app.zephyr.controller('workspaceController', app.controllers.Workspace);
44194 app.zephyr.controller('historyController', app.controllers.History);
44195 app.zephyr.controller('commandController', app.directives.CommandController);
44196 app.zephyr.controller('optionController', app.directives.OptionController);
44197 app.zephyr.controller('directoryController', app.directives.DirectoryController);
44198 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
44199 app.zephyr.directive('command', app.directives.Command.Factory());
44200 app.zephyr.directive('option', app.directives.Option.Factory());
44201 app.zephyr.directive('directory', app.directives.Directory.Factory());
44202 })(app || (app = {}));
44207 /***/ function(module, exports) {
44212 (function (declares) {
44213 var CommandInfo = (function () {
44214 function CommandInfo(name) {
44217 return CommandInfo;
44219 declares.CommandInfo = CommandInfo;
44220 })(declares = app.declares || (app.declares = {}));
44221 })(app || (app = {}));
44225 (function (services) {
44226 var APIEndPoint = (function () {
44227 function APIEndPoint($resource) {
44228 this.$resource = $resource;
44230 APIEndPoint.prototype.resource = function (endPoint) {
44231 var customAction = {
44235 return this.$resource(endPoint, {}, { customAction: customAction });
44237 APIEndPoint.prototype.getOptionControlFile = function (command) {
44238 var endPoint = '/api/optionControlFile/' + command;
44239 return this.resource(endPoint).get();
44241 APIEndPoint.prototype.getFiles = function (fileId) {
44242 var endPoint = '/api/v1/workspace';
44244 endPoint += '/' + fileId;
44246 return this.resource(endPoint).get();
44248 APIEndPoint.prototype.getDirectories = function () {
44249 var endPoint = '/spi/v1/workspace/';
44250 return this.resource(endPoint).get();
44252 return APIEndPoint;
44254 services.APIEndPoint = APIEndPoint;
44255 })(services = app.services || (app.services = {}));
44256 })(app || (app = {}));
44260 (function (services) {
44261 var MyModal = (function () {
44262 function MyModal($uibModal) {
44263 this.$uibModal = $uibModal;
44264 this.modalOption = {
44271 MyModal.prototype.open = function (modalName) {
44272 if (modalName === 'SelectCommand') {
44273 this.modalOption.templateUrl = 'templates/select-command.html';
44274 this.modalOption.size = 'lg';
44276 return this.$uibModal.open(this.modalOption);
44280 services.MyModal = MyModal;
44281 })(services = app.services || (app.services = {}));
44282 })(app || (app = {}));
44286 (function (directives) {
44287 var Command = (function () {
44288 function Command() {
44289 this.restrict = 'E';
44290 this.replace = true;
44292 this.controller = 'commandController';
44293 this.controllerAs = 'ctrl';
44294 this.bindToController = {
44300 this.templateUrl = 'templates/command.html';
44302 Command.Factory = function () {
44303 var directive = function () {
44304 return new Command();
44306 directive.$inject = [];
44311 directives.Command = Command;
44312 var CommandController = (function () {
44313 function CommandController(APIEndPoint, $scope) {
44314 this.APIEndPoint = APIEndPoint;
44315 this.$scope = $scope;
44316 var controller = this;
44317 console.log(this.list);
44319 .getOptionControlFile('dcdFilePrint')
44321 .then(function (result) {
44322 console.log(result);
44323 controller.options = result.info;
44325 this.files = ['a.file', 'b.file', 'c.file'];
44326 this.heading = "[" + this.index + "]: dcdFilePring";
44327 this.isOpen = true;
44328 this.$scope.$on('close', function () {
44329 controller.isOpen = false;
44332 CommandController.prototype.submit = function () {
44334 angular.forEach(this.options, function (option) {
44336 angular.forEach(option.arg, function (arg) {
44338 inputs.push(arg.input);
44341 if (inputs.length > 0) {
44342 params[option.option] = inputs;
44345 console.log(params);
44347 CommandController.prototype.removeMySelf = function (index) {
44348 this.remove()(index, this.list);
44350 CommandController.$inject = ['APIEndPoint', '$scope'];
44351 return CommandController;
44353 directives.CommandController = CommandController;
44354 })(directives = app.directives || (app.directives = {}));
44355 })(app || (app = {}));
44359 (function (directives) {
44360 var HeaderMenu = (function () {
44361 function HeaderMenu() {
44362 this.restrict = 'E';
44363 this.replace = true;
44364 this.templateUrl = 'templates/header-menu.html';
44366 HeaderMenu.Factory = function () {
44367 var directive = function () {
44368 return new HeaderMenu();
44374 directives.HeaderMenu = HeaderMenu;
44375 })(directives = app.directives || (app.directives = {}));
44376 })(app || (app = {}));
44380 (function (directives) {
44381 var Option = (function () {
44382 function Option() {
44383 this.restrict = 'E';
44384 this.replace = true;
44385 this.controller = 'optionController';
44386 this.bindToController = {
44391 this.templateUrl = 'templates/option.html';
44392 this.controllerAs = 'ctrl';
44394 Option.Factory = function () {
44395 var directive = function () {
44396 return new Option();
44398 directive.$inject = [];
44403 directives.Option = Option;
44404 var OptionController = (function () {
44405 function OptionController() {
44406 var controller = this;
44407 angular.forEach(controller.info.arg, function (arg) {
44408 if (arg.initialValue) {
44409 if (arg.formType === 'number') {
44410 arg.input = parseInt(arg.initialValue);
44413 arg.input = arg.initialValue;
44418 OptionController.$inject = [];
44419 return OptionController;
44421 directives.OptionController = OptionController;
44422 })(directives = app.directives || (app.directives = {}));
44423 })(app || (app = {}));
44427 (function (directives) {
44428 var Directory = (function () {
44429 function Directory() {
44430 this.restrict = 'E';
44431 this.replace = true;
44432 this.controller = 'directoryController';
44433 this.controllerAs = 'ctrl';
44434 this.bindToController = {
44440 this.templateUrl = 'templates/directory.html';
44442 Directory.Factory = function () {
44443 var directive = function () {
44444 return new Directory();
44450 directives.Directory = Directory;
44451 var DirectoryController = (function () {
44452 function DirectoryController(APIEndPoint, $scope) {
44453 this.APIEndPoint = APIEndPoint;
44454 this.$scope = $scope;
44455 var controller = this;
44457 .getFiles(this.info.fileId)
44459 .then(function (result) {
44460 if (result.status === 'success') {
44461 controller.files = result.info;
44462 angular.forEach(result.info, function (file) {
44463 if (file.fileType === '0') {
44465 if (controller.info.path === '/') {
44466 o.path = '/' + file.name;
44469 o.path = controller.info.path + '/' + file.name;
44471 controller.add()(o, controller.list);
44478 DirectoryController.$inject = ['APIEndPoint', '$scope'];
44479 return DirectoryController;
44481 directives.DirectoryController = DirectoryController;
44482 })(directives = app.directives || (app.directives = {}));
44483 })(app || (app = {}));
44487 (function (controllers) {
44488 var Execution = (function () {
44489 function Execution(MyModal, $scope) {
44490 this.MyModal = MyModal;
44491 this.$scope = $scope;
44492 this.commandInfoList = [];
44495 Execution.prototype.add = function () {
44496 this.$scope.$broadcast('close');
44497 this.commandInfoList.push(new app.declares.CommandInfo('dcdFilePrint'));
44499 Execution.prototype.open = function () {
44500 var result = this.MyModal.open('SelectCommand');
44501 console.log(result);
44503 Execution.prototype.remove = function (index, list) {
44504 list.splice(index, 1);
44506 Execution.prototype.close = function () {
44507 console.log("close");
44509 Execution.$inject = ['MyModal', '$scope'];
44512 controllers.Execution = Execution;
44513 })(controllers = app.controllers || (app.controllers = {}));
44514 })(app || (app = {}));
44518 (function (controllers) {
44519 var Workspace = (function () {
44520 function Workspace($scope, APIEndPoint) {
44521 this.$scope = $scope;
44522 this.APIEndPoint = APIEndPoint;
44523 this.directoryList = [];
44524 var controller = this;
44525 var directoryList = this.directoryList;
44527 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
44535 directoryList.push(o);
44537 Workspace.prototype.addDirectory = function (info, directoryList) {
44538 directoryList.push(info);
44540 Workspace.$inject = ['$scope', 'APIEndPoint'];
44543 controllers.Workspace = Workspace;
44544 })(controllers = app.controllers || (app.controllers = {}));
44545 })(app || (app = {}));
44549 (function (controllers) {
44550 var History = (function () {
44551 function History($scope) {
44552 this.page = "History";
44554 History.$inject = ['$scope'];
44557 controllers.History = History;
44558 })(controllers = app.controllers || (app.controllers = {}));
44559 })(app || (app = {}));
44563 var appName = 'zephyr';
44564 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
44565 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
44566 $urlRouterProvider.otherwise('/execution');
44567 $locationProvider.html5Mode({
44572 .state('execution', {
44574 templateUrl: 'templates/execution.html',
44575 controller: 'executionController',
44578 .state('workspace', {
44580 templateUrl: 'templates/workspace.html',
44581 controller: 'workspaceController',
44584 .state('history', {
44586 templateUrl: 'templates/history.html',
44587 controller: 'historyController',
44591 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
44592 app.zephyr.service('MyModal', app.services.MyModal);
44593 app.zephyr.controller('executionController', app.controllers.Execution);
44594 app.zephyr.controller('workspaceController', app.controllers.Workspace);
44595 app.zephyr.controller('historyController', app.controllers.History);
44596 app.zephyr.controller('commandController', app.directives.CommandController);
44597 app.zephyr.controller('optionController', app.directives.OptionController);
44598 app.zephyr.controller('directoryController', app.directives.DirectoryController);
44599 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
44600 app.zephyr.directive('command', app.directives.Command.Factory());
44601 app.zephyr.directive('option', app.directives.Option.Factory());
44602 app.zephyr.directive('directory', app.directives.Directory.Factory());
44603 })(app || (app = {}));
44608 /***/ function(module, exports) {
44613 (function (declares) {
44614 var CommandInfo = (function () {
44615 function CommandInfo(name) {
44618 return CommandInfo;
44620 declares.CommandInfo = CommandInfo;
44621 })(declares = app.declares || (app.declares = {}));
44622 })(app || (app = {}));
44626 (function (services) {
44627 var APIEndPoint = (function () {
44628 function APIEndPoint($resource) {
44629 this.$resource = $resource;
44631 APIEndPoint.prototype.resource = function (endPoint) {
44632 var customAction = {
44636 return this.$resource(endPoint, {}, { customAction: customAction });
44638 APIEndPoint.prototype.getOptionControlFile = function (command) {
44639 var endPoint = '/api/optionControlFile/' + command;
44640 return this.resource(endPoint).get();
44642 APIEndPoint.prototype.getFiles = function (fileId) {
44643 var endPoint = '/api/v1/workspace';
44645 endPoint += '/' + fileId;
44647 return this.resource(endPoint).get();
44649 APIEndPoint.prototype.getDirectories = function () {
44650 var endPoint = '/spi/v1/workspace/';
44651 return this.resource(endPoint).get();
44653 return APIEndPoint;
44655 services.APIEndPoint = APIEndPoint;
44656 })(services = app.services || (app.services = {}));
44657 })(app || (app = {}));
44661 (function (services) {
44662 var MyModal = (function () {
44663 function MyModal($uibModal) {
44664 this.$uibModal = $uibModal;
44665 this.modalOption = {
44672 MyModal.prototype.open = function (modalName) {
44673 if (modalName === 'SelectCommand') {
44674 this.modalOption.templateUrl = 'templates/select-command.html';
44675 this.modalOption.size = 'lg';
44677 return this.$uibModal.open(this.modalOption);
44681 services.MyModal = MyModal;
44682 })(services = app.services || (app.services = {}));
44683 })(app || (app = {}));
44687 (function (directives) {
44688 var Command = (function () {
44689 function Command() {
44690 this.restrict = 'E';
44691 this.replace = true;
44693 this.controller = 'commandController';
44694 this.controllerAs = 'ctrl';
44695 this.bindToController = {
44701 this.templateUrl = 'templates/command.html';
44703 Command.Factory = function () {
44704 var directive = function () {
44705 return new Command();
44707 directive.$inject = [];
44712 directives.Command = Command;
44713 var CommandController = (function () {
44714 function CommandController(APIEndPoint, $scope) {
44715 this.APIEndPoint = APIEndPoint;
44716 this.$scope = $scope;
44717 var controller = this;
44718 console.log(this.list);
44720 .getOptionControlFile('dcdFilePrint')
44722 .then(function (result) {
44723 console.log(result);
44724 controller.options = result.info;
44726 this.files = ['a.file', 'b.file', 'c.file'];
44727 this.heading = "[" + this.index + "]: dcdFilePring";
44728 this.isOpen = true;
44729 this.$scope.$on('close', function () {
44730 controller.isOpen = false;
44733 CommandController.prototype.submit = function () {
44735 angular.forEach(this.options, function (option) {
44737 angular.forEach(option.arg, function (arg) {
44739 inputs.push(arg.input);
44742 if (inputs.length > 0) {
44743 params[option.option] = inputs;
44746 console.log(params);
44748 CommandController.prototype.removeMySelf = function (index) {
44749 this.remove()(index, this.list);
44751 CommandController.$inject = ['APIEndPoint', '$scope'];
44752 return CommandController;
44754 directives.CommandController = CommandController;
44755 })(directives = app.directives || (app.directives = {}));
44756 })(app || (app = {}));
44760 (function (directives) {
44761 var HeaderMenu = (function () {
44762 function HeaderMenu() {
44763 this.restrict = 'E';
44764 this.replace = true;
44765 this.templateUrl = 'templates/header-menu.html';
44767 HeaderMenu.Factory = function () {
44768 var directive = function () {
44769 return new HeaderMenu();
44775 directives.HeaderMenu = HeaderMenu;
44776 })(directives = app.directives || (app.directives = {}));
44777 })(app || (app = {}));
44781 (function (directives) {
44782 var Option = (function () {
44783 function Option() {
44784 this.restrict = 'E';
44785 this.replace = true;
44786 this.controller = 'optionController';
44787 this.bindToController = {
44792 this.templateUrl = 'templates/option.html';
44793 this.controllerAs = 'ctrl';
44795 Option.Factory = function () {
44796 var directive = function () {
44797 return new Option();
44799 directive.$inject = [];
44804 directives.Option = Option;
44805 var OptionController = (function () {
44806 function OptionController() {
44807 var controller = this;
44808 angular.forEach(controller.info.arg, function (arg) {
44809 if (arg.initialValue) {
44810 if (arg.formType === 'number') {
44811 arg.input = parseInt(arg.initialValue);
44814 arg.input = arg.initialValue;
44819 OptionController.$inject = [];
44820 return OptionController;
44822 directives.OptionController = OptionController;
44823 })(directives = app.directives || (app.directives = {}));
44824 })(app || (app = {}));
44828 (function (directives) {
44829 var Directory = (function () {
44830 function Directory() {
44831 this.restrict = 'E';
44832 this.replace = true;
44833 this.controller = 'directoryController';
44834 this.controllerAs = 'ctrl';
44835 this.bindToController = {
44841 this.templateUrl = 'templates/directory.html';
44843 Directory.Factory = function () {
44844 var directive = function () {
44845 return new Directory();
44851 directives.Directory = Directory;
44852 var DirectoryController = (function () {
44853 function DirectoryController(APIEndPoint, $scope) {
44854 this.APIEndPoint = APIEndPoint;
44855 this.$scope = $scope;
44856 var controller = this;
44858 .getFiles(this.info.fileId)
44860 .then(function (result) {
44861 if (result.status === 'success') {
44862 controller.files = result.info;
44863 angular.forEach(result.info, function (file) {
44864 if (file.fileType === '0') {
44866 if (controller.info.path === '/') {
44867 o.path = '/' + file.name;
44870 o.path = controller.info.path + '/' + file.name;
44872 controller.add()(o, controller.list);
44879 DirectoryController.$inject = ['APIEndPoint', '$scope'];
44880 return DirectoryController;
44882 directives.DirectoryController = DirectoryController;
44883 })(directives = app.directives || (app.directives = {}));
44884 })(app || (app = {}));
44888 (function (controllers) {
44889 var Execution = (function () {
44890 function Execution(MyModal, $scope) {
44891 this.MyModal = MyModal;
44892 this.$scope = $scope;
44893 this.commandInfoList = [];
44896 Execution.prototype.add = function () {
44897 this.$scope.$broadcast('close');
44898 this.commandInfoList.push(new app.declares.CommandInfo('dcdFilePrint'));
44900 Execution.prototype.open = function () {
44901 var result = this.MyModal.open('SelectCommand');
44902 console.log(result);
44904 Execution.prototype.remove = function (index, list) {
44905 list.splice(index, 1);
44907 Execution.prototype.close = function () {
44908 console.log("close");
44910 Execution.$inject = ['MyModal', '$scope'];
44913 controllers.Execution = Execution;
44914 })(controllers = app.controllers || (app.controllers = {}));
44915 })(app || (app = {}));
44919 (function (controllers) {
44920 var Workspace = (function () {
44921 function Workspace($scope, APIEndPoint) {
44922 this.$scope = $scope;
44923 this.APIEndPoint = APIEndPoint;
44924 this.directoryList = [];
44925 var controller = this;
44926 var directoryList = this.directoryList;
44928 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
44936 directoryList.push(o);
44938 Workspace.prototype.addDirectory = function (info, directoryList) {
44939 directoryList.push(info);
44941 Workspace.$inject = ['$scope', 'APIEndPoint'];
44944 controllers.Workspace = Workspace;
44945 })(controllers = app.controllers || (app.controllers = {}));
44946 })(app || (app = {}));
44950 (function (controllers) {
44951 var History = (function () {
44952 function History($scope) {
44953 this.page = "History";
44955 History.$inject = ['$scope'];
44958 controllers.History = History;
44959 })(controllers = app.controllers || (app.controllers = {}));
44960 })(app || (app = {}));
44964 var appName = 'zephyr';
44965 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
44966 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
44967 $urlRouterProvider.otherwise('/execution');
44968 $locationProvider.html5Mode({
44973 .state('execution', {
44975 templateUrl: 'templates/execution.html',
44976 controller: 'executionController',
44979 .state('workspace', {
44981 templateUrl: 'templates/workspace.html',
44982 controller: 'workspaceController',
44985 .state('history', {
44987 templateUrl: 'templates/history.html',
44988 controller: 'historyController',
44992 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
44993 app.zephyr.service('MyModal', app.services.MyModal);
44994 app.zephyr.controller('executionController', app.controllers.Execution);
44995 app.zephyr.controller('workspaceController', app.controllers.Workspace);
44996 app.zephyr.controller('historyController', app.controllers.History);
44997 app.zephyr.controller('commandController', app.directives.CommandController);
44998 app.zephyr.controller('optionController', app.directives.OptionController);
44999 app.zephyr.controller('directoryController', app.directives.DirectoryController);
45000 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
45001 app.zephyr.directive('command', app.directives.Command.Factory());
45002 app.zephyr.directive('option', app.directives.Option.Factory());
45003 app.zephyr.directive('directory', app.directives.Directory.Factory());
45004 })(app || (app = {}));
45009 /***/ function(module, exports) {
45014 (function (declares) {
45015 var CommandInfo = (function () {
45016 function CommandInfo(name) {
45019 return CommandInfo;
45021 declares.CommandInfo = CommandInfo;
45022 })(declares = app.declares || (app.declares = {}));
45023 })(app || (app = {}));
45027 (function (services) {
45028 var APIEndPoint = (function () {
45029 function APIEndPoint($resource) {
45030 this.$resource = $resource;
45032 APIEndPoint.prototype.resource = function (endPoint) {
45033 var customAction = {
45037 return this.$resource(endPoint, {}, { customAction: customAction });
45039 APIEndPoint.prototype.getOptionControlFile = function (command) {
45040 var endPoint = '/api/optionControlFile/' + command;
45041 return this.resource(endPoint).get();
45043 APIEndPoint.prototype.getFiles = function (fileId) {
45044 var endPoint = '/api/v1/workspace';
45046 endPoint += '/' + fileId;
45048 return this.resource(endPoint).get();
45050 APIEndPoint.prototype.getDirectories = function () {
45051 var endPoint = '/spi/v1/workspace/';
45052 return this.resource(endPoint).get();
45054 return APIEndPoint;
45056 services.APIEndPoint = APIEndPoint;
45057 })(services = app.services || (app.services = {}));
45058 })(app || (app = {}));
45062 (function (services) {
45063 var MyModal = (function () {
45064 function MyModal($uibModal) {
45065 this.$uibModal = $uibModal;
45066 this.modalOption = {
45073 MyModal.prototype.open = function (modalName) {
45074 if (modalName === 'SelectCommand') {
45075 this.modalOption.templateUrl = 'templates/select-command.html';
45076 this.modalOption.size = 'lg';
45078 return this.$uibModal.open(this.modalOption);
45082 services.MyModal = MyModal;
45083 })(services = app.services || (app.services = {}));
45084 })(app || (app = {}));
45088 (function (directives) {
45089 var Command = (function () {
45090 function Command() {
45091 this.restrict = 'E';
45092 this.replace = true;
45094 this.controller = 'commandController';
45095 this.controllerAs = 'ctrl';
45096 this.bindToController = {
45102 this.templateUrl = 'templates/command.html';
45104 Command.Factory = function () {
45105 var directive = function () {
45106 return new Command();
45108 directive.$inject = [];
45113 directives.Command = Command;
45114 var CommandController = (function () {
45115 function CommandController(APIEndPoint, $scope) {
45116 this.APIEndPoint = APIEndPoint;
45117 this.$scope = $scope;
45118 var controller = this;
45119 console.log(this.list);
45121 .getOptionControlFile('dcdFilePrint')
45123 .then(function (result) {
45124 console.log(result);
45125 controller.options = result.info;
45127 this.files = ['a.file', 'b.file', 'c.file'];
45128 this.heading = "[" + this.index + "]: dcdFilePring";
45129 this.isOpen = true;
45130 this.$scope.$on('close', function () {
45131 controller.isOpen = false;
45134 CommandController.prototype.submit = function () {
45136 angular.forEach(this.options, function (option) {
45138 angular.forEach(option.arg, function (arg) {
45140 inputs.push(arg.input);
45143 if (inputs.length > 0) {
45144 params[option.option] = inputs;
45147 console.log(params);
45149 CommandController.prototype.removeMySelf = function (index) {
45150 this.remove()(index, this.list);
45152 CommandController.$inject = ['APIEndPoint', '$scope'];
45153 return CommandController;
45155 directives.CommandController = CommandController;
45156 })(directives = app.directives || (app.directives = {}));
45157 })(app || (app = {}));
45161 (function (directives) {
45162 var HeaderMenu = (function () {
45163 function HeaderMenu() {
45164 this.restrict = 'E';
45165 this.replace = true;
45166 this.templateUrl = 'templates/header-menu.html';
45168 HeaderMenu.Factory = function () {
45169 var directive = function () {
45170 return new HeaderMenu();
45176 directives.HeaderMenu = HeaderMenu;
45177 })(directives = app.directives || (app.directives = {}));
45178 })(app || (app = {}));
45182 (function (directives) {
45183 var Option = (function () {
45184 function Option() {
45185 this.restrict = 'E';
45186 this.replace = true;
45187 this.controller = 'optionController';
45188 this.bindToController = {
45193 this.templateUrl = 'templates/option.html';
45194 this.controllerAs = 'ctrl';
45196 Option.Factory = function () {
45197 var directive = function () {
45198 return new Option();
45200 directive.$inject = [];
45205 directives.Option = Option;
45206 var OptionController = (function () {
45207 function OptionController() {
45208 var controller = this;
45209 angular.forEach(controller.info.arg, function (arg) {
45210 if (arg.initialValue) {
45211 if (arg.formType === 'number') {
45212 arg.input = parseInt(arg.initialValue);
45215 arg.input = arg.initialValue;
45220 OptionController.$inject = [];
45221 return OptionController;
45223 directives.OptionController = OptionController;
45224 })(directives = app.directives || (app.directives = {}));
45225 })(app || (app = {}));
45229 (function (directives) {
45230 var Directory = (function () {
45231 function Directory() {
45232 this.restrict = 'E';
45233 this.replace = true;
45234 this.controller = 'directoryController';
45235 this.controllerAs = 'ctrl';
45236 this.bindToController = {
45242 this.templateUrl = 'templates/directory.html';
45244 Directory.Factory = function () {
45245 var directive = function () {
45246 return new Directory();
45252 directives.Directory = Directory;
45253 var DirectoryController = (function () {
45254 function DirectoryController(APIEndPoint, $scope) {
45255 this.APIEndPoint = APIEndPoint;
45256 this.$scope = $scope;
45257 var controller = this;
45259 .getFiles(this.info.fileId)
45261 .then(function (result) {
45262 if (result.status === 'success') {
45263 controller.files = result.info;
45264 angular.forEach(result.info, function (file) {
45265 if (file.fileType === '0') {
45267 if (controller.info.path === '/') {
45268 o.path = '/' + file.name;
45271 o.path = controller.info.path + '/' + file.name;
45273 controller.add()(o, controller.list);
45280 DirectoryController.$inject = ['APIEndPoint', '$scope'];
45281 return DirectoryController;
45283 directives.DirectoryController = DirectoryController;
45284 })(directives = app.directives || (app.directives = {}));
45285 })(app || (app = {}));
45289 (function (controllers) {
45290 var Execution = (function () {
45291 function Execution(MyModal, $scope) {
45292 this.MyModal = MyModal;
45293 this.$scope = $scope;
45294 this.commandInfoList = [];
45297 Execution.prototype.add = function () {
45298 this.$scope.$broadcast('close');
45299 this.commandInfoList.push(new app.declares.CommandInfo('dcdFilePrint'));
45301 Execution.prototype.open = function () {
45302 var result = this.MyModal.open('SelectCommand');
45303 console.log(result);
45305 Execution.prototype.remove = function (index, list) {
45306 list.splice(index, 1);
45308 Execution.prototype.close = function () {
45309 console.log("close");
45311 Execution.$inject = ['MyModal', '$scope'];
45314 controllers.Execution = Execution;
45315 })(controllers = app.controllers || (app.controllers = {}));
45316 })(app || (app = {}));
45320 (function (controllers) {
45321 var Workspace = (function () {
45322 function Workspace($scope, APIEndPoint) {
45323 this.$scope = $scope;
45324 this.APIEndPoint = APIEndPoint;
45325 this.directoryList = [];
45326 var controller = this;
45327 var directoryList = this.directoryList;
45329 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
45337 directoryList.push(o);
45339 Workspace.prototype.addDirectory = function (info, directoryList) {
45340 directoryList.push(info);
45342 Workspace.$inject = ['$scope', 'APIEndPoint'];
45345 controllers.Workspace = Workspace;
45346 })(controllers = app.controllers || (app.controllers = {}));
45347 })(app || (app = {}));
45351 (function (controllers) {
45352 var History = (function () {
45353 function History($scope) {
45354 this.page = "History";
45356 History.$inject = ['$scope'];
45359 controllers.History = History;
45360 })(controllers = app.controllers || (app.controllers = {}));
45361 })(app || (app = {}));
45365 var appName = 'zephyr';
45366 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
45367 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
45368 $urlRouterProvider.otherwise('/execution');
45369 $locationProvider.html5Mode({
45374 .state('execution', {
45376 templateUrl: 'templates/execution.html',
45377 controller: 'executionController',
45380 .state('workspace', {
45382 templateUrl: 'templates/workspace.html',
45383 controller: 'workspaceController',
45386 .state('history', {
45388 templateUrl: 'templates/history.html',
45389 controller: 'historyController',
45393 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
45394 app.zephyr.service('MyModal', app.services.MyModal);
45395 app.zephyr.controller('executionController', app.controllers.Execution);
45396 app.zephyr.controller('workspaceController', app.controllers.Workspace);
45397 app.zephyr.controller('historyController', app.controllers.History);
45398 app.zephyr.controller('commandController', app.directives.CommandController);
45399 app.zephyr.controller('optionController', app.directives.OptionController);
45400 app.zephyr.controller('directoryController', app.directives.DirectoryController);
45401 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
45402 app.zephyr.directive('command', app.directives.Command.Factory());
45403 app.zephyr.directive('option', app.directives.Option.Factory());
45404 app.zephyr.directive('directory', app.directives.Directory.Factory());
45405 })(app || (app = {}));