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);
62 __webpack_require__(19);
67 /***/ function(module, exports, __webpack_require__) {
69 __webpack_require__(2);
70 module.exports = angular;
75 /***/ function(module, exports) {
78 * @license AngularJS v1.4.8
79 * (c) 2010-2015 Google, Inc. http://angularjs.org
82 (function(window, document, undefined) {'use strict';
87 * This object provides a utility for producing rich Error messages within
88 * Angular. It can be called as follows:
90 * var exampleMinErr = minErr('example');
91 * throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
93 * The above creates an instance of minErr in the example namespace. The
94 * resulting error will have a namespaced error code of example.one. The
95 * resulting error will replace {0} with the value of foo, and {1} with the
96 * value of bar. The object is not restricted in the number of arguments it can
99 * If fewer arguments are specified than necessary for interpolation, the extra
100 * interpolation markers will be preserved in the final string.
102 * Since data will be parsed statically during a build step, some restrictions
103 * are applied with respect to how minErr instances are created and called.
104 * Instances should have names of the form namespaceMinErr for a minErr created
105 * using minErr('namespace') . Error codes, namespaces and template strings
106 * should all be static strings, not variables or general expressions.
108 * @param {string} module The namespace to use for the new minErr instance.
109 * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning
110 * error from returned function, for cases when a particular type of error is useful.
111 * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance
114 function minErr(module, ErrorConstructor) {
115 ErrorConstructor = ErrorConstructor || Error;
117 var SKIP_INDEXES = 2;
119 var templateArgs = arguments,
120 code = templateArgs[0],
121 message = '[' + (module ? module + ':' : '') + code + '] ',
122 template = templateArgs[1],
125 message += template.replace(/\{\d+\}/g, function(match) {
126 var index = +match.slice(1, -1),
127 shiftedIndex = index + SKIP_INDEXES;
129 if (shiftedIndex < templateArgs.length) {
130 return toDebugString(templateArgs[shiftedIndex]);
136 message += '\nhttp://errors.angularjs.org/1.4.8/' +
137 (module ? module + '/' : '') + code;
139 for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
140 message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' +
141 encodeURIComponent(toDebugString(templateArgs[i]));
144 return new ErrorConstructor(message);
148 /* We need to tell jshint what variables are being exported */
149 /* global angular: true,
160 REGEX_STRING_REGEXP: true,
161 VALIDITY_STATE_PROPERTY: true,
165 manualLowercase: true,
166 manualUppercase: true,
199 escapeForRegexp: true,
212 toJsonReplacer: true,
215 convertTimezoneToLocal: true,
216 timezoneToOffset: true,
218 tryDecodeURIComponent: true,
221 encodeUriSegment: true,
222 encodeUriQuery: true,
225 getTestability: true,
230 assertNotHasOwnProperty: true,
233 hasOwnProperty: true,
236 NODE_TYPE_ELEMENT: true,
237 NODE_TYPE_ATTRIBUTE: true,
238 NODE_TYPE_TEXT: true,
239 NODE_TYPE_COMMENT: true,
240 NODE_TYPE_DOCUMENT: true,
241 NODE_TYPE_DOCUMENT_FRAGMENT: true,
244 ////////////////////////////////////
253 * The ng module is loaded by default when an AngularJS application is started. The module itself
254 * contains the essential components for an AngularJS application to function. The table below
255 * lists a high level breakdown of each of the services/factories, filters, directives and testing
256 * components available within this core module.
258 * <div doc-module-components="ng"></div>
261 var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/;
263 // The name of a form control's ValidityState property.
264 // This is used so that it's possible for internal tests to create mock ValidityStates.
265 var VALIDITY_STATE_PROPERTY = 'validity';
269 * @name angular.lowercase
273 * @description Converts the specified string to lowercase.
274 * @param {string} string String to be converted to lowercase.
275 * @returns {string} Lowercased string.
277 var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;};
278 var hasOwnProperty = Object.prototype.hasOwnProperty;
282 * @name angular.uppercase
286 * @description Converts the specified string to uppercase.
287 * @param {string} string String to be converted to uppercase.
288 * @returns {string} Uppercased string.
290 var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;};
293 var manualLowercase = function(s) {
294 /* jshint bitwise: false */
296 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);})
299 var manualUppercase = function(s) {
300 /* jshint bitwise: false */
302 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);})
307 // String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
308 // locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
309 // with correct but slower alternatives.
310 if ('i' !== 'I'.toLowerCase()) {
311 lowercase = manualLowercase;
312 uppercase = manualUppercase;
317 msie, // holds major version number for IE, or NaN if UA is not IE.
318 jqLite, // delay binding since jQuery could be loaded after us.
319 jQuery, // delay binding
323 toString = Object.prototype.toString,
324 getPrototypeOf = Object.getPrototypeOf,
325 ngMinErr = minErr('ng'),
328 angular = window.angular || (window.angular = {}),
333 * documentMode is an IE-only property
334 * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
336 msie = document.documentMode;
342 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments,
345 function isArrayLike(obj) {
347 // `null`, `undefined` and `window` are not array-like
348 if (obj == null || isWindow(obj)) return false;
350 // arrays, strings and jQuery/jqLite objects are array like
351 // * jqLite is either the jQuery or jqLite constructor function
352 // * we have to check the existance of jqLite first as this method is called
353 // via the forEach method when constructing the jqLite object in the first place
354 if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true;
356 // Support: iOS 8.2 (not reproducible in simulator)
357 // "length" in obj used to prevent JIT error (gh-11508)
358 var length = "length" in Object(obj) && obj.length;
360 // NodeList objects (with `item` method) and
361 // other objects with suitable length characteristics are array-like
362 return isNumber(length) &&
363 (length >= 0 && (length - 1) in obj || typeof obj.item == 'function');
368 * @name angular.forEach
373 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an
374 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value`
375 * is the value of an object property or an array element, `key` is the object property key or
376 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional.
378 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters
379 * using the `hasOwnProperty` method.
382 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),
383 * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
384 * return the value provided.
387 var values = {name: 'misko', gender: 'male'};
389 angular.forEach(values, function(value, key) {
390 this.push(key + ': ' + value);
392 expect(log).toEqual(['name: misko', 'gender: male']);
395 * @param {Object|Array} obj Object to iterate over.
396 * @param {Function} iterator Iterator function.
397 * @param {Object=} context Object to become context (`this`) for the iterator function.
398 * @returns {Object|Array} Reference to `obj`.
401 function forEach(obj, iterator, context) {
404 if (isFunction(obj)) {
406 // Need to check if hasOwnProperty exists,
407 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
408 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {
409 iterator.call(context, obj[key], key, obj);
412 } else if (isArray(obj) || isArrayLike(obj)) {
413 var isPrimitive = typeof obj !== 'object';
414 for (key = 0, length = obj.length; key < length; key++) {
415 if (isPrimitive || key in obj) {
416 iterator.call(context, obj[key], key, obj);
419 } else if (obj.forEach && obj.forEach !== forEach) {
420 obj.forEach(iterator, context, obj);
421 } else if (isBlankObject(obj)) {
422 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
424 iterator.call(context, obj[key], key, obj);
426 } else if (typeof obj.hasOwnProperty === 'function') {
427 // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed
429 if (obj.hasOwnProperty(key)) {
430 iterator.call(context, obj[key], key, obj);
434 // Slow path for objects which do not have a method `hasOwnProperty`
436 if (hasOwnProperty.call(obj, key)) {
437 iterator.call(context, obj[key], key, obj);
445 function forEachSorted(obj, iterator, context) {
446 var keys = Object.keys(obj).sort();
447 for (var i = 0; i < keys.length; i++) {
448 iterator.call(context, obj[keys[i]], keys[i]);
455 * when using forEach the params are value, key, but it is often useful to have key, value.
456 * @param {function(string, *)} iteratorFn
457 * @returns {function(*, string)}
459 function reverseParams(iteratorFn) {
460 return function(value, key) { iteratorFn(key, value); };
464 * A consistent way of creating unique IDs in angular.
466 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before
467 * we hit number precision issues in JavaScript.
469 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M
471 * @returns {number} an unique alpha-numeric string
479 * Set or clear the hashkey for an object.
481 * @param h the hashkey (!truthy to delete the hashkey)
483 function setHashKey(obj, h) {
487 delete obj.$$hashKey;
492 function baseExtend(dst, objs, deep) {
493 var h = dst.$$hashKey;
495 for (var i = 0, ii = objs.length; i < ii; ++i) {
497 if (!isObject(obj) && !isFunction(obj)) continue;
498 var keys = Object.keys(obj);
499 for (var j = 0, jj = keys.length; j < jj; j++) {
503 if (deep && isObject(src)) {
505 dst[key] = new Date(src.valueOf());
506 } else if (isRegExp(src)) {
507 dst[key] = new RegExp(src);
508 } else if (src.nodeName) {
509 dst[key] = src.cloneNode(true);
510 } else if (isElement(src)) {
511 dst[key] = src.clone();
513 if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
514 baseExtend(dst[key], [src], true);
528 * @name angular.extend
533 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
534 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
535 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
537 * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use
538 * {@link angular.merge} for this.
540 * @param {Object} dst Destination object.
541 * @param {...Object} src Source object(s).
542 * @returns {Object} Reference to `dst`.
544 function extend(dst) {
545 return baseExtend(dst, slice.call(arguments, 1), false);
551 * @name angular.merge
556 * Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
557 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
558 * by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`.
560 * Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source
561 * objects, performing a deep copy.
563 * @param {Object} dst Destination object.
564 * @param {...Object} src Source object(s).
565 * @returns {Object} Reference to `dst`.
567 function merge(dst) {
568 return baseExtend(dst, slice.call(arguments, 1), true);
573 function toInt(str) {
574 return parseInt(str, 10);
578 function inherit(parent, extra) {
579 return extend(Object.create(parent), extra);
589 * A function that performs no operations. This function can be useful when writing code in the
592 function foo(callback) {
593 var result = calculateResult();
594 (callback || angular.noop)(result);
604 * @name angular.identity
609 * A function that returns its first argument. This function is useful when writing code in the
613 function transformer(transformationFn, value) {
614 return (transformationFn || angular.identity)(value);
617 * @param {*} value to be returned.
618 * @returns {*} the value passed in.
620 function identity($) {return $;}
621 identity.$inject = [];
624 function valueFn(value) {return function() {return value;};}
626 function hasCustomToString(obj) {
627 return isFunction(obj.toString) && obj.toString !== toString;
633 * @name angular.isUndefined
638 * Determines if a reference is undefined.
640 * @param {*} value Reference to check.
641 * @returns {boolean} True if `value` is undefined.
643 function isUndefined(value) {return typeof value === 'undefined';}
648 * @name angular.isDefined
653 * Determines if a reference is defined.
655 * @param {*} value Reference to check.
656 * @returns {boolean} True if `value` is defined.
658 function isDefined(value) {return typeof value !== 'undefined';}
663 * @name angular.isObject
668 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
669 * considered to be objects. Note that JavaScript arrays are objects.
671 * @param {*} value Reference to check.
672 * @returns {boolean} True if `value` is an `Object` but not `null`.
674 function isObject(value) {
675 // http://jsperf.com/isobject4
676 return value !== null && typeof value === 'object';
681 * Determine if a value is an object with a null prototype
683 * @returns {boolean} True if `value` is an `Object` with a null prototype
685 function isBlankObject(value) {
686 return value !== null && typeof value === 'object' && !getPrototypeOf(value);
692 * @name angular.isString
697 * Determines if a reference is a `String`.
699 * @param {*} value Reference to check.
700 * @returns {boolean} True if `value` is a `String`.
702 function isString(value) {return typeof value === 'string';}
707 * @name angular.isNumber
712 * Determines if a reference is a `Number`.
714 * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`.
716 * If you wish to exclude these then you can use the native
717 * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite)
720 * @param {*} value Reference to check.
721 * @returns {boolean} True if `value` is a `Number`.
723 function isNumber(value) {return typeof value === 'number';}
728 * @name angular.isDate
733 * Determines if a value is a date.
735 * @param {*} value Reference to check.
736 * @returns {boolean} True if `value` is a `Date`.
738 function isDate(value) {
739 return toString.call(value) === '[object Date]';
745 * @name angular.isArray
750 * Determines if a reference is an `Array`.
752 * @param {*} value Reference to check.
753 * @returns {boolean} True if `value` is an `Array`.
755 var isArray = Array.isArray;
759 * @name angular.isFunction
764 * Determines if a reference is a `Function`.
766 * @param {*} value Reference to check.
767 * @returns {boolean} True if `value` is a `Function`.
769 function isFunction(value) {return typeof value === 'function';}
773 * Determines if a value is a regular expression object.
776 * @param {*} value Reference to check.
777 * @returns {boolean} True if `value` is a `RegExp`.
779 function isRegExp(value) {
780 return toString.call(value) === '[object RegExp]';
785 * Checks if `obj` is a window object.
788 * @param {*} obj Object to check
789 * @returns {boolean} True if `obj` is a window obj.
791 function isWindow(obj) {
792 return obj && obj.window === obj;
796 function isScope(obj) {
797 return obj && obj.$evalAsync && obj.$watch;
801 function isFile(obj) {
802 return toString.call(obj) === '[object File]';
806 function isFormData(obj) {
807 return toString.call(obj) === '[object FormData]';
811 function isBlob(obj) {
812 return toString.call(obj) === '[object Blob]';
816 function isBoolean(value) {
817 return typeof value === 'boolean';
821 function isPromiseLike(obj) {
822 return obj && isFunction(obj.then);
826 var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/;
827 function isTypedArray(value) {
828 return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value));
832 var trim = function(value) {
833 return isString(value) ? value.trim() : value;
837 // http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021
838 // Prereq: s is a string.
839 var escapeForRegexp = function(s) {
840 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
841 replace(/\x08/g, '\\x08');
847 * @name angular.isElement
852 * Determines if a reference is a DOM element (or wrapped jQuery element).
854 * @param {*} value Reference to check.
855 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
857 function isElement(node) {
859 (node.nodeName // we are a direct element
860 || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API
864 * @param str 'key1,key2,...'
865 * @returns {object} in the form of {key1:true, key2:true, ...}
867 function makeMap(str) {
868 var obj = {}, items = str.split(","), i;
869 for (i = 0; i < items.length; i++) {
870 obj[items[i]] = true;
876 function nodeName_(element) {
877 return lowercase(element.nodeName || (element[0] && element[0].nodeName));
880 function includes(array, obj) {
881 return Array.prototype.indexOf.call(array, obj) != -1;
884 function arrayRemove(array, value) {
885 var index = array.indexOf(value);
887 array.splice(index, 1);
899 * Creates a deep copy of `source`, which should be an object or an array.
901 * * If no destination is supplied, a copy of the object or array is created.
902 * * If a destination is provided, all of its elements (for arrays) or properties (for objects)
903 * are deleted and then all elements/properties from the source are copied to it.
904 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
905 * * If `source` is identical to 'destination' an exception will be thrown.
907 * @param {*} source The source that will be used to make a copy.
908 * Can be any type, including primitives, `null`, and `undefined`.
909 * @param {(Object|Array)=} destination Destination into which the source is copied. If
910 * provided, must be of the same type as `source`.
911 * @returns {*} The copy or updated `destination`, if `destination` was specified.
914 <example module="copyExample">
915 <file name="index.html">
916 <div ng-controller="ExampleController">
917 <form novalidate class="simple-form">
918 Name: <input type="text" ng-model="user.name" /><br />
919 E-mail: <input type="email" ng-model="user.email" /><br />
920 Gender: <input type="radio" ng-model="user.gender" value="male" />male
921 <input type="radio" ng-model="user.gender" value="female" />female<br />
922 <button ng-click="reset()">RESET</button>
923 <button ng-click="update(user)">SAVE</button>
925 <pre>form = {{user | json}}</pre>
926 <pre>master = {{master | json}}</pre>
930 angular.module('copyExample', [])
931 .controller('ExampleController', ['$scope', function($scope) {
934 $scope.update = function(user) {
935 // Example with 1 argument
936 $scope.master= angular.copy(user);
939 $scope.reset = function() {
940 // Example with 2 arguments
941 angular.copy($scope.master, $scope.user);
950 function copy(source, destination) {
951 var stackSource = [];
955 if (isTypedArray(destination)) {
956 throw ngMinErr('cpta', "Can't copy! TypedArray destination cannot be mutated.");
958 if (source === destination) {
959 throw ngMinErr('cpi', "Can't copy! Source and destination are identical.");
962 // Empty the destination object
963 if (isArray(destination)) {
964 destination.length = 0;
966 forEach(destination, function(value, key) {
967 if (key !== '$$hashKey') {
968 delete destination[key];
973 stackSource.push(source);
974 stackDest.push(destination);
975 return copyRecurse(source, destination);
978 return copyElement(source);
980 function copyRecurse(source, destination) {
981 var h = destination.$$hashKey;
983 if (isArray(source)) {
984 for (var i = 0, ii = source.length; i < ii; i++) {
985 destination.push(copyElement(source[i]));
987 } else if (isBlankObject(source)) {
988 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
989 for (key in source) {
990 destination[key] = copyElement(source[key]);
992 } else if (source && typeof source.hasOwnProperty === 'function') {
993 // Slow path, which must rely on hasOwnProperty
994 for (key in source) {
995 if (source.hasOwnProperty(key)) {
996 destination[key] = copyElement(source[key]);
1000 // Slowest path --- hasOwnProperty can't be called as a method
1001 for (key in source) {
1002 if (hasOwnProperty.call(source, key)) {
1003 destination[key] = copyElement(source[key]);
1007 setHashKey(destination, h);
1011 function copyElement(source) {
1013 if (!isObject(source)) {
1017 // Already copied values
1018 var index = stackSource.indexOf(source);
1020 return stackDest[index];
1023 if (isWindow(source) || isScope(source)) {
1024 throw ngMinErr('cpws',
1025 "Can't copy! Making copies of Window or Scope instances is not supported.");
1028 var needsRecurse = false;
1031 if (isArray(source)) {
1033 needsRecurse = true;
1034 } else if (isTypedArray(source)) {
1035 destination = new source.constructor(source);
1036 } else if (isDate(source)) {
1037 destination = new Date(source.getTime());
1038 } else if (isRegExp(source)) {
1039 destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
1040 destination.lastIndex = source.lastIndex;
1041 } else if (isFunction(source.cloneNode)) {
1042 destination = source.cloneNode(true);
1044 destination = Object.create(getPrototypeOf(source));
1045 needsRecurse = true;
1048 stackSource.push(source);
1049 stackDest.push(destination);
1052 ? copyRecurse(source, destination)
1058 * Creates a shallow copy of an object, an array or a primitive.
1060 * Assumes that there are no proto properties for objects.
1062 function shallowCopy(src, dst) {
1066 for (var i = 0, ii = src.length; i < ii; i++) {
1069 } else if (isObject(src)) {
1072 for (var key in src) {
1073 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
1074 dst[key] = src[key];
1085 * @name angular.equals
1090 * Determines if two objects or two values are equivalent. Supports value types, regular
1091 * expressions, arrays and objects.
1093 * Two objects or values are considered equivalent if at least one of the following is true:
1095 * * Both objects or values pass `===` comparison.
1096 * * Both objects or values are of the same type and all of their properties are equal by
1097 * comparing them with `angular.equals`.
1098 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal)
1099 * * Both values represent the same regular expression (In JavaScript,
1100 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual
1101 * representation matches).
1103 * During a property comparison, properties of `function` type and properties with names
1104 * that begin with `$` are ignored.
1106 * Scope and DOMWindow objects are being compared only by identify (`===`).
1108 * @param {*} o1 Object or value to compare.
1109 * @param {*} o2 Object or value to compare.
1110 * @returns {boolean} True if arguments are equal.
1112 function equals(o1, o2) {
1113 if (o1 === o2) return true;
1114 if (o1 === null || o2 === null) return false;
1115 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
1116 var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
1118 if (t1 == 'object') {
1120 if (!isArray(o2)) return false;
1121 if ((length = o1.length) == o2.length) {
1122 for (key = 0; key < length; key++) {
1123 if (!equals(o1[key], o2[key])) return false;
1127 } else if (isDate(o1)) {
1128 if (!isDate(o2)) return false;
1129 return equals(o1.getTime(), o2.getTime());
1130 } else if (isRegExp(o1)) {
1131 return isRegExp(o2) ? o1.toString() == o2.toString() : false;
1133 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
1134 isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
1135 keySet = createMap();
1137 if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
1138 if (!equals(o1[key], o2[key])) return false;
1142 if (!(key in keySet) &&
1143 key.charAt(0) !== '$' &&
1144 isDefined(o2[key]) &&
1145 !isFunction(o2[key])) return false;
1154 var csp = function() {
1155 if (!isDefined(csp.rules)) {
1158 var ngCspElement = (document.querySelector('[ng-csp]') ||
1159 document.querySelector('[data-ng-csp]'));
1162 var ngCspAttribute = ngCspElement.getAttribute('ng-csp') ||
1163 ngCspElement.getAttribute('data-ng-csp');
1165 noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1),
1166 noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1)
1170 noUnsafeEval: noUnsafeEval(),
1171 noInlineStyle: false
1178 function noUnsafeEval() {
1180 /* jshint -W031, -W054 */
1182 /* jshint +W031, +W054 */
1196 * @param {string=} ngJq the name of the library available under `window`
1197 * to be used for angular.element
1199 * Use this directive to force the angular.element library. This should be
1200 * used to force either jqLite by leaving ng-jq blank or setting the name of
1201 * the jquery variable under window (eg. jQuery).
1203 * Since angular looks for this directive when it is loaded (doesn't wait for the
1204 * DOMContentLoaded event), it must be placed on an element that comes before the script
1205 * which loads angular. Also, only the first instance of `ng-jq` will be used and all
1209 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag.
1218 * This example shows how to use a jQuery based library of a different name.
1219 * The library name must be available at the top most 'window'.
1222 <html ng-app ng-jq="jQueryLib">
1228 var jq = function() {
1229 if (isDefined(jq.name_)) return jq.name_;
1231 var i, ii = ngAttrPrefixes.length, prefix, name;
1232 for (i = 0; i < ii; ++i) {
1233 prefix = ngAttrPrefixes[i];
1234 if (el = document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) {
1235 name = el.getAttribute(prefix + 'jq');
1240 return (jq.name_ = name);
1243 function concat(array1, array2, index) {
1244 return array1.concat(slice.call(array2, index));
1247 function sliceArgs(args, startIndex) {
1248 return slice.call(args, startIndex || 0);
1255 * @name angular.bind
1260 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for
1261 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also
1262 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as
1263 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application).
1265 * @param {Object} self Context which `fn` should be evaluated in.
1266 * @param {function()} fn Function to be bound.
1267 * @param {...*} args Optional arguments to be prebound to the `fn` function call.
1268 * @returns {function()} Function that wraps the `fn` with all the specified bindings.
1271 function bind(self, fn) {
1272 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : [];
1273 if (isFunction(fn) && !(fn instanceof RegExp)) {
1274 return curryArgs.length
1276 return arguments.length
1277 ? fn.apply(self, concat(curryArgs, arguments, 0))
1278 : fn.apply(self, curryArgs);
1281 return arguments.length
1282 ? fn.apply(self, arguments)
1286 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be)
1292 function toJsonReplacer(key, value) {
1295 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') {
1297 } else if (isWindow(value)) {
1299 } else if (value && document === value) {
1301 } else if (isScope(value)) {
1311 * @name angular.toJson
1316 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be
1317 * stripped since angular uses this notation internally.
1319 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
1320 * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace.
1321 * If set to an integer, the JSON output will contain that many spaces per indentation.
1322 * @returns {string|undefined} JSON-ified string representing `obj`.
1324 function toJson(obj, pretty) {
1325 if (typeof obj === 'undefined') return undefined;
1326 if (!isNumber(pretty)) {
1327 pretty = pretty ? 2 : null;
1329 return JSON.stringify(obj, toJsonReplacer, pretty);
1335 * @name angular.fromJson
1340 * Deserializes a JSON string.
1342 * @param {string} json JSON string to deserialize.
1343 * @returns {Object|Array|string|number} Deserialized JSON string.
1345 function fromJson(json) {
1346 return isString(json)
1352 function timezoneToOffset(timezone, fallback) {
1353 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
1354 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
1358 function addDateMinutes(date, minutes) {
1359 date = new Date(date.getTime());
1360 date.setMinutes(date.getMinutes() + minutes);
1365 function convertTimezoneToLocal(date, timezone, reverse) {
1366 reverse = reverse ? -1 : 1;
1367 var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
1368 return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
1373 * @returns {string} Returns the string representation of the element.
1375 function startingTag(element) {
1376 element = jqLite(element).clone();
1378 // turns out IE does not let you set .html() on elements which
1379 // are not allowed to have children. So we just ignore it.
1382 var elemHtml = jqLite('<div>').append(element).html();
1384 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
1386 match(/^(<[^>]+>)/)[1].
1387 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
1389 return lowercase(elemHtml);
1395 /////////////////////////////////////////////////
1398 * Tries to decode the URI component without throwing an exception.
1401 * @param str value potential URI component to check.
1402 * @returns {boolean} True if `value` can be decoded
1403 * with the decodeURIComponent function.
1405 function tryDecodeURIComponent(value) {
1407 return decodeURIComponent(value);
1409 // Ignore any invalid uri component
1415 * Parses an escaped url query string into key-value pairs.
1416 * @returns {Object.<string,boolean|Array>}
1418 function parseKeyValue(/**string*/keyValue) {
1420 forEach((keyValue || "").split('&'), function(keyValue) {
1421 var splitPoint, key, val;
1423 key = keyValue = keyValue.replace(/\+/g,'%20');
1424 splitPoint = keyValue.indexOf('=');
1425 if (splitPoint !== -1) {
1426 key = keyValue.substring(0, splitPoint);
1427 val = keyValue.substring(splitPoint + 1);
1429 key = tryDecodeURIComponent(key);
1430 if (isDefined(key)) {
1431 val = isDefined(val) ? tryDecodeURIComponent(val) : true;
1432 if (!hasOwnProperty.call(obj, key)) {
1434 } else if (isArray(obj[key])) {
1437 obj[key] = [obj[key],val];
1445 function toKeyValue(obj) {
1447 forEach(obj, function(value, key) {
1448 if (isArray(value)) {
1449 forEach(value, function(arrayValue) {
1450 parts.push(encodeUriQuery(key, true) +
1451 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true)));
1454 parts.push(encodeUriQuery(key, true) +
1455 (value === true ? '' : '=' + encodeUriQuery(value, true)));
1458 return parts.length ? parts.join('&') : '';
1463 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
1464 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
1467 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1468 * pct-encoded = "%" HEXDIG HEXDIG
1469 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1470 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1471 * / "*" / "+" / "," / ";" / "="
1473 function encodeUriSegment(val) {
1474 return encodeUriQuery(val, true).
1475 replace(/%26/gi, '&').
1476 replace(/%3D/gi, '=').
1477 replace(/%2B/gi, '+');
1482 * This method is intended for encoding *key* or *value* parts of query component. We need a custom
1483 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
1484 * encoded per http://tools.ietf.org/html/rfc3986:
1485 * query = *( pchar / "/" / "?" )
1486 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1487 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1488 * pct-encoded = "%" HEXDIG HEXDIG
1489 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1490 * / "*" / "+" / "," / ";" / "="
1492 function encodeUriQuery(val, pctEncodeSpaces) {
1493 return encodeURIComponent(val).
1494 replace(/%40/gi, '@').
1495 replace(/%3A/gi, ':').
1496 replace(/%24/g, '$').
1497 replace(/%2C/gi, ',').
1498 replace(/%3B/gi, ';').
1499 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
1502 var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];
1504 function getNgAttribute(element, ngAttr) {
1505 var attr, i, ii = ngAttrPrefixes.length;
1506 for (i = 0; i < ii; ++i) {
1507 attr = ngAttrPrefixes[i] + ngAttr;
1508 if (isString(attr = element.getAttribute(attr))) {
1521 * @param {angular.Module} ngApp an optional application
1522 * {@link angular.module module} name to load.
1523 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be
1524 * created in "strict-di" mode. This means that the application will fail to invoke functions which
1525 * do not use explicit function annotation (and are thus unsuitable for minification), as described
1526 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in
1527 * tracking down the root of these bugs.
1531 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive
1532 * designates the **root element** of the application and is typically placed near the root element
1533 * of the page - e.g. on the `<body>` or `<html>` tags.
1535 * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp`
1536 * found in the document will be used to define the root element to auto-bootstrap as an
1537 * application. To run multiple applications in an HTML document you must manually bootstrap them using
1538 * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other.
1540 * You can specify an **AngularJS module** to be used as the root module for the application. This
1541 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It
1542 * should contain the application code needed or have dependencies on other modules that will
1543 * contain the code. See {@link angular.module} for more information.
1545 * In the example below if the `ngApp` directive were not placed on the `html` element then the
1546 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
1547 * would not be resolved to `3`.
1549 * `ngApp` is the easiest, and most common way to bootstrap an application.
1551 <example module="ngAppDemo">
1552 <file name="index.html">
1553 <div ng-controller="ngAppDemoController">
1554 I can add: {{a}} + {{b}} = {{ a+b }}
1557 <file name="script.js">
1558 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
1565 * Using `ngStrictDi`, you would see something like this:
1567 <example ng-app-included="true">
1568 <file name="index.html">
1569 <div ng-app="ngAppStrictDemo" ng-strict-di>
1570 <div ng-controller="GoodController1">
1571 I can add: {{a}} + {{b}} = {{ a+b }}
1573 <p>This renders because the controller does not fail to
1574 instantiate, by using explicit annotation style (see
1575 script.js for details)
1579 <div ng-controller="GoodController2">
1580 Name: <input ng-model="name"><br />
1583 <p>This renders because the controller does not fail to
1584 instantiate, by using explicit annotation style
1585 (see script.js for details)
1589 <div ng-controller="BadController">
1590 I can add: {{a}} + {{b}} = {{ a+b }}
1592 <p>The controller could not be instantiated, due to relying
1593 on automatic function annotations (which are disabled in
1594 strict mode). As such, the content of this section is not
1595 interpolated, and there should be an error in your web console.
1600 <file name="script.js">
1601 angular.module('ngAppStrictDemo', [])
1602 // BadController will fail to instantiate, due to relying on automatic function annotation,
1603 // rather than an explicit annotation
1604 .controller('BadController', function($scope) {
1608 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated,
1609 // due to using explicit annotations using the array style and $inject property, respectively.
1610 .controller('GoodController1', ['$scope', function($scope) {
1614 .controller('GoodController2', GoodController2);
1615 function GoodController2($scope) {
1616 $scope.name = "World";
1618 GoodController2.$inject = ['$scope'];
1620 <file name="style.css">
1621 div[ng-controller] {
1623 -webkit-border-radius: 4px;
1628 div[ng-controller^=Good] {
1629 border-color: #d6e9c6;
1630 background-color: #dff0d8;
1633 div[ng-controller^=Bad] {
1634 border-color: #ebccd1;
1635 background-color: #f2dede;
1642 function angularInit(element, bootstrap) {
1647 // The element `element` has priority over any other element
1648 forEach(ngAttrPrefixes, function(prefix) {
1649 var name = prefix + 'app';
1651 if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
1652 appElement = element;
1653 module = element.getAttribute(name);
1656 forEach(ngAttrPrefixes, function(prefix) {
1657 var name = prefix + 'app';
1660 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) {
1661 appElement = candidate;
1662 module = candidate.getAttribute(name);
1666 config.strictDi = getNgAttribute(appElement, "strict-di") !== null;
1667 bootstrap(appElement, module ? [module] : [], config);
1673 * @name angular.bootstrap
1676 * Use this function to manually start up angular application.
1678 * See: {@link guide/bootstrap Bootstrap}
1680 * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually.
1681 * They must use {@link ng.directive:ngApp ngApp}.
1683 * Angular will detect if it has been loaded into the browser more than once and only allow the
1684 * first loaded script to be bootstrapped and will report a warning to the browser console for
1685 * each of the subsequent scripts. This prevents strange results in applications, where otherwise
1686 * multiple instances of Angular try to work on the DOM.
1692 * <div ng-controller="WelcomeController">
1696 * <script src="angular.js"></script>
1698 * var app = angular.module('demo', [])
1699 * .controller('WelcomeController', function($scope) {
1700 * $scope.greeting = 'Welcome!';
1702 * angular.bootstrap(document, ['demo']);
1708 * @param {DOMElement} element DOM element which is the root of angular application.
1709 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application.
1710 * Each item in the array should be the name of a predefined module or a (DI annotated)
1711 * function that will be invoked by the injector as a `config` block.
1712 * See: {@link angular.module modules}
1713 * @param {Object=} config an object for defining configuration options for the application. The
1714 * following keys are supported:
1716 * * `strictDi` - disable automatic function annotation for the application. This is meant to
1717 * assist in finding bugs which break minified code. Defaults to `false`.
1719 * @returns {auto.$injector} Returns the newly created injector for this app.
1721 function bootstrap(element, modules, config) {
1722 if (!isObject(config)) config = {};
1723 var defaultConfig = {
1726 config = extend(defaultConfig, config);
1727 var doBootstrap = function() {
1728 element = jqLite(element);
1730 if (element.injector()) {
1731 var tag = (element[0] === document) ? 'document' : startingTag(element);
1732 //Encode angle brackets to prevent input from being sanitized to empty string #8683
1735 "App Already Bootstrapped with this Element '{0}'",
1736 tag.replace(/</,'<').replace(/>/,'>'));
1739 modules = modules || [];
1740 modules.unshift(['$provide', function($provide) {
1741 $provide.value('$rootElement', element);
1744 if (config.debugInfoEnabled) {
1745 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
1746 modules.push(['$compileProvider', function($compileProvider) {
1747 $compileProvider.debugInfoEnabled(true);
1751 modules.unshift('ng');
1752 var injector = createInjector(modules, config.strictDi);
1753 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
1754 function bootstrapApply(scope, element, compile, injector) {
1755 scope.$apply(function() {
1756 element.data('$injector', injector);
1757 compile(element)(scope);
1764 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/;
1765 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
1767 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) {
1768 config.debugInfoEnabled = true;
1769 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, '');
1772 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
1773 return doBootstrap();
1776 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
1777 angular.resumeBootstrap = function(extraModules) {
1778 forEach(extraModules, function(module) {
1779 modules.push(module);
1781 return doBootstrap();
1784 if (isFunction(angular.resumeDeferredBootstrap)) {
1785 angular.resumeDeferredBootstrap();
1791 * @name angular.reloadWithDebugInfo
1794 * Use this function to reload the current application with debug information turned on.
1795 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`.
1797 * See {@link ng.$compileProvider#debugInfoEnabled} for more.
1799 function reloadWithDebugInfo() {
1800 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name;
1801 window.location.reload();
1805 * @name angular.getTestability
1808 * Get the testability service for the instance of Angular on the given
1810 * @param {DOMElement} element DOM element which is the root of angular application.
1812 function getTestability(rootElement) {
1813 var injector = angular.element(rootElement).injector();
1815 throw ngMinErr('test',
1816 'no injector found for element argument to getTestability');
1818 return injector.get('$$testability');
1821 var SNAKE_CASE_REGEXP = /[A-Z]/g;
1822 function snake_case(name, separator) {
1823 separator = separator || '_';
1824 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
1825 return (pos ? separator : '') + letter.toLowerCase();
1829 var bindJQueryFired = false;
1830 var skipDestroyOnNextJQueryCleanData;
1831 function bindJQuery() {
1832 var originalCleanData;
1834 if (bindJQueryFired) {
1838 // bind to jQuery if present;
1840 jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present)
1841 !jqName ? undefined : // use jqLite
1842 window[jqName]; // use jQuery specified by `ngJq`
1844 // Use jQuery if it exists with proper functionality, otherwise default to us.
1845 // Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
1846 // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older
1847 // versions. It will not work for sure with jQuery <1.7, though.
1848 if (jQuery && jQuery.fn.on) {
1851 scope: JQLitePrototype.scope,
1852 isolateScope: JQLitePrototype.isolateScope,
1853 controller: JQLitePrototype.controller,
1854 injector: JQLitePrototype.injector,
1855 inheritedData: JQLitePrototype.inheritedData
1858 // All nodes removed from the DOM via various jQuery APIs like .remove()
1859 // are passed through jQuery.cleanData. Monkey-patch this method to fire
1860 // the $destroy event on all removed nodes.
1861 originalCleanData = jQuery.cleanData;
1862 jQuery.cleanData = function(elems) {
1864 if (!skipDestroyOnNextJQueryCleanData) {
1865 for (var i = 0, elem; (elem = elems[i]) != null; i++) {
1866 events = jQuery._data(elem, "events");
1867 if (events && events.$destroy) {
1868 jQuery(elem).triggerHandler('$destroy');
1872 skipDestroyOnNextJQueryCleanData = false;
1874 originalCleanData(elems);
1880 angular.element = jqLite;
1882 // Prevent double-proxying.
1883 bindJQueryFired = true;
1887 * throw error if the argument is falsy.
1889 function assertArg(arg, name, reason) {
1891 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
1896 function assertArgFn(arg, name, acceptArrayAnnotation) {
1897 if (acceptArrayAnnotation && isArray(arg)) {
1898 arg = arg[arg.length - 1];
1901 assertArg(isFunction(arg), name, 'not a function, got ' +
1902 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg));
1907 * throw error if the name given is hasOwnProperty
1908 * @param {String} name the name to test
1909 * @param {String} context the context in which the name is used, such as module or directive
1911 function assertNotHasOwnProperty(name, context) {
1912 if (name === 'hasOwnProperty') {
1913 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context);
1918 * Return the value accessible from the object by path. Any undefined traversals are ignored
1919 * @param {Object} obj starting object
1920 * @param {String} path path to traverse
1921 * @param {boolean} [bindFnToScope=true]
1922 * @returns {Object} value as accessible by path
1924 //TODO(misko): this function needs to be removed
1925 function getter(obj, path, bindFnToScope) {
1926 if (!path) return obj;
1927 var keys = path.split('.');
1929 var lastInstance = obj;
1930 var len = keys.length;
1932 for (var i = 0; i < len; i++) {
1935 obj = (lastInstance = obj)[key];
1938 if (!bindFnToScope && isFunction(obj)) {
1939 return bind(lastInstance, obj);
1945 * Return the DOM siblings between the first and last node in the given array.
1946 * @param {Array} array like object
1947 * @returns {Array} the inputted object or a jqLite collection containing the nodes
1949 function getBlockNodes(nodes) {
1950 // TODO(perf): update `nodes` instead of creating a new object?
1951 var node = nodes[0];
1952 var endNode = nodes[nodes.length - 1];
1955 for (var i = 1; node !== endNode && (node = node.nextSibling); i++) {
1956 if (blockNodes || nodes[i] !== node) {
1958 blockNodes = jqLite(slice.call(nodes, 0, i));
1960 blockNodes.push(node);
1964 return blockNodes || nodes;
1969 * Creates a new object without a prototype. This object is useful for lookup without having to
1970 * guard against prototypically inherited properties via hasOwnProperty.
1972 * Related micro-benchmarks:
1973 * - http://jsperf.com/object-create2
1974 * - http://jsperf.com/proto-map-lookup/2
1975 * - http://jsperf.com/for-in-vs-object-keys2
1979 function createMap() {
1980 return Object.create(null);
1983 var NODE_TYPE_ELEMENT = 1;
1984 var NODE_TYPE_ATTRIBUTE = 2;
1985 var NODE_TYPE_TEXT = 3;
1986 var NODE_TYPE_COMMENT = 8;
1987 var NODE_TYPE_DOCUMENT = 9;
1988 var NODE_TYPE_DOCUMENT_FRAGMENT = 11;
1992 * @name angular.Module
1996 * Interface for configuring angular {@link angular.module modules}.
1999 function setupModuleLoader(window) {
2001 var $injectorMinErr = minErr('$injector');
2002 var ngMinErr = minErr('ng');
2004 function ensure(obj, name, factory) {
2005 return obj[name] || (obj[name] = factory());
2008 var angular = ensure(window, 'angular', Object);
2010 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
2011 angular.$$minErr = angular.$$minErr || minErr;
2013 return ensure(angular, 'module', function() {
2014 /** @type {Object.<string, angular.Module>} */
2019 * @name angular.module
2023 * The `angular.module` is a global place for creating, registering and retrieving Angular
2025 * All modules (angular core or 3rd party) that should be available to an application must be
2026 * registered using this mechanism.
2028 * Passing one argument retrieves an existing {@link angular.Module},
2029 * whereas passing more than one argument creates a new {@link angular.Module}
2034 * A module is a collection of services, directives, controllers, filters, and configuration information.
2035 * `angular.module` is used to configure the {@link auto.$injector $injector}.
2038 * // Create a new module
2039 * var myModule = angular.module('myModule', []);
2041 * // register a new service
2042 * myModule.value('appName', 'MyCoolApp');
2044 * // configure existing services inside initialization blocks.
2045 * myModule.config(['$locationProvider', function($locationProvider) {
2046 * // Configure existing providers
2047 * $locationProvider.hashPrefix('!');
2051 * Then you can create an injector and load your modules like this:
2054 * var injector = angular.injector(['ng', 'myModule'])
2057 * However it's more likely that you'll just use
2058 * {@link ng.directive:ngApp ngApp} or
2059 * {@link angular.bootstrap} to simplify this process for you.
2061 * @param {!string} name The name of the module to create or retrieve.
2062 * @param {!Array.<string>=} requires If specified then new module is being created. If
2063 * unspecified then the module is being retrieved for further configuration.
2064 * @param {Function=} configFn Optional configuration function for the module. Same as
2065 * {@link angular.Module#config Module#config()}.
2066 * @returns {module} new module with the {@link angular.Module} api.
2068 return function module(name, requires, configFn) {
2069 var assertNotHasOwnProperty = function(name, context) {
2070 if (name === 'hasOwnProperty') {
2071 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
2075 assertNotHasOwnProperty(name, 'module');
2076 if (requires && modules.hasOwnProperty(name)) {
2077 modules[name] = null;
2079 return ensure(modules, name, function() {
2081 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
2082 "the module name or forgot to load it. If registering a module ensure that you " +
2083 "specify the dependencies as the second argument.", name);
2086 /** @type {!Array.<Array.<*>>} */
2087 var invokeQueue = [];
2089 /** @type {!Array.<Function>} */
2090 var configBlocks = [];
2092 /** @type {!Array.<Function>} */
2095 var config = invokeLater('$injector', 'invoke', 'push', configBlocks);
2097 /** @type {angular.Module} */
2098 var moduleInstance = {
2100 _invokeQueue: invokeQueue,
2101 _configBlocks: configBlocks,
2102 _runBlocks: runBlocks,
2106 * @name angular.Module#requires
2110 * Holds the list of modules which the injector will load before the current module is
2117 * @name angular.Module#name
2121 * Name of the module.
2128 * @name angular.Module#provider
2130 * @param {string} name service name
2131 * @param {Function} providerType Construction function for creating new instance of the
2134 * See {@link auto.$provide#provider $provide.provider()}.
2136 provider: invokeLaterAndSetModuleName('$provide', 'provider'),
2140 * @name angular.Module#factory
2142 * @param {string} name service name
2143 * @param {Function} providerFunction Function for creating new instance of the service.
2145 * See {@link auto.$provide#factory $provide.factory()}.
2147 factory: invokeLaterAndSetModuleName('$provide', 'factory'),
2151 * @name angular.Module#service
2153 * @param {string} name service name
2154 * @param {Function} constructor A constructor function that will be instantiated.
2156 * See {@link auto.$provide#service $provide.service()}.
2158 service: invokeLaterAndSetModuleName('$provide', 'service'),
2162 * @name angular.Module#value
2164 * @param {string} name service name
2165 * @param {*} object Service instance object.
2167 * See {@link auto.$provide#value $provide.value()}.
2169 value: invokeLater('$provide', 'value'),
2173 * @name angular.Module#constant
2175 * @param {string} name constant name
2176 * @param {*} object Constant value.
2178 * Because the constants are fixed, they get applied before other provide methods.
2179 * See {@link auto.$provide#constant $provide.constant()}.
2181 constant: invokeLater('$provide', 'constant', 'unshift'),
2185 * @name angular.Module#decorator
2187 * @param {string} The name of the service to decorate.
2188 * @param {Function} This function will be invoked when the service needs to be
2189 * instantiated and should return the decorated service instance.
2191 * See {@link auto.$provide#decorator $provide.decorator()}.
2193 decorator: invokeLaterAndSetModuleName('$provide', 'decorator'),
2197 * @name angular.Module#animation
2199 * @param {string} name animation name
2200 * @param {Function} animationFactory Factory function for creating new instance of an
2204 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
2207 * Defines an animation hook that can be later used with
2208 * {@link $animate $animate} service and directives that use this service.
2211 * module.animation('.animation-name', function($inject1, $inject2) {
2213 * eventName : function(element, done) {
2214 * //code to run the animation
2215 * //once complete, then run done()
2216 * return function cancellationFunction(element) {
2217 * //code to cancel the animation
2224 * See {@link ng.$animateProvider#register $animateProvider.register()} and
2225 * {@link ngAnimate ngAnimate module} for more information.
2227 animation: invokeLaterAndSetModuleName('$animateProvider', 'register'),
2231 * @name angular.Module#filter
2233 * @param {string} name Filter name - this must be a valid angular expression identifier
2234 * @param {Function} filterFactory Factory function for creating new instance of filter.
2236 * See {@link ng.$filterProvider#register $filterProvider.register()}.
2238 * <div class="alert alert-warning">
2239 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
2240 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
2241 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
2242 * (`myapp_subsection_filterx`).
2245 filter: invokeLaterAndSetModuleName('$filterProvider', 'register'),
2249 * @name angular.Module#controller
2251 * @param {string|Object} name Controller name, or an object map of controllers where the
2252 * keys are the names and the values are the constructors.
2253 * @param {Function} constructor Controller constructor function.
2255 * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
2257 controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'),
2261 * @name angular.Module#directive
2263 * @param {string|Object} name Directive name, or an object map of directives where the
2264 * keys are the names and the values are the factories.
2265 * @param {Function} directiveFactory Factory function for creating new instance of
2268 * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
2270 directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
2274 * @name angular.Module#config
2276 * @param {Function} configFn Execute this function on module load. Useful for service
2279 * Use this method to register work which needs to be performed on module loading.
2280 * For more about how to configure services, see
2281 * {@link providers#provider-recipe Provider Recipe}.
2287 * @name angular.Module#run
2289 * @param {Function} initializationFn Execute this function after injector creation.
2290 * Useful for application initialization.
2292 * Use this method to register work which should be performed when the injector is done
2293 * loading all modules.
2295 run: function(block) {
2296 runBlocks.push(block);
2305 return moduleInstance;
2308 * @param {string} provider
2309 * @param {string} method
2310 * @param {String=} insertMethod
2311 * @returns {angular.Module}
2313 function invokeLater(provider, method, insertMethod, queue) {
2314 if (!queue) queue = invokeQueue;
2316 queue[insertMethod || 'push']([provider, method, arguments]);
2317 return moduleInstance;
2322 * @param {string} provider
2323 * @param {string} method
2324 * @returns {angular.Module}
2326 function invokeLaterAndSetModuleName(provider, method) {
2327 return function(recipeName, factoryFunction) {
2328 if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
2329 invokeQueue.push([provider, method, arguments]);
2330 return moduleInstance;
2339 /* global: toDebugString: true */
2341 function serializeObject(obj) {
2344 return JSON.stringify(obj, function(key, val) {
2345 val = toJsonReplacer(key, val);
2346 if (isObject(val)) {
2348 if (seen.indexOf(val) >= 0) return '...';
2356 function toDebugString(obj) {
2357 if (typeof obj === 'function') {
2358 return obj.toString().replace(/ \{[\s\S]*$/, '');
2359 } else if (isUndefined(obj)) {
2361 } else if (typeof obj !== 'string') {
2362 return serializeObject(obj);
2367 /* global angularModule: true,
2372 htmlAnchorDirective,
2381 ngBindHtmlDirective,
2382 ngBindTemplateDirective,
2384 ngClassEvenDirective,
2385 ngClassOddDirective,
2387 ngControllerDirective,
2392 ngIncludeFillContentDirective,
2394 ngNonBindableDirective,
2395 ngPluralizeDirective,
2400 ngSwitchWhenDirective,
2401 ngSwitchDefaultDirective,
2403 ngTranscludeDirective,
2416 ngModelOptionsDirective,
2417 ngAttributeAliasDirectives,
2420 $AnchorScrollProvider,
2422 $CoreAnimateCssProvider,
2423 $$CoreAnimateQueueProvider,
2424 $$CoreAnimateRunnerProvider,
2426 $CacheFactoryProvider,
2427 $ControllerProvider,
2429 $ExceptionHandlerProvider,
2431 $$ForceReflowProvider,
2432 $InterpolateProvider,
2436 $HttpParamSerializerProvider,
2437 $HttpParamSerializerJQLikeProvider,
2438 $HttpBackendProvider,
2439 $xhrFactoryProvider,
2446 $$SanitizeUriProvider,
2448 $SceDelegateProvider,
2450 $TemplateCacheProvider,
2451 $TemplateRequestProvider,
2452 $$TestabilityProvider,
2457 $$CookieReaderProvider
2463 * @name angular.version
2466 * An object that contains information about the current AngularJS version.
2468 * This object has the following properties:
2470 * - `full` – `{string}` – Full version string, such as "0.9.18".
2471 * - `major` – `{number}` – Major version number, such as "0".
2472 * - `minor` – `{number}` – Minor version number, such as "9".
2473 * - `dot` – `{number}` – Dot version number, such as "18".
2474 * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
2477 full: '1.4.8', // all of these placeholder strings will be replaced by grunt's
2478 major: 1, // package task
2481 codeName: 'ice-manipulation'
2485 function publishExternalAPI(angular) {
2487 'bootstrap': bootstrap,
2494 'injector': createInjector,
2498 'fromJson': fromJson,
2499 'identity': identity,
2500 'isUndefined': isUndefined,
2501 'isDefined': isDefined,
2502 'isString': isString,
2503 'isFunction': isFunction,
2504 'isObject': isObject,
2505 'isNumber': isNumber,
2506 'isElement': isElement,
2510 'lowercase': lowercase,
2511 'uppercase': uppercase,
2512 'callbacks': {counter: 0},
2513 'getTestability': getTestability,
2516 'reloadWithDebugInfo': reloadWithDebugInfo
2519 angularModule = setupModuleLoader(window);
2521 angularModule('ng', ['ngLocale'], ['$provide',
2522 function ngModule($provide) {
2523 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
2525 $$sanitizeUri: $$SanitizeUriProvider
2527 $provide.provider('$compile', $CompileProvider).
2529 a: htmlAnchorDirective,
2530 input: inputDirective,
2531 textarea: inputDirective,
2532 form: formDirective,
2533 script: scriptDirective,
2534 select: selectDirective,
2535 style: styleDirective,
2536 option: optionDirective,
2537 ngBind: ngBindDirective,
2538 ngBindHtml: ngBindHtmlDirective,
2539 ngBindTemplate: ngBindTemplateDirective,
2540 ngClass: ngClassDirective,
2541 ngClassEven: ngClassEvenDirective,
2542 ngClassOdd: ngClassOddDirective,
2543 ngCloak: ngCloakDirective,
2544 ngController: ngControllerDirective,
2545 ngForm: ngFormDirective,
2546 ngHide: ngHideDirective,
2547 ngIf: ngIfDirective,
2548 ngInclude: ngIncludeDirective,
2549 ngInit: ngInitDirective,
2550 ngNonBindable: ngNonBindableDirective,
2551 ngPluralize: ngPluralizeDirective,
2552 ngRepeat: ngRepeatDirective,
2553 ngShow: ngShowDirective,
2554 ngStyle: ngStyleDirective,
2555 ngSwitch: ngSwitchDirective,
2556 ngSwitchWhen: ngSwitchWhenDirective,
2557 ngSwitchDefault: ngSwitchDefaultDirective,
2558 ngOptions: ngOptionsDirective,
2559 ngTransclude: ngTranscludeDirective,
2560 ngModel: ngModelDirective,
2561 ngList: ngListDirective,
2562 ngChange: ngChangeDirective,
2563 pattern: patternDirective,
2564 ngPattern: patternDirective,
2565 required: requiredDirective,
2566 ngRequired: requiredDirective,
2567 minlength: minlengthDirective,
2568 ngMinlength: minlengthDirective,
2569 maxlength: maxlengthDirective,
2570 ngMaxlength: maxlengthDirective,
2571 ngValue: ngValueDirective,
2572 ngModelOptions: ngModelOptionsDirective
2575 ngInclude: ngIncludeFillContentDirective
2577 directive(ngAttributeAliasDirectives).
2578 directive(ngEventDirectives);
2580 $anchorScroll: $AnchorScrollProvider,
2581 $animate: $AnimateProvider,
2582 $animateCss: $CoreAnimateCssProvider,
2583 $$animateQueue: $$CoreAnimateQueueProvider,
2584 $$AnimateRunner: $$CoreAnimateRunnerProvider,
2585 $browser: $BrowserProvider,
2586 $cacheFactory: $CacheFactoryProvider,
2587 $controller: $ControllerProvider,
2588 $document: $DocumentProvider,
2589 $exceptionHandler: $ExceptionHandlerProvider,
2590 $filter: $FilterProvider,
2591 $$forceReflow: $$ForceReflowProvider,
2592 $interpolate: $InterpolateProvider,
2593 $interval: $IntervalProvider,
2594 $http: $HttpProvider,
2595 $httpParamSerializer: $HttpParamSerializerProvider,
2596 $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
2597 $httpBackend: $HttpBackendProvider,
2598 $xhrFactory: $xhrFactoryProvider,
2599 $location: $LocationProvider,
2601 $parse: $ParseProvider,
2602 $rootScope: $RootScopeProvider,
2606 $sceDelegate: $SceDelegateProvider,
2607 $sniffer: $SnifferProvider,
2608 $templateCache: $TemplateCacheProvider,
2609 $templateRequest: $TemplateRequestProvider,
2610 $$testability: $$TestabilityProvider,
2611 $timeout: $TimeoutProvider,
2612 $window: $WindowProvider,
2613 $$rAF: $$RAFProvider,
2614 $$jqLite: $$jqLiteProvider,
2615 $$HashMap: $$HashMapProvider,
2616 $$cookieReader: $$CookieReaderProvider
2622 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2623 * Any commits to this file should be reviewed with security in mind. *
2624 * Changes to this file can potentially create security vulnerabilities. *
2625 * An approval from 2 Core members with history of modifying *
2626 * this file is required. *
2628 * Does the change somehow allow for arbitrary javascript to be executed? *
2629 * Or allows for someone to change the prototype of built-in objects? *
2630 * Or gives undesired access to variables likes document or window? *
2631 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2633 /* global JQLitePrototype: true,
2634 addEventListenerFn: true,
2635 removeEventListenerFn: true,
2640 //////////////////////////////////
2642 //////////////////////////////////
2646 * @name angular.element
2651 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
2653 * If jQuery is available, `angular.element` is an alias for the
2654 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element`
2655 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite."
2657 * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows
2658 * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most
2659 * commonly needed functionality with the goal of having a very small footprint.</div>
2661 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file.
2663 * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or
2664 * jqLite; they are never raw DOM references.</div>
2666 * ## Angular's jqLite
2667 * jqLite provides only the following jQuery methods:
2669 * - [`addClass()`](http://api.jquery.com/addClass/)
2670 * - [`after()`](http://api.jquery.com/after/)
2671 * - [`append()`](http://api.jquery.com/append/)
2672 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
2673 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData
2674 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
2675 * - [`clone()`](http://api.jquery.com/clone/)
2676 * - [`contents()`](http://api.jquery.com/contents/)
2677 * - [`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'.
2678 * - [`data()`](http://api.jquery.com/data/)
2679 * - [`detach()`](http://api.jquery.com/detach/)
2680 * - [`empty()`](http://api.jquery.com/empty/)
2681 * - [`eq()`](http://api.jquery.com/eq/)
2682 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name
2683 * - [`hasClass()`](http://api.jquery.com/hasClass/)
2684 * - [`html()`](http://api.jquery.com/html/)
2685 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors
2686 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
2687 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter
2688 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors
2689 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors
2690 * - [`prepend()`](http://api.jquery.com/prepend/)
2691 * - [`prop()`](http://api.jquery.com/prop/)
2692 * - [`ready()`](http://api.jquery.com/ready/)
2693 * - [`remove()`](http://api.jquery.com/remove/)
2694 * - [`removeAttr()`](http://api.jquery.com/removeAttr/)
2695 * - [`removeClass()`](http://api.jquery.com/removeClass/)
2696 * - [`removeData()`](http://api.jquery.com/removeData/)
2697 * - [`replaceWith()`](http://api.jquery.com/replaceWith/)
2698 * - [`text()`](http://api.jquery.com/text/)
2699 * - [`toggleClass()`](http://api.jquery.com/toggleClass/)
2700 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers.
2701 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter
2702 * - [`val()`](http://api.jquery.com/val/)
2703 * - [`wrap()`](http://api.jquery.com/wrap/)
2705 * ## jQuery/jqLite Extras
2706 * Angular also provides the following additional methods and events to both jQuery and jqLite:
2709 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event
2710 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM
2711 * element before it is removed.
2714 * - `controller(name)` - retrieves the controller of the current element or its parent. By default
2715 * retrieves controller associated with the `ngController` directive. If `name` is provided as
2716 * camelCase directive name, then the controller for this directive will be retrieved (e.g.
2718 * - `injector()` - retrieves the injector of the current element or its parent.
2719 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current
2720 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to
2722 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the
2723 * current element. This getter should be used only on elements that contain a directive which starts a new isolate
2724 * scope. Calling `scope()` on this element always returns the original non-isolate scope.
2725 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled.
2726 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
2727 * parent element is reached.
2729 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
2730 * @returns {Object} jQuery object.
2733 JQLite.expando = 'ng339';
2735 var jqCache = JQLite.cache = {},
2737 addEventListenerFn = function(element, type, fn) {
2738 element.addEventListener(type, fn, false);
2740 removeEventListenerFn = function(element, type, fn) {
2741 element.removeEventListener(type, fn, false);
2745 * !!! This is an undocumented "private" function !!!
2747 JQLite._data = function(node) {
2748 //jQuery always returns an object on cache miss
2749 return this.cache[node[this.expando]] || {};
2752 function jqNextId() { return ++jqId; }
2755 var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
2756 var MOZ_HACK_REGEXP = /^moz([A-Z])/;
2757 var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"};
2758 var jqLiteMinErr = minErr('jqLite');
2761 * Converts snake_case to camelCase.
2762 * Also there is special case for Moz prefix starting with upper case letter.
2763 * @param name Name to normalize
2765 function camelCase(name) {
2767 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
2768 return offset ? letter.toUpperCase() : letter;
2770 replace(MOZ_HACK_REGEXP, 'Moz$1');
2773 var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/;
2774 var HTML_REGEXP = /<|&#?\w+;/;
2775 var TAG_NAME_REGEXP = /<([\w:-]+)/;
2776 var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi;
2779 'option': [1, '<select multiple="multiple">', '</select>'],
2781 'thead': [1, '<table>', '</table>'],
2782 'col': [2, '<table><colgroup>', '</colgroup></table>'],
2783 'tr': [2, '<table><tbody>', '</tbody></table>'],
2784 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'],
2785 '_default': [0, "", ""]
2788 wrapMap.optgroup = wrapMap.option;
2789 wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
2790 wrapMap.th = wrapMap.td;
2793 function jqLiteIsTextNode(html) {
2794 return !HTML_REGEXP.test(html);
2797 function jqLiteAcceptsData(node) {
2798 // The window object can accept data but has no nodeType
2799 // Otherwise we are only interested in elements (1) and documents (9)
2800 var nodeType = node.nodeType;
2801 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
2804 function jqLiteHasData(node) {
2805 for (var key in jqCache[node.ng339]) {
2811 function jqLiteBuildFragment(html, context) {
2813 fragment = context.createDocumentFragment(),
2816 if (jqLiteIsTextNode(html)) {
2817 // Convert non-html into a text node
2818 nodes.push(context.createTextNode(html));
2820 // Convert html into DOM nodes
2821 tmp = tmp || fragment.appendChild(context.createElement("div"));
2822 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase();
2823 wrap = wrapMap[tag] || wrapMap._default;
2824 tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2];
2826 // Descend through wrappers to the right content
2829 tmp = tmp.lastChild;
2832 nodes = concat(nodes, tmp.childNodes);
2834 tmp = fragment.firstChild;
2835 tmp.textContent = "";
2838 // Remove wrapper from fragment
2839 fragment.textContent = "";
2840 fragment.innerHTML = ""; // Clear inner HTML
2841 forEach(nodes, function(node) {
2842 fragment.appendChild(node);
2848 function jqLiteParseHTML(html, context) {
2849 context = context || document;
2852 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) {
2853 return [context.createElement(parsed[1])];
2856 if ((parsed = jqLiteBuildFragment(html, context))) {
2857 return parsed.childNodes;
2864 // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
2865 var jqLiteContains = Node.prototype.contains || function(arg) {
2866 // jshint bitwise: false
2867 return !!(this.compareDocumentPosition(arg) & 16);
2868 // jshint bitwise: true
2871 /////////////////////////////////////////////
2872 function JQLite(element) {
2873 if (element instanceof JQLite) {
2879 if (isString(element)) {
2880 element = trim(element);
2883 if (!(this instanceof JQLite)) {
2884 if (argIsString && element.charAt(0) != '<') {
2885 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
2887 return new JQLite(element);
2891 jqLiteAddNodes(this, jqLiteParseHTML(element));
2893 jqLiteAddNodes(this, element);
2897 function jqLiteClone(element) {
2898 return element.cloneNode(true);
2901 function jqLiteDealoc(element, onlyDescendants) {
2902 if (!onlyDescendants) jqLiteRemoveData(element);
2904 if (element.querySelectorAll) {
2905 var descendants = element.querySelectorAll('*');
2906 for (var i = 0, l = descendants.length; i < l; i++) {
2907 jqLiteRemoveData(descendants[i]);
2912 function jqLiteOff(element, type, fn, unsupported) {
2913 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument');
2915 var expandoStore = jqLiteExpandoStore(element);
2916 var events = expandoStore && expandoStore.events;
2917 var handle = expandoStore && expandoStore.handle;
2919 if (!handle) return; //no listeners registered
2922 for (type in events) {
2923 if (type !== '$destroy') {
2924 removeEventListenerFn(element, type, handle);
2926 delete events[type];
2930 var removeHandler = function(type) {
2931 var listenerFns = events[type];
2932 if (isDefined(fn)) {
2933 arrayRemove(listenerFns || [], fn);
2935 if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) {
2936 removeEventListenerFn(element, type, handle);
2937 delete events[type];
2941 forEach(type.split(' '), function(type) {
2942 removeHandler(type);
2943 if (MOUSE_EVENT_MAP[type]) {
2944 removeHandler(MOUSE_EVENT_MAP[type]);
2950 function jqLiteRemoveData(element, name) {
2951 var expandoId = element.ng339;
2952 var expandoStore = expandoId && jqCache[expandoId];
2956 delete expandoStore.data[name];
2960 if (expandoStore.handle) {
2961 if (expandoStore.events.$destroy) {
2962 expandoStore.handle({}, '$destroy');
2966 delete jqCache[expandoId];
2967 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
2972 function jqLiteExpandoStore(element, createIfNecessary) {
2973 var expandoId = element.ng339,
2974 expandoStore = expandoId && jqCache[expandoId];
2976 if (createIfNecessary && !expandoStore) {
2977 element.ng339 = expandoId = jqNextId();
2978 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined};
2981 return expandoStore;
2985 function jqLiteData(element, key, value) {
2986 if (jqLiteAcceptsData(element)) {
2988 var isSimpleSetter = isDefined(value);
2989 var isSimpleGetter = !isSimpleSetter && key && !isObject(key);
2990 var massGetter = !key;
2991 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter);
2992 var data = expandoStore && expandoStore.data;
2994 if (isSimpleSetter) { // data('key', value)
2997 if (massGetter) { // data()
3000 if (isSimpleGetter) { // data('key')
3001 // don't force creation of expandoStore if it doesn't exist yet
3002 return data && data[key];
3003 } else { // mass-setter: data({key1: val1, key2: val2})
3011 function jqLiteHasClass(element, selector) {
3012 if (!element.getAttribute) return false;
3013 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " ").
3014 indexOf(" " + selector + " ") > -1);
3017 function jqLiteRemoveClass(element, cssClasses) {
3018 if (cssClasses && element.setAttribute) {
3019 forEach(cssClasses.split(' '), function(cssClass) {
3020 element.setAttribute('class', trim(
3021 (" " + (element.getAttribute('class') || '') + " ")
3022 .replace(/[\n\t]/g, " ")
3023 .replace(" " + trim(cssClass) + " ", " "))
3029 function jqLiteAddClass(element, cssClasses) {
3030 if (cssClasses && element.setAttribute) {
3031 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ')
3032 .replace(/[\n\t]/g, " ");
3034 forEach(cssClasses.split(' '), function(cssClass) {
3035 cssClass = trim(cssClass);
3036 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
3037 existingClasses += cssClass + ' ';
3041 element.setAttribute('class', trim(existingClasses));
3046 function jqLiteAddNodes(root, elements) {
3047 // THIS CODE IS VERY HOT. Don't make changes without benchmarking.
3051 // if a Node (the most common case)
3052 if (elements.nodeType) {
3053 root[root.length++] = elements;
3055 var length = elements.length;
3057 // if an Array or NodeList and not a Window
3058 if (typeof length === 'number' && elements.window !== elements) {
3060 for (var i = 0; i < length; i++) {
3061 root[root.length++] = elements[i];
3065 root[root.length++] = elements;
3072 function jqLiteController(element, name) {
3073 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller');
3076 function jqLiteInheritedData(element, name, value) {
3077 // if element is the document object work with the html element instead
3078 // this makes $(document).scope() possible
3079 if (element.nodeType == NODE_TYPE_DOCUMENT) {
3080 element = element.documentElement;
3082 var names = isArray(name) ? name : [name];
3085 for (var i = 0, ii = names.length; i < ii; i++) {
3086 if (isDefined(value = jqLite.data(element, names[i]))) return value;
3089 // If dealing with a document fragment node with a host element, and no parent, use the host
3090 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
3091 // to lookup parent controllers.
3092 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host);
3096 function jqLiteEmpty(element) {
3097 jqLiteDealoc(element, true);
3098 while (element.firstChild) {
3099 element.removeChild(element.firstChild);
3103 function jqLiteRemove(element, keepData) {
3104 if (!keepData) jqLiteDealoc(element);
3105 var parent = element.parentNode;
3106 if (parent) parent.removeChild(element);
3110 function jqLiteDocumentLoaded(action, win) {
3111 win = win || window;
3112 if (win.document.readyState === 'complete') {
3113 // Force the action to be run async for consistent behaviour
3114 // from the action's point of view
3115 // i.e. it will definitely not be in a $apply
3116 win.setTimeout(action);
3118 // No need to unbind this handler as load is only ever called once
3119 jqLite(win).on('load', action);
3123 //////////////////////////////////////////
3124 // Functions which are declared directly.
3125 //////////////////////////////////////////
3126 var JQLitePrototype = JQLite.prototype = {
3127 ready: function(fn) {
3130 function trigger() {
3136 // check if document is already loaded
3137 if (document.readyState === 'complete') {
3138 setTimeout(trigger);
3140 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9
3141 // we can not use jqLite since we are not done loading and jQuery could be loaded later.
3143 JQLite(window).on('load', trigger); // fallback to window.onload for others
3147 toString: function() {
3149 forEach(this, function(e) { value.push('' + e);});
3150 return '[' + value.join(', ') + ']';
3153 eq: function(index) {
3154 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
3163 //////////////////////////////////////////
3164 // Functions iterating getter/setters.
3165 // these functions return self on setter and
3167 //////////////////////////////////////////
3168 var BOOLEAN_ATTR = {};
3169 forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) {
3170 BOOLEAN_ATTR[lowercase(value)] = value;
3172 var BOOLEAN_ELEMENTS = {};
3173 forEach('input,select,option,textarea,button,form,details'.split(','), function(value) {
3174 BOOLEAN_ELEMENTS[value] = true;
3176 var ALIASED_ATTR = {
3177 'ngMinlength': 'minlength',
3178 'ngMaxlength': 'maxlength',
3181 'ngPattern': 'pattern'
3184 function getBooleanAttrName(element, name) {
3185 // check dom last since we will most likely fail on name
3186 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
3188 // booleanAttr is here twice to minimize DOM access
3189 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
3192 function getAliasedAttrName(name) {
3193 return ALIASED_ATTR[name];
3198 removeData: jqLiteRemoveData,
3199 hasData: jqLiteHasData
3200 }, function(fn, name) {
3206 inheritedData: jqLiteInheritedData,
3208 scope: function(element) {
3209 // Can't use jqLiteData here directly so we stay compatible with jQuery!
3210 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']);
3213 isolateScope: function(element) {
3214 // Can't use jqLiteData here directly so we stay compatible with jQuery!
3215 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate');
3218 controller: jqLiteController,
3220 injector: function(element) {
3221 return jqLiteInheritedData(element, '$injector');
3224 removeAttr: function(element, name) {
3225 element.removeAttribute(name);
3228 hasClass: jqLiteHasClass,
3230 css: function(element, name, value) {
3231 name = camelCase(name);
3233 if (isDefined(value)) {
3234 element.style[name] = value;
3236 return element.style[name];
3240 attr: function(element, name, value) {
3241 var nodeType = element.nodeType;
3242 if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) {
3245 var lowercasedName = lowercase(name);
3246 if (BOOLEAN_ATTR[lowercasedName]) {
3247 if (isDefined(value)) {
3249 element[name] = true;
3250 element.setAttribute(name, lowercasedName);
3252 element[name] = false;
3253 element.removeAttribute(lowercasedName);
3256 return (element[name] ||
3257 (element.attributes.getNamedItem(name) || noop).specified)
3261 } else if (isDefined(value)) {
3262 element.setAttribute(name, value);
3263 } else if (element.getAttribute) {
3264 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code
3265 // some elements (e.g. Document) don't have get attribute, so return undefined
3266 var ret = element.getAttribute(name, 2);
3267 // normalize non-existing attributes to undefined (as jQuery)
3268 return ret === null ? undefined : ret;
3272 prop: function(element, name, value) {
3273 if (isDefined(value)) {
3274 element[name] = value;
3276 return element[name];
3284 function getText(element, value) {
3285 if (isUndefined(value)) {
3286 var nodeType = element.nodeType;
3287 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : '';
3289 element.textContent = value;
3293 val: function(element, value) {
3294 if (isUndefined(value)) {
3295 if (element.multiple && nodeName_(element) === 'select') {
3297 forEach(element.options, function(option) {
3298 if (option.selected) {
3299 result.push(option.value || option.text);
3302 return result.length === 0 ? null : result;
3304 return element.value;
3306 element.value = value;
3309 html: function(element, value) {
3310 if (isUndefined(value)) {
3311 return element.innerHTML;
3313 jqLiteDealoc(element, true);
3314 element.innerHTML = value;
3318 }, function(fn, name) {
3320 * Properties: writes return selection, reads return first value
3322 JQLite.prototype[name] = function(arg1, arg2) {
3324 var nodeCount = this.length;
3326 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
3327 // in a way that survives minification.
3328 // jqLiteEmpty takes no arguments but is a setter.
3329 if (fn !== jqLiteEmpty &&
3330 (isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) {
3331 if (isObject(arg1)) {
3333 // we are a write, but the object properties are the key/values
3334 for (i = 0; i < nodeCount; i++) {
3335 if (fn === jqLiteData) {
3336 // data() takes the whole object in jQuery
3340 fn(this[i], key, arg1[key]);
3344 // return self for chaining
3347 // we are a read, so read the first child.
3348 // TODO: do we still need this?
3350 // Only if we have $dv do we iterate over all, otherwise it is just the first element.
3351 var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount;
3352 for (var j = 0; j < jj; j++) {
3353 var nodeValue = fn(this[j], arg1, arg2);
3354 value = value ? value + nodeValue : nodeValue;
3359 // we are a write, so apply to all children
3360 for (i = 0; i < nodeCount; i++) {
3361 fn(this[i], arg1, arg2);
3363 // return self for chaining
3369 function createEventHandler(element, events) {
3370 var eventHandler = function(event, type) {
3371 // jQuery specific api
3372 event.isDefaultPrevented = function() {
3373 return event.defaultPrevented;
3376 var eventFns = events[type || event.type];
3377 var eventFnsLength = eventFns ? eventFns.length : 0;
3379 if (!eventFnsLength) return;
3381 if (isUndefined(event.immediatePropagationStopped)) {
3382 var originalStopImmediatePropagation = event.stopImmediatePropagation;
3383 event.stopImmediatePropagation = function() {
3384 event.immediatePropagationStopped = true;
3386 if (event.stopPropagation) {
3387 event.stopPropagation();
3390 if (originalStopImmediatePropagation) {
3391 originalStopImmediatePropagation.call(event);
3396 event.isImmediatePropagationStopped = function() {
3397 return event.immediatePropagationStopped === true;
3400 // Some events have special handlers that wrap the real handler
3401 var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper;
3403 // Copy event handlers in case event handlers array is modified during execution.
3404 if ((eventFnsLength > 1)) {
3405 eventFns = shallowCopy(eventFns);
3408 for (var i = 0; i < eventFnsLength; i++) {
3409 if (!event.isImmediatePropagationStopped()) {
3410 handlerWrapper(element, event, eventFns[i]);
3415 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all
3416 // events on `element`
3417 eventHandler.elem = element;
3418 return eventHandler;
3421 function defaultHandlerWrapper(element, event, handler) {
3422 handler.call(element, event);
3425 function specialMouseHandlerWrapper(target, event, handler) {
3426 // Refer to jQuery's implementation of mouseenter & mouseleave
3427 // Read about mouseenter and mouseleave:
3428 // http://www.quirksmode.org/js/events_mouse.html#link8
3429 var related = event.relatedTarget;
3430 // For mousenter/leave call the handler if related is outside the target.
3431 // NB: No relatedTarget if the mouse left/entered the browser window
3432 if (!related || (related !== target && !jqLiteContains.call(target, related))) {
3433 handler.call(target, event);
3437 //////////////////////////////////////////
3438 // Functions iterating traversal.
3439 // These functions chain results into a single
3441 //////////////////////////////////////////
3443 removeData: jqLiteRemoveData,
3445 on: function jqLiteOn(element, type, fn, unsupported) {
3446 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters');
3448 // Do not add event handlers to non-elements because they will not be cleaned up.
3449 if (!jqLiteAcceptsData(element)) {
3453 var expandoStore = jqLiteExpandoStore(element, true);
3454 var events = expandoStore.events;
3455 var handle = expandoStore.handle;
3458 handle = expandoStore.handle = createEventHandler(element, events);
3461 // http://jsperf.com/string-indexof-vs-split
3462 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type];
3463 var i = types.length;
3465 var addHandler = function(type, specialHandlerWrapper, noEventListener) {
3466 var eventFns = events[type];
3469 eventFns = events[type] = [];
3470 eventFns.specialHandlerWrapper = specialHandlerWrapper;
3471 if (type !== '$destroy' && !noEventListener) {
3472 addEventListenerFn(element, type, handle);
3481 if (MOUSE_EVENT_MAP[type]) {
3482 addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper);
3483 addHandler(type, undefined, true);
3492 one: function(element, type, fn) {
3493 element = jqLite(element);
3495 //add the listener twice so that when it is called
3496 //you can remove the original function and still be
3497 //able to call element.off(ev, fn) normally
3498 element.on(type, function onFn() {
3499 element.off(type, fn);
3500 element.off(type, onFn);
3502 element.on(type, fn);
3505 replaceWith: function(element, replaceNode) {
3506 var index, parent = element.parentNode;
3507 jqLiteDealoc(element);
3508 forEach(new JQLite(replaceNode), function(node) {
3510 parent.insertBefore(node, index.nextSibling);
3512 parent.replaceChild(node, element);
3518 children: function(element) {
3520 forEach(element.childNodes, function(element) {
3521 if (element.nodeType === NODE_TYPE_ELEMENT) {
3522 children.push(element);
3528 contents: function(element) {
3529 return element.contentDocument || element.childNodes || [];
3532 append: function(element, node) {
3533 var nodeType = element.nodeType;
3534 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return;
3536 node = new JQLite(node);
3538 for (var i = 0, ii = node.length; i < ii; i++) {
3539 var child = node[i];
3540 element.appendChild(child);
3544 prepend: function(element, node) {
3545 if (element.nodeType === NODE_TYPE_ELEMENT) {
3546 var index = element.firstChild;
3547 forEach(new JQLite(node), function(child) {
3548 element.insertBefore(child, index);
3553 wrap: function(element, wrapNode) {
3554 wrapNode = jqLite(wrapNode).eq(0).clone()[0];
3555 var parent = element.parentNode;
3557 parent.replaceChild(wrapNode, element);
3559 wrapNode.appendChild(element);
3562 remove: jqLiteRemove,
3564 detach: function(element) {
3565 jqLiteRemove(element, true);
3568 after: function(element, newElement) {
3569 var index = element, parent = element.parentNode;
3570 newElement = new JQLite(newElement);
3572 for (var i = 0, ii = newElement.length; i < ii; i++) {
3573 var node = newElement[i];
3574 parent.insertBefore(node, index.nextSibling);
3579 addClass: jqLiteAddClass,
3580 removeClass: jqLiteRemoveClass,
3582 toggleClass: function(element, selector, condition) {
3584 forEach(selector.split(' '), function(className) {
3585 var classCondition = condition;
3586 if (isUndefined(classCondition)) {
3587 classCondition = !jqLiteHasClass(element, className);
3589 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className);
3594 parent: function(element) {
3595 var parent = element.parentNode;
3596 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null;
3599 next: function(element) {
3600 return element.nextElementSibling;
3603 find: function(element, selector) {
3604 if (element.getElementsByTagName) {
3605 return element.getElementsByTagName(selector);
3613 triggerHandler: function(element, event, extraParameters) {
3615 var dummyEvent, eventFnsCopy, handlerArgs;
3616 var eventName = event.type || event;
3617 var expandoStore = jqLiteExpandoStore(element);
3618 var events = expandoStore && expandoStore.events;
3619 var eventFns = events && events[eventName];
3622 // Create a dummy event to pass to the handlers
3624 preventDefault: function() { this.defaultPrevented = true; },
3625 isDefaultPrevented: function() { return this.defaultPrevented === true; },
3626 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; },
3627 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; },
3628 stopPropagation: noop,
3633 // If a custom event was provided then extend our dummy event with it
3635 dummyEvent = extend(dummyEvent, event);
3638 // Copy event handlers in case event handlers array is modified during execution.
3639 eventFnsCopy = shallowCopy(eventFns);
3640 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent];
3642 forEach(eventFnsCopy, function(fn) {
3643 if (!dummyEvent.isImmediatePropagationStopped()) {
3644 fn.apply(element, handlerArgs);
3649 }, function(fn, name) {
3651 * chaining functions
3653 JQLite.prototype[name] = function(arg1, arg2, arg3) {
3656 for (var i = 0, ii = this.length; i < ii; i++) {
3657 if (isUndefined(value)) {
3658 value = fn(this[i], arg1, arg2, arg3);
3659 if (isDefined(value)) {
3660 // any function which returns a value needs to be wrapped
3661 value = jqLite(value);
3664 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3));
3667 return isDefined(value) ? value : this;
3670 // bind legacy bind/unbind to on/off
3671 JQLite.prototype.bind = JQLite.prototype.on;
3672 JQLite.prototype.unbind = JQLite.prototype.off;
3676 // Provider for private $$jqLite service
3677 function $$jqLiteProvider() {
3678 this.$get = function $$jqLite() {
3679 return extend(JQLite, {
3680 hasClass: function(node, classes) {
3681 if (node.attr) node = node[0];
3682 return jqLiteHasClass(node, classes);
3684 addClass: function(node, classes) {
3685 if (node.attr) node = node[0];
3686 return jqLiteAddClass(node, classes);
3688 removeClass: function(node, classes) {
3689 if (node.attr) node = node[0];
3690 return jqLiteRemoveClass(node, classes);
3697 * Computes a hash of an 'obj'.
3700 * number is number as string
3701 * object is either result of calling $$hashKey function on the object or uniquely generated id,
3702 * that is also assigned to the $$hashKey property of the object.
3705 * @returns {string} hash string such that the same input will have the same hash string.
3706 * The resulting string key is in 'type:hashKey' format.
3708 function hashKey(obj, nextUidFn) {
3709 var key = obj && obj.$$hashKey;
3712 if (typeof key === 'function') {
3713 key = obj.$$hashKey();
3718 var objType = typeof obj;
3719 if (objType == 'function' || (objType == 'object' && obj !== null)) {
3720 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)();
3722 key = objType + ':' + obj;
3729 * HashMap which can use objects as keys
3731 function HashMap(array, isolatedUid) {
3734 this.nextUid = function() {
3738 forEach(array, this.put, this);
3740 HashMap.prototype = {
3742 * Store key value pair
3743 * @param key key to store can be any type
3744 * @param value value to store can be any type
3746 put: function(key, value) {
3747 this[hashKey(key, this.nextUid)] = value;
3752 * @returns {Object} the value for the key
3754 get: function(key) {
3755 return this[hashKey(key, this.nextUid)];
3759 * Remove the key/value pair
3762 remove: function(key) {
3763 var value = this[key = hashKey(key, this.nextUid)];
3769 var $$HashMapProvider = [function() {
3770 this.$get = [function() {
3778 * @name angular.injector
3782 * Creates an injector object that can be used for retrieving services as well as for
3783 * dependency injection (see {@link guide/di dependency injection}).
3785 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
3786 * {@link angular.module}. The `ng` module must be explicitly added.
3787 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which
3788 * disallows argument name annotation inference.
3789 * @returns {injector} Injector object. See {@link auto.$injector $injector}.
3794 * // create an injector
3795 * var $injector = angular.injector(['ng']);
3797 * // use the injector to kick off your application
3798 * // use the type inference to auto inject arguments, or use implicit injection
3799 * $injector.invoke(function($rootScope, $compile, $document) {
3800 * $compile($document)($rootScope);
3801 * $rootScope.$digest();
3805 * Sometimes you want to get access to the injector of a currently running Angular app
3806 * from outside Angular. Perhaps, you want to inject and compile some markup after the
3807 * application has been bootstrapped. You can do this using the extra `injector()` added
3808 * to JQuery/jqLite elements. See {@link angular.element}.
3810 * *This is fairly rare but could be the case if a third party library is injecting the
3813 * In the following example a new block of HTML containing a `ng-controller`
3814 * directive is added to the end of the document body by JQuery. We then compile and link
3815 * it into the current AngularJS scope.
3818 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>');
3819 * $(document.body).append($div);
3821 * angular.element(document).injector().invoke(function($compile) {
3822 * var scope = angular.element($div).scope();
3823 * $compile($div)(scope);
3834 * Implicit module which gets automatically added to each {@link auto.$injector $injector}.
3837 var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m;
3838 var FN_ARG_SPLIT = /,/;
3839 var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
3840 var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
3841 var $injectorMinErr = minErr('$injector');
3843 function anonFn(fn) {
3844 // For anonymous functions, showing at the very least the function signature can help in
3846 var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
3847 args = fnText.match(FN_ARGS);
3849 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
3854 function annotate(fn, strictDi, name) {
3860 if (typeof fn === 'function') {
3861 if (!($inject = fn.$inject)) {
3865 if (!isString(name) || !name) {
3866 name = fn.name || anonFn(fn);
3868 throw $injectorMinErr('strictdi',
3869 '{0} is not using explicit annotation and cannot be invoked in strict mode', name);
3871 fnText = fn.toString().replace(STRIP_COMMENTS, '');
3872 argDecl = fnText.match(FN_ARGS);
3873 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
3874 arg.replace(FN_ARG, function(all, underscore, name) {
3879 fn.$inject = $inject;
3881 } else if (isArray(fn)) {
3882 last = fn.length - 1;
3883 assertArgFn(fn[last], 'fn');
3884 $inject = fn.slice(0, last);
3886 assertArgFn(fn, 'fn', true);
3891 ///////////////////////////////////////
3899 * `$injector` is used to retrieve object instances as defined by
3900 * {@link auto.$provide provider}, instantiate types, invoke methods,
3903 * The following always holds true:
3906 * var $injector = angular.injector();
3907 * expect($injector.get('$injector')).toBe($injector);
3908 * expect($injector.invoke(function($injector) {
3910 * })).toBe($injector);
3913 * # Injection Function Annotation
3915 * JavaScript does not have annotations, and annotations are needed for dependency injection. The
3916 * following are all valid ways of annotating function with injection arguments and are equivalent.
3919 * // inferred (only works if code not minified/obfuscated)
3920 * $injector.invoke(function(serviceA){});
3923 * function explicit(serviceA) {};
3924 * explicit.$inject = ['serviceA'];
3925 * $injector.invoke(explicit);
3928 * $injector.invoke(['serviceA', function(serviceA){}]);
3933 * In JavaScript calling `toString()` on a function returns the function definition. The definition
3934 * can then be parsed and the function arguments can be extracted. This method of discovering
3935 * annotations is disallowed when the injector is in strict mode.
3936 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the
3939 * ## `$inject` Annotation
3940 * By adding an `$inject` property onto a function the injection parameters can be specified.
3943 * As an array of injection names, where the last item in the array is the function to call.
3948 * @name $injector#get
3951 * Return an instance of the service.
3953 * @param {string} name The name of the instance to retrieve.
3954 * @param {string=} caller An optional string to provide the origin of the function call for error messages.
3955 * @return {*} The instance.
3960 * @name $injector#invoke
3963 * Invoke the method and supply the method arguments from the `$injector`.
3965 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are
3966 * injected according to the {@link guide/di $inject Annotation} rules.
3967 * @param {Object=} self The `this` for the invoked method.
3968 * @param {Object=} locals Optional object. If preset then any argument names are read from this
3969 * object first, before the `$injector` is consulted.
3970 * @returns {*} the value returned by the invoked `fn` function.
3975 * @name $injector#has
3978 * Allows the user to query if the particular service exists.
3980 * @param {string} name Name of the service to query.
3981 * @returns {boolean} `true` if injector has given service.
3986 * @name $injector#instantiate
3988 * Create a new instance of JS type. The method takes a constructor function, invokes the new
3989 * operator, and supplies all of the arguments to the constructor function as specified by the
3990 * constructor annotation.
3992 * @param {Function} Type Annotated constructor function.
3993 * @param {Object=} locals Optional object. If preset then any argument names are read from this
3994 * object first, before the `$injector` is consulted.
3995 * @returns {Object} new instance of `Type`.
4000 * @name $injector#annotate
4003 * Returns an array of service names which the function is requesting for injection. This API is
4004 * used by the injector to determine which services need to be injected into the function when the
4005 * function is invoked. There are three ways in which the function can be annotated with the needed
4010 * The simplest form is to extract the dependencies from the arguments of the function. This is done
4011 * by converting the function into a string using `toString()` method and extracting the argument
4015 * function MyController($scope, $route) {
4020 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
4023 * You can disallow this method by using strict injection mode.
4025 * This method does not work with code minification / obfuscation. For this reason the following
4026 * annotation strategies are supported.
4028 * # The `$inject` property
4030 * If a function has an `$inject` property and its value is an array of strings, then the strings
4031 * represent names of services to be injected into the function.
4034 * var MyController = function(obfuscatedScope, obfuscatedRoute) {
4037 * // Define function dependencies
4038 * MyController['$inject'] = ['$scope', '$route'];
4041 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
4044 * # The array notation
4046 * It is often desirable to inline Injected functions and that's when setting the `$inject` property
4047 * is very inconvenient. In these situations using the array notation to specify the dependencies in
4048 * a way that survives minification is a better choice:
4051 * // We wish to write this (not minification / obfuscation safe)
4052 * injector.invoke(function($compile, $rootScope) {
4056 * // We are forced to write break inlining
4057 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
4060 * tmpFn.$inject = ['$compile', '$rootScope'];
4061 * injector.invoke(tmpFn);
4063 * // To better support inline function the inline annotation is supported
4064 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
4069 * expect(injector.annotate(
4070 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
4071 * ).toEqual(['$compile', '$rootScope']);
4074 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to
4075 * be retrieved as described above.
4077 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference.
4079 * @returns {Array.<string>} The names of the services which the function requires.
4091 * The {@link auto.$provide $provide} service has a number of methods for registering components
4092 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on
4093 * {@link angular.Module}.
4095 * An Angular **service** is a singleton object created by a **service factory**. These **service
4096 * factories** are functions which, in turn, are created by a **service provider**.
4097 * The **service providers** are constructor functions. When instantiated they must contain a
4098 * property called `$get`, which holds the **service factory** function.
4100 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the
4101 * correct **service provider**, instantiating it and then calling its `$get` **service factory**
4102 * function to get the instance of the **service**.
4104 * Often services have no configuration options and there is no need to add methods to the service
4105 * provider. The provider will be no more than a constructor function with a `$get` property. For
4106 * these cases the {@link auto.$provide $provide} service has additional helper methods to register
4107 * services without specifying a provider.
4109 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the
4110 * {@link auto.$injector $injector}
4111 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by
4112 * providers and services.
4113 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by
4114 * services, not providers.
4115 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`,
4116 * that will be wrapped in a **service provider** object, whose `$get` property will contain the
4117 * given factory function.
4118 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class`
4119 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate
4120 * a new object using the given constructor function.
4122 * See the individual methods for more information and examples.
4127 * @name $provide#provider
4130 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions
4131 * are constructor functions, whose instances are responsible for "providing" a factory for a
4134 * Service provider names start with the name of the service they provide followed by `Provider`.
4135 * For example, the {@link ng.$log $log} service has a provider called
4136 * {@link ng.$logProvider $logProvider}.
4138 * Service provider objects can have additional methods which allow configuration of the provider
4139 * and its service. Importantly, you can configure what kind of service is created by the `$get`
4140 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a
4141 * method {@link ng.$logProvider#debugEnabled debugEnabled}
4142 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the
4145 * @param {string} name The name of the instance. NOTE: the provider will be available under `name +
4147 * @param {(Object|function())} provider If the provider is:
4149 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using
4150 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created.
4151 * - `Constructor`: a new instance of the provider will be created using
4152 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`.
4154 * @returns {Object} registered provider instance
4158 * The following example shows how to create a simple event tracking service and register it using
4159 * {@link auto.$provide#provider $provide.provider()}.
4162 * // Define the eventTracker provider
4163 * function EventTrackerProvider() {
4164 * var trackingUrl = '/track';
4166 * // A provider method for configuring where the tracked events should been saved
4167 * this.setTrackingUrl = function(url) {
4168 * trackingUrl = url;
4171 * // The service factory function
4172 * this.$get = ['$http', function($http) {
4173 * var trackedEvents = {};
4175 * // Call this to track an event
4176 * event: function(event) {
4177 * var count = trackedEvents[event] || 0;
4179 * trackedEvents[event] = count;
4182 * // Call this to save the tracked events to the trackingUrl
4183 * save: function() {
4184 * $http.post(trackingUrl, trackedEvents);
4190 * describe('eventTracker', function() {
4193 * beforeEach(module(function($provide) {
4194 * // Register the eventTracker provider
4195 * $provide.provider('eventTracker', EventTrackerProvider);
4198 * beforeEach(module(function(eventTrackerProvider) {
4199 * // Configure eventTracker provider
4200 * eventTrackerProvider.setTrackingUrl('/custom-track');
4203 * it('tracks events', inject(function(eventTracker) {
4204 * expect(eventTracker.event('login')).toEqual(1);
4205 * expect(eventTracker.event('login')).toEqual(2);
4208 * it('saves to the tracking url', inject(function(eventTracker, $http) {
4209 * postSpy = spyOn($http, 'post');
4210 * eventTracker.event('login');
4211 * eventTracker.save();
4212 * expect(postSpy).toHaveBeenCalled();
4213 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track');
4214 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track');
4215 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 });
4223 * @name $provide#factory
4226 * Register a **service factory**, which will be called to return the service instance.
4227 * This is short for registering a service where its provider consists of only a `$get` property,
4228 * which is the given service factory function.
4229 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to
4230 * configure your service in a provider.
4232 * @param {string} name The name of the instance.
4233 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation.
4234 * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`.
4235 * @returns {Object} registered provider instance
4238 * Here is an example of registering a service
4240 * $provide.factory('ping', ['$http', function($http) {
4241 * return function ping() {
4242 * return $http.send('/ping');
4246 * You would then inject and use this service like this:
4248 * someModule.controller('Ctrl', ['ping', function(ping) {
4257 * @name $provide#service
4260 * Register a **service constructor**, which will be invoked with `new` to create the service
4262 * This is short for registering a service where its provider's `$get` property is the service
4263 * constructor function that will be used to instantiate the service instance.
4265 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service
4268 * @param {string} name The name of the instance.
4269 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function)
4270 * that will be instantiated.
4271 * @returns {Object} registered provider instance
4274 * Here is an example of registering a service using
4275 * {@link auto.$provide#service $provide.service(class)}.
4277 * var Ping = function($http) {
4278 * this.$http = $http;
4281 * Ping.$inject = ['$http'];
4283 * Ping.prototype.send = function() {
4284 * return this.$http.get('/ping');
4286 * $provide.service('ping', Ping);
4288 * You would then inject and use this service like this:
4290 * someModule.controller('Ctrl', ['ping', function(ping) {
4299 * @name $provide#value
4302 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a
4303 * number, an array, an object or a function. This is short for registering a service where its
4304 * provider's `$get` property is a factory function that takes no arguments and returns the **value
4307 * Value services are similar to constant services, except that they cannot be injected into a
4308 * module configuration function (see {@link angular.Module#config}) but they can be overridden by
4310 * {@link auto.$provide#decorator decorator}.
4312 * @param {string} name The name of the instance.
4313 * @param {*} value The value.
4314 * @returns {Object} registered provider instance
4317 * Here are some examples of creating value services.
4319 * $provide.value('ADMIN_USER', 'admin');
4321 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 });
4323 * $provide.value('halfOf', function(value) {
4332 * @name $provide#constant
4335 * Register a **constant service**, such as a string, a number, an array, an object or a function,
4336 * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be
4337 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot
4338 * be overridden by an Angular {@link auto.$provide#decorator decorator}.
4340 * @param {string} name The name of the constant.
4341 * @param {*} value The constant value.
4342 * @returns {Object} registered instance
4345 * Here a some examples of creating constants:
4347 * $provide.constant('SHARD_HEIGHT', 306);
4349 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']);
4351 * $provide.constant('double', function(value) {
4360 * @name $provide#decorator
4363 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator
4364 * intercepts the creation of a service, allowing it to override or modify the behaviour of the
4365 * service. The object returned by the decorator may be the original service, or a new service
4366 * object which replaces or wraps and delegates to the original service.
4368 * @param {string} name The name of the service to decorate.
4369 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be
4370 * instantiated and should return the decorated service instance. The function is called using
4371 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable.
4372 * Local injection arguments:
4374 * * `$delegate` - The original service instance, which can be monkey patched, configured,
4375 * decorated or delegated to.
4378 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting
4379 * calls to {@link ng.$log#error $log.warn()}.
4381 * $provide.decorator('$log', ['$delegate', function($delegate) {
4382 * $delegate.warn = $delegate.error;
4389 function createInjector(modulesToLoad, strictDi) {
4390 strictDi = (strictDi === true);
4391 var INSTANTIATING = {},
4392 providerSuffix = 'Provider',
4394 loadedModules = new HashMap([], true),
4397 provider: supportObject(provider),
4398 factory: supportObject(factory),
4399 service: supportObject(service),
4400 value: supportObject(value),
4401 constant: supportObject(constant),
4402 decorator: decorator
4405 providerInjector = (providerCache.$injector =
4406 createInternalInjector(providerCache, function(serviceName, caller) {
4407 if (angular.isString(caller)) {
4410 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
4413 instanceInjector = (instanceCache.$injector =
4414 createInternalInjector(instanceCache, function(serviceName, caller) {
4415 var provider = providerInjector.get(serviceName + providerSuffix, caller);
4416 return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
4420 forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });
4422 return instanceInjector;
4424 ////////////////////////////////////
4426 ////////////////////////////////////
4428 function supportObject(delegate) {
4429 return function(key, value) {
4430 if (isObject(key)) {
4431 forEach(key, reverseParams(delegate));
4433 return delegate(key, value);
4438 function provider(name, provider_) {
4439 assertNotHasOwnProperty(name, 'service');
4440 if (isFunction(provider_) || isArray(provider_)) {
4441 provider_ = providerInjector.instantiate(provider_);
4443 if (!provider_.$get) {
4444 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name);
4446 return providerCache[name + providerSuffix] = provider_;
4449 function enforceReturnValue(name, factory) {
4450 return function enforcedReturnValue() {
4451 var result = instanceInjector.invoke(factory, this);
4452 if (isUndefined(result)) {
4453 throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
4459 function factory(name, factoryFn, enforce) {
4460 return provider(name, {
4461 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
4465 function service(name, constructor) {
4466 return factory(name, ['$injector', function($injector) {
4467 return $injector.instantiate(constructor);
4471 function value(name, val) { return factory(name, valueFn(val), false); }
4473 function constant(name, value) {
4474 assertNotHasOwnProperty(name, 'constant');
4475 providerCache[name] = value;
4476 instanceCache[name] = value;
4479 function decorator(serviceName, decorFn) {
4480 var origProvider = providerInjector.get(serviceName + providerSuffix),
4481 orig$get = origProvider.$get;
4483 origProvider.$get = function() {
4484 var origInstance = instanceInjector.invoke(orig$get, origProvider);
4485 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
4489 ////////////////////////////////////
4491 ////////////////////////////////////
4492 function loadModules(modulesToLoad) {
4493 assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array');
4494 var runBlocks = [], moduleFn;
4495 forEach(modulesToLoad, function(module) {
4496 if (loadedModules.get(module)) return;
4497 loadedModules.put(module, true);
4499 function runInvokeQueue(queue) {
4501 for (i = 0, ii = queue.length; i < ii; i++) {
4502 var invokeArgs = queue[i],
4503 provider = providerInjector.get(invokeArgs[0]);
4505 provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
4510 if (isString(module)) {
4511 moduleFn = angularModule(module);
4512 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
4513 runInvokeQueue(moduleFn._invokeQueue);
4514 runInvokeQueue(moduleFn._configBlocks);
4515 } else if (isFunction(module)) {
4516 runBlocks.push(providerInjector.invoke(module));
4517 } else if (isArray(module)) {
4518 runBlocks.push(providerInjector.invoke(module));
4520 assertArgFn(module, 'module');
4523 if (isArray(module)) {
4524 module = module[module.length - 1];
4526 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) {
4527 // Safari & FF's stack traces don't contain error.message content
4528 // unlike those of Chrome and IE
4529 // So if stack doesn't contain message, we create a new string that contains both.
4530 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
4532 e = e.message + '\n' + e.stack;
4534 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}",
4535 module, e.stack || e.message || e);
4541 ////////////////////////////////////
4542 // internal Injector
4543 ////////////////////////////////////
4545 function createInternalInjector(cache, factory) {
4547 function getService(serviceName, caller) {
4548 if (cache.hasOwnProperty(serviceName)) {
4549 if (cache[serviceName] === INSTANTIATING) {
4550 throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
4551 serviceName + ' <- ' + path.join(' <- '));
4553 return cache[serviceName];
4556 path.unshift(serviceName);
4557 cache[serviceName] = INSTANTIATING;
4558 return cache[serviceName] = factory(serviceName, caller);
4560 if (cache[serviceName] === INSTANTIATING) {
4561 delete cache[serviceName];
4570 function invoke(fn, self, locals, serviceName) {
4571 if (typeof locals === 'string') {
4572 serviceName = locals;
4577 $inject = createInjector.$$annotate(fn, strictDi, serviceName),
4581 for (i = 0, length = $inject.length; i < length; i++) {
4583 if (typeof key !== 'string') {
4584 throw $injectorMinErr('itkn',
4585 'Incorrect injection token! Expected service name as string, got {0}', key);
4588 locals && locals.hasOwnProperty(key)
4590 : getService(key, serviceName)
4597 // http://jsperf.com/angularjs-invoke-apply-vs-switch
4599 return fn.apply(self, args);
4602 function instantiate(Type, locals, serviceName) {
4603 // Check if Type is annotated and use just the given function at n-1 as parameter
4604 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
4605 // Object creation: http://jsperf.com/create-constructor/2
4606 var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
4607 var returnedValue = invoke(Type, instance, locals, serviceName);
4609 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
4614 instantiate: instantiate,
4616 annotate: createInjector.$$annotate,
4617 has: function(name) {
4618 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
4624 createInjector.$$annotate = annotate;
4628 * @name $anchorScrollProvider
4631 * Use `$anchorScrollProvider` to disable automatic scrolling whenever
4632 * {@link ng.$location#hash $location.hash()} changes.
4634 function $AnchorScrollProvider() {
4636 var autoScrollingEnabled = true;
4640 * @name $anchorScrollProvider#disableAutoScrolling
4643 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to
4644 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br />
4645 * Use this method to disable automatic scrolling.
4647 * If automatic scrolling is disabled, one must explicitly call
4648 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the
4651 this.disableAutoScrolling = function() {
4652 autoScrollingEnabled = false;
4657 * @name $anchorScroll
4660 * @requires $location
4661 * @requires $rootScope
4664 * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the
4665 * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified
4667 * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#the-indicated-part-of-the-document).
4669 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
4670 * match any anchor whenever it changes. This can be disabled by calling
4671 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}.
4673 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a
4674 * vertical scroll-offset (either fixed or dynamic).
4676 * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of
4677 * {@link ng.$location#hash $location.hash()} will be used.
4679 * @property {(number|function|jqLite)} yOffset
4680 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed
4681 * positioned elements at the top of the page, such as navbars, headers etc.
4683 * `yOffset` can be specified in various ways:
4684 * - **number**: A fixed number of pixels to be used as offset.<br /><br />
4685 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return
4686 * a number representing the offset (in pixels).<br /><br />
4687 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from
4688 * the top of the page to the element's bottom will be used as offset.<br />
4689 * **Note**: The element will be taken into account only as long as its `position` is set to
4690 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust
4691 * their height and/or positioning according to the viewport's size.
4694 * <div class="alert alert-warning">
4695 * In order for `yOffset` to work properly, scrolling should take place on the document's root and
4696 * not some child element.
4700 <example module="anchorScrollExample">
4701 <file name="index.html">
4702 <div id="scrollArea" ng-controller="ScrollController">
4703 <a ng-click="gotoBottom()">Go to bottom</a>
4704 <a id="bottom"></a> You're at the bottom!
4707 <file name="script.js">
4708 angular.module('anchorScrollExample', [])
4709 .controller('ScrollController', ['$scope', '$location', '$anchorScroll',
4710 function ($scope, $location, $anchorScroll) {
4711 $scope.gotoBottom = function() {
4712 // set the location.hash to the id of
4713 // the element you wish to scroll to.
4714 $location.hash('bottom');
4716 // call $anchorScroll()
4721 <file name="style.css">
4735 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value).
4736 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details.
4739 <example module="anchorScrollOffsetExample">
4740 <file name="index.html">
4741 <div class="fixed-header" ng-controller="headerCtrl">
4742 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]">
4746 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]">
4750 <file name="script.js">
4751 angular.module('anchorScrollOffsetExample', [])
4752 .run(['$anchorScroll', function($anchorScroll) {
4753 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels
4755 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope',
4756 function ($anchorScroll, $location, $scope) {
4757 $scope.gotoAnchor = function(x) {
4758 var newHash = 'anchor' + x;
4759 if ($location.hash() !== newHash) {
4760 // set the $location.hash to `newHash` and
4761 // $anchorScroll will automatically scroll to it
4762 $location.hash('anchor' + x);
4764 // call $anchorScroll() explicitly,
4765 // since $location.hash hasn't changed
4772 <file name="style.css">
4778 border: 2px dashed DarkOrchid;
4779 padding: 10px 10px 200px 10px;
4783 background-color: rgba(0, 0, 0, 0.2);
4786 top: 0; left: 0; right: 0;
4790 display: inline-block;
4796 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
4797 var document = $window.document;
4799 // Helper function to get first anchor from a NodeList
4800 // (using `Array#some()` instead of `angular#forEach()` since it's more performant
4801 // and working in all supported browsers.)
4802 function getFirstAnchor(list) {
4804 Array.prototype.some.call(list, function(element) {
4805 if (nodeName_(element) === 'a') {
4813 function getYOffset() {
4815 var offset = scroll.yOffset;
4817 if (isFunction(offset)) {
4819 } else if (isElement(offset)) {
4820 var elem = offset[0];
4821 var style = $window.getComputedStyle(elem);
4822 if (style.position !== 'fixed') {
4825 offset = elem.getBoundingClientRect().bottom;
4827 } else if (!isNumber(offset)) {
4834 function scrollTo(elem) {
4836 elem.scrollIntoView();
4838 var offset = getYOffset();
4841 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly.
4842 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the
4843 // top of the viewport.
4845 // IF the number of pixels from the top of `elem` to the end of the page's content is less
4846 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some
4847 // way down the page.
4849 // This is often the case for elements near the bottom of the page.
4851 // In such cases we do not need to scroll the whole `offset` up, just the difference between
4852 // the top of the element and the offset, which is enough to align the top of `elem` at the
4853 // desired position.
4854 var elemTop = elem.getBoundingClientRect().top;
4855 $window.scrollBy(0, elemTop - offset);
4858 $window.scrollTo(0, 0);
4862 function scroll(hash) {
4863 hash = isString(hash) ? hash : $location.hash();
4866 // empty hash, scroll to the top of the page
4867 if (!hash) scrollTo(null);
4869 // element with given id
4870 else if ((elm = document.getElementById(hash))) scrollTo(elm);
4872 // first anchor with given name :-D
4873 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm);
4875 // no element and hash == 'top', scroll to the top of the page
4876 else if (hash === 'top') scrollTo(null);
4879 // does not scroll when user clicks on anchor link that is currently on
4880 // (no url change, no $location.hash() change), browser native does scroll
4881 if (autoScrollingEnabled) {
4882 $rootScope.$watch(function autoScrollWatch() {return $location.hash();},
4883 function autoScrollWatchAction(newVal, oldVal) {
4884 // skip the initial scroll if $location.hash is empty
4885 if (newVal === oldVal && newVal === '') return;
4887 jqLiteDocumentLoaded(function() {
4888 $rootScope.$evalAsync(scroll);
4897 var $animateMinErr = minErr('$animate');
4898 var ELEMENT_NODE = 1;
4899 var NG_ANIMATE_CLASSNAME = 'ng-animate';
4901 function mergeClasses(a,b) {
4902 if (!a && !b) return '';
4905 if (isArray(a)) a = a.join(' ');
4906 if (isArray(b)) b = b.join(' ');
4910 function extractElementNode(element) {
4911 for (var i = 0; i < element.length; i++) {
4912 var elm = element[i];
4913 if (elm.nodeType === ELEMENT_NODE) {
4919 function splitClasses(classes) {
4920 if (isString(classes)) {
4921 classes = classes.split(' ');
4924 // Use createMap() to prevent class assumptions involving property names in
4926 var obj = createMap();
4927 forEach(classes, function(klass) {
4928 // sometimes the split leaves empty string values
4929 // incase extra spaces were applied to the options
4937 // if any other type of options value besides an Object value is
4938 // passed into the $animate.method() animation then this helper code
4939 // will be run which will ignore it. While this patch is not the
4940 // greatest solution to this, a lot of existing plugins depend on
4941 // $animate to either call the callback (< 1.2) or return a promise
4942 // that can be changed. This helper function ensures that the options
4943 // are wiped clean incase a callback function is provided.
4944 function prepareAnimateOptions(options) {
4945 return isObject(options)
4950 var $$CoreAnimateRunnerProvider = function() {
4951 this.$get = ['$q', '$$rAF', function($q, $$rAF) {
4952 function AnimateRunner() {}
4953 AnimateRunner.all = noop;
4954 AnimateRunner.chain = noop;
4955 AnimateRunner.prototype = {
4961 then: function(pass, fail) {
4962 return $q(function(resolve) {
4966 }).then(pass, fail);
4969 return AnimateRunner;
4973 // this is prefixed with Core since it conflicts with
4974 // the animateQueueProvider defined in ngAnimate/animateQueue.js
4975 var $$CoreAnimateQueueProvider = function() {
4976 var postDigestQueue = new HashMap();
4977 var postDigestElements = [];
4979 this.$get = ['$$AnimateRunner', '$rootScope',
4980 function($$AnimateRunner, $rootScope) {
4987 push: function(element, event, options, domOperation) {
4988 domOperation && domOperation();
4990 options = options || {};
4991 options.from && element.css(options.from);
4992 options.to && element.css(options.to);
4994 if (options.addClass || options.removeClass) {
4995 addRemoveClassesPostDigest(element, options.addClass, options.removeClass);
4998 return new $$AnimateRunner(); // jshint ignore:line
5003 function updateData(data, classes, value) {
5004 var changed = false;
5006 classes = isString(classes) ? classes.split(' ') :
5007 isArray(classes) ? classes : [];
5008 forEach(classes, function(className) {
5011 data[className] = value;
5018 function handleCSSClassChanges() {
5019 forEach(postDigestElements, function(element) {
5020 var data = postDigestQueue.get(element);
5022 var existing = splitClasses(element.attr('class'));
5025 forEach(data, function(status, className) {
5026 var hasClass = !!existing[className];
5027 if (status !== hasClass) {
5029 toAdd += (toAdd.length ? ' ' : '') + className;
5031 toRemove += (toRemove.length ? ' ' : '') + className;
5036 forEach(element, function(elm) {
5037 toAdd && jqLiteAddClass(elm, toAdd);
5038 toRemove && jqLiteRemoveClass(elm, toRemove);
5040 postDigestQueue.remove(element);
5043 postDigestElements.length = 0;
5047 function addRemoveClassesPostDigest(element, add, remove) {
5048 var data = postDigestQueue.get(element) || {};
5050 var classesAdded = updateData(data, add, true);
5051 var classesRemoved = updateData(data, remove, false);
5053 if (classesAdded || classesRemoved) {
5055 postDigestQueue.put(element, data);
5056 postDigestElements.push(element);
5058 if (postDigestElements.length === 1) {
5059 $rootScope.$$postDigest(handleCSSClassChanges);
5068 * @name $animateProvider
5071 * Default implementation of $animate that doesn't perform any animations, instead just
5072 * synchronously performs DOM updates and resolves the returned runner promise.
5074 * In order to enable animations the `ngAnimate` module has to be loaded.
5076 * To see the functional implementation check out `src/ngAnimate/animate.js`.
5078 var $AnimateProvider = ['$provide', function($provide) {
5079 var provider = this;
5081 this.$$registeredAnimations = Object.create(null);
5085 * @name $animateProvider#register
5088 * Registers a new injectable animation factory function. The factory function produces the
5089 * animation object which contains callback functions for each event that is expected to be
5092 * * `eventFn`: `function(element, ... , doneFunction, options)`
5093 * The element to animate, the `doneFunction` and the options fed into the animation. Depending
5094 * on the type of animation additional arguments will be injected into the animation function. The
5095 * list below explains the function signatures for the different animation methods:
5097 * - setClass: function(element, addedClasses, removedClasses, doneFunction, options)
5098 * - addClass: function(element, addedClasses, doneFunction, options)
5099 * - removeClass: function(element, removedClasses, doneFunction, options)
5100 * - enter, leave, move: function(element, doneFunction, options)
5101 * - animate: function(element, fromStyles, toStyles, doneFunction, options)
5103 * Make sure to trigger the `doneFunction` once the animation is fully complete.
5107 * //enter, leave, move signature
5108 * eventFn : function(element, done, options) {
5109 * //code to run the animation
5110 * //once complete, then run done()
5111 * return function endFunction(wasCancelled) {
5112 * //code to cancel the animation
5118 * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to).
5119 * @param {Function} factory The factory function that will be executed to return the animation
5122 this.register = function(name, factory) {
5123 if (name && name.charAt(0) !== '.') {
5124 throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name);
5127 var key = name + '-animation';
5128 provider.$$registeredAnimations[name.substr(1)] = key;
5129 $provide.factory(key, factory);
5134 * @name $animateProvider#classNameFilter
5137 * Sets and/or returns the CSS class regular expression that is checked when performing
5138 * an animation. Upon bootstrap the classNameFilter value is not set at all and will
5139 * therefore enable $animate to attempt to perform an animation on any element that is triggered.
5140 * When setting the `classNameFilter` value, animations will only be performed on elements
5141 * that successfully match the filter expression. This in turn can boost performance
5142 * for low-powered devices as well as applications containing a lot of structural operations.
5143 * @param {RegExp=} expression The className expression which will be checked against all animations
5144 * @return {RegExp} The current CSS className expression value. If null then there is no expression value
5146 this.classNameFilter = function(expression) {
5147 if (arguments.length === 1) {
5148 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null;
5149 if (this.$$classNameFilter) {
5150 var reservedRegex = new RegExp("(\\s+|\\/)" + NG_ANIMATE_CLASSNAME + "(\\s+|\\/)");
5151 if (reservedRegex.test(this.$$classNameFilter.toString())) {
5152 throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME);
5157 return this.$$classNameFilter;
5160 this.$get = ['$$animateQueue', function($$animateQueue) {
5161 function domInsert(element, parentElement, afterElement) {
5162 // if for some reason the previous element was removed
5163 // from the dom sometime before this code runs then let's
5164 // just stick to using the parent element as the anchor
5166 var afterNode = extractElementNode(afterElement);
5167 if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) {
5168 afterElement = null;
5171 afterElement ? afterElement.after(element) : parentElement.prepend(element);
5177 * @description The $animate service exposes a series of DOM utility methods that provide support
5178 * for animation hooks. The default behavior is the application of DOM operations, however,
5179 * when an animation is detected (and animations are enabled), $animate will do the heavy lifting
5180 * to ensure that animation runs with the triggered DOM operation.
5182 * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't
5183 * included and only when it is active then the animation hooks that `$animate` triggers will be
5184 * functional. Once active then all structural `ng-` directives will trigger animations as they perform
5185 * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`,
5186 * `ngShow`, `ngHide` and `ngMessages` also provide support for animations.
5188 * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives.
5190 * To learn more about enabling animation support, click here to visit the
5191 * {@link ngAnimate ngAnimate module page}.
5194 // we don't call it directly since non-existant arguments may
5195 // be interpreted as null within the sub enabled function
5202 * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...)
5203 * has fired on the given element or among any of its children. Once the listener is fired, the provided callback
5204 * is fired with the following params:
5207 * $animate.on('enter', container,
5208 * function callback(element, phase) {
5209 * // cool we detected an enter animation within the container
5214 * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...)
5215 * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself
5216 * as well as among its children
5217 * @param {Function} callback the callback function that will be fired when the listener is triggered
5219 * The arguments present in the callback function are:
5220 * * `element` - The captured DOM element that the animation was fired on.
5221 * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends).
5223 on: $$animateQueue.on,
5228 * @name $animate#off
5230 * @description Deregisters an event listener based on the event which has been associated with the provided element. This method
5231 * can be used in three different ways depending on the arguments:
5234 * // remove all the animation event listeners listening for `enter`
5235 * $animate.off('enter');
5237 * // remove all the animation event listeners listening for `enter` on the given element and its children
5238 * $animate.off('enter', container);
5240 * // remove the event listener function provided by `listenerFn` that is set
5241 * // to listen for `enter` on the given `element` as well as its children
5242 * $animate.off('enter', container, callback);
5245 * @param {string} event the animation event (e.g. enter, leave, move, addClass, removeClass, etc...)
5246 * @param {DOMElement=} container the container element the event listener was placed on
5247 * @param {Function=} callback the callback function that was registered as the listener
5249 off: $$animateQueue.off,
5253 * @name $animate#pin
5255 * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists
5256 * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the
5257 * element despite being outside the realm of the application or within another application. Say for example if the application
5258 * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated
5259 * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind
5260 * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association.
5262 * Note that this feature is only active when the `ngAnimate` module is used.
5264 * @param {DOMElement} element the external element that will be pinned
5265 * @param {DOMElement} parentElement the host parent element that will be associated with the external element
5267 pin: $$animateQueue.pin,
5272 * @name $animate#enabled
5274 * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This
5275 * function can be called in four ways:
5278 * // returns true or false
5279 * $animate.enabled();
5281 * // changes the enabled state for all animations
5282 * $animate.enabled(false);
5283 * $animate.enabled(true);
5285 * // returns true or false if animations are enabled for an element
5286 * $animate.enabled(element);
5288 * // changes the enabled state for an element and its children
5289 * $animate.enabled(element, true);
5290 * $animate.enabled(element, false);
5293 * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state
5294 * @param {boolean=} enabled whether or not the animations will be enabled for the element
5296 * @return {boolean} whether or not animations are enabled
5298 enabled: $$animateQueue.enabled,
5302 * @name $animate#cancel
5304 * @description Cancels the provided animation.
5306 * @param {Promise} animationPromise The animation promise that is returned when an animation is started.
5308 cancel: function(runner) {
5309 runner.end && runner.end();
5315 * @name $animate#enter
5317 * @description Inserts the element into the DOM either after the `after` element (if provided) or
5318 * as the first child within the `parent` element and then triggers an animation.
5319 * A promise is returned that will be resolved during the next digest once the animation
5322 * @param {DOMElement} element the element which will be inserted into the DOM
5323 * @param {DOMElement} parent the parent element which will append the element as
5324 * a child (so long as the after element is not present)
5325 * @param {DOMElement=} after the sibling element after which the element will be appended
5326 * @param {object=} options an optional collection of options/styles that will be applied to the element
5328 * @return {Promise} the animation callback promise
5330 enter: function(element, parent, after, options) {
5331 parent = parent && jqLite(parent);
5332 after = after && jqLite(after);
5333 parent = parent || after.parent();
5334 domInsert(element, parent, after);
5335 return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options));
5341 * @name $animate#move
5343 * @description Inserts (moves) the element into its new position in the DOM either after
5344 * the `after` element (if provided) or as the first child within the `parent` element
5345 * and then triggers an animation. A promise is returned that will be resolved
5346 * during the next digest once the animation has completed.
5348 * @param {DOMElement} element the element which will be moved into the new DOM position
5349 * @param {DOMElement} parent the parent element which will append the element as
5350 * a child (so long as the after element is not present)
5351 * @param {DOMElement=} after the sibling element after which the element will be appended
5352 * @param {object=} options an optional collection of options/styles that will be applied to the element
5354 * @return {Promise} the animation callback promise
5356 move: function(element, parent, after, options) {
5357 parent = parent && jqLite(parent);
5358 after = after && jqLite(after);
5359 parent = parent || after.parent();
5360 domInsert(element, parent, after);
5361 return $$animateQueue.push(element, 'move', prepareAnimateOptions(options));
5366 * @name $animate#leave
5368 * @description Triggers an animation and then removes the element from the DOM.
5369 * When the function is called a promise is returned that will be resolved during the next
5370 * digest once the animation has completed.
5372 * @param {DOMElement} element the element which will be removed from the DOM
5373 * @param {object=} options an optional collection of options/styles that will be applied to the element
5375 * @return {Promise} the animation callback promise
5377 leave: function(element, options) {
5378 return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() {
5385 * @name $animate#addClass
5388 * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon
5389 * execution, the addClass operation will only be handled after the next digest and it will not trigger an
5390 * animation if element already contains the CSS class or if the class is removed at a later step.
5391 * Note that class-based animations are treated differently compared to structural animations
5392 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5393 * depending if CSS or JavaScript animations are used.
5395 * @param {DOMElement} element the element which the CSS classes will be applied to
5396 * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces)
5397 * @param {object=} options an optional collection of options/styles that will be applied to the element
5399 * @return {Promise} the animation callback promise
5401 addClass: function(element, className, options) {
5402 options = prepareAnimateOptions(options);
5403 options.addClass = mergeClasses(options.addclass, className);
5404 return $$animateQueue.push(element, 'addClass', options);
5409 * @name $animate#removeClass
5412 * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon
5413 * execution, the removeClass operation will only be handled after the next digest and it will not trigger an
5414 * animation if element does not contain the CSS class or if the class is added at a later step.
5415 * Note that class-based animations are treated differently compared to structural animations
5416 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5417 * depending if CSS or JavaScript animations are used.
5419 * @param {DOMElement} element the element which the CSS classes will be applied to
5420 * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces)
5421 * @param {object=} options an optional collection of options/styles that will be applied to the element
5423 * @return {Promise} the animation callback promise
5425 removeClass: function(element, className, options) {
5426 options = prepareAnimateOptions(options);
5427 options.removeClass = mergeClasses(options.removeClass, className);
5428 return $$animateQueue.push(element, 'removeClass', options);
5433 * @name $animate#setClass
5436 * @description Performs both the addition and removal of a CSS classes on an element and (during the process)
5437 * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and
5438 * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has
5439 * passed. Note that class-based animations are treated differently compared to structural animations
5440 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5441 * depending if CSS or JavaScript animations are used.
5443 * @param {DOMElement} element the element which the CSS classes will be applied to
5444 * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces)
5445 * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces)
5446 * @param {object=} options an optional collection of options/styles that will be applied to the element
5448 * @return {Promise} the animation callback promise
5450 setClass: function(element, add, remove, options) {
5451 options = prepareAnimateOptions(options);
5452 options.addClass = mergeClasses(options.addClass, add);
5453 options.removeClass = mergeClasses(options.removeClass, remove);
5454 return $$animateQueue.push(element, 'setClass', options);
5459 * @name $animate#animate
5462 * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element.
5463 * If any detected CSS transition, keyframe or JavaScript matches the provided className value then the animation will take
5464 * on the provided styles. For example, if a transition animation is set for the given className then the provided from and
5465 * to styles will be applied alongside the given transition. If a JavaScript animation is detected then the provided styles
5466 * will be given in as function paramters into the `animate` method (or as apart of the `options` parameter).
5468 * @param {DOMElement} element the element which the CSS styles will be applied to
5469 * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation.
5470 * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation.
5471 * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If
5472 * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element.
5473 * (Note that if no animation is detected then this value will not be appplied to the element.)
5474 * @param {object=} options an optional collection of options/styles that will be applied to the element
5476 * @return {Promise} the animation callback promise
5478 animate: function(element, from, to, className, options) {
5479 options = prepareAnimateOptions(options);
5480 options.from = options.from ? extend(options.from, from) : from;
5481 options.to = options.to ? extend(options.to, to) : to;
5483 className = className || 'ng-inline-animate';
5484 options.tempClasses = mergeClasses(options.tempClasses, className);
5485 return $$animateQueue.push(element, 'animate', options);
5497 * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included,
5498 * then the `$animateCss` service will actually perform animations.
5500 * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}.
5502 var $CoreAnimateCssProvider = function() {
5503 this.$get = ['$$rAF', '$q', function($$rAF, $q) {
5505 var RAFPromise = function() {};
5506 RAFPromise.prototype = {
5507 done: function(cancel) {
5508 this.defer && this.defer[cancel === true ? 'reject' : 'resolve']();
5513 cancel: function() {
5516 getPromise: function() {
5518 this.defer = $q.defer();
5520 return this.defer.promise;
5522 then: function(f1,f2) {
5523 return this.getPromise().then(f1,f2);
5525 'catch': function(f1) {
5526 return this.getPromise()['catch'](f1);
5528 'finally': function(f1) {
5529 return this.getPromise()['finally'](f1);
5533 return function(element, options) {
5534 // there is no point in applying the styles since
5535 // there is no animation that goes on at all in
5536 // this version of $animateCss.
5537 if (options.cleanupStyles) {
5538 options.from = options.to = null;
5542 element.css(options.from);
5543 options.from = null;
5546 var closed, runner = new RAFPromise();
5564 if (options.addClass) {
5565 element.addClass(options.addClass);
5566 options.addClass = null;
5568 if (options.removeClass) {
5569 element.removeClass(options.removeClass);
5570 options.removeClass = null;
5573 element.css(options.to);
5581 /* global stripHash: true */
5584 * ! This is a private undocumented service !
5589 * This object has two goals:
5591 * - hide all the global state in the browser caused by the window object
5592 * - abstract away all the browser specific features and inconsistencies
5594 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser`
5595 * service, which can be used for convenient testing of the application without the interaction with
5596 * the real browser apis.
5599 * @param {object} window The global window object.
5600 * @param {object} document jQuery wrapped document.
5601 * @param {object} $log window.console or an object with the same interface.
5602 * @param {object} $sniffer $sniffer service
5604 function Browser(window, document, $log, $sniffer) {
5606 rawDocument = document[0],
5607 location = window.location,
5608 history = window.history,
5609 setTimeout = window.setTimeout,
5610 clearTimeout = window.clearTimeout,
5611 pendingDeferIds = {};
5613 self.isMock = false;
5615 var outstandingRequestCount = 0;
5616 var outstandingRequestCallbacks = [];
5618 // TODO(vojta): remove this temporary api
5619 self.$$completeOutstandingRequest = completeOutstandingRequest;
5620 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
5623 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
5624 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
5626 function completeOutstandingRequest(fn) {
5628 fn.apply(null, sliceArgs(arguments, 1));
5630 outstandingRequestCount--;
5631 if (outstandingRequestCount === 0) {
5632 while (outstandingRequestCallbacks.length) {
5634 outstandingRequestCallbacks.pop()();
5643 function getHash(url) {
5644 var index = url.indexOf('#');
5645 return index === -1 ? '' : url.substr(index);
5650 * Note: this method is used only by scenario runner
5651 * TODO(vojta): prefix this method with $$ ?
5652 * @param {function()} callback Function that will be called when no outstanding request
5654 self.notifyWhenNoOutstandingRequests = function(callback) {
5655 if (outstandingRequestCount === 0) {
5658 outstandingRequestCallbacks.push(callback);
5662 //////////////////////////////////////////////////////////////
5664 //////////////////////////////////////////////////////////////
5666 var cachedState, lastHistoryState,
5667 lastBrowserUrl = location.href,
5668 baseElement = document.find('base'),
5669 pendingLocation = null;
5672 lastHistoryState = cachedState;
5675 * @name $browser#url
5679 * Without any argument, this method just returns current value of location.href.
5682 * With at least one argument, this method sets url to new value.
5683 * If html5 history api supported, pushState/replaceState is used, otherwise
5684 * location.href/location.replace is used.
5685 * Returns its own instance to allow chaining
5687 * NOTE: this api is intended for use only by the $location service. Please use the
5688 * {@link ng.$location $location service} to change url.
5690 * @param {string} url New url (when used as setter)
5691 * @param {boolean=} replace Should new url replace current history record?
5692 * @param {object=} state object to use with pushState/replaceState
5694 self.url = function(url, replace, state) {
5695 // In modern browsers `history.state` is `null` by default; treating it separately
5696 // from `undefined` would cause `$browser.url('/foo')` to change `history.state`
5697 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here.
5698 if (isUndefined(state)) {
5702 // Android Browser BFCache causes location, history reference to become stale.
5703 if (location !== window.location) location = window.location;
5704 if (history !== window.history) history = window.history;
5708 var sameState = lastHistoryState === state;
5710 // Don't change anything if previous and current URLs and states match. This also prevents
5711 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
5712 // See https://github.com/angular/angular.js/commit/ffb2701
5713 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) {
5716 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
5717 lastBrowserUrl = url;
5718 lastHistoryState = state;
5719 // Don't use history API if only the hash changed
5720 // due to a bug in IE10/IE11 which leads
5721 // to not firing a `hashchange` nor `popstate` event
5722 // in some cases (see #9143).
5723 if ($sniffer.history && (!sameBase || !sameState)) {
5724 history[replace ? 'replaceState' : 'pushState'](state, '', url);
5726 // Do the assignment again so that those two variables are referentially identical.
5727 lastHistoryState = cachedState;
5729 if (!sameBase || pendingLocation) {
5730 pendingLocation = url;
5733 location.replace(url);
5734 } else if (!sameBase) {
5735 location.href = url;
5737 location.hash = getHash(url);
5739 if (location.href !== url) {
5740 pendingLocation = url;
5746 // - pendingLocation is needed as browsers don't allow to read out
5747 // the new location.href if a reload happened or if there is a bug like in iOS 9 (see
5748 // https://openradar.appspot.com/22186109).
5749 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
5750 return pendingLocation || location.href.replace(/%27/g,"'");
5755 * @name $browser#state
5758 * This method is a getter.
5760 * Return history.state or null if history.state is undefined.
5762 * @returns {object} state
5764 self.state = function() {
5768 var urlChangeListeners = [],
5769 urlChangeInit = false;
5771 function cacheStateAndFireUrlChange() {
5772 pendingLocation = null;
5777 function getCurrentState() {
5779 return history.state;
5781 // MSIE can reportedly throw when there is no state (UNCONFIRMED).
5785 // This variable should be used *only* inside the cacheState function.
5786 var lastCachedState = null;
5787 function cacheState() {
5788 // This should be the only place in $browser where `history.state` is read.
5789 cachedState = getCurrentState();
5790 cachedState = isUndefined(cachedState) ? null : cachedState;
5792 // Prevent callbacks fo fire twice if both hashchange & popstate were fired.
5793 if (equals(cachedState, lastCachedState)) {
5794 cachedState = lastCachedState;
5796 lastCachedState = cachedState;
5799 function fireUrlChange() {
5800 if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) {
5804 lastBrowserUrl = self.url();
5805 lastHistoryState = cachedState;
5806 forEach(urlChangeListeners, function(listener) {
5807 listener(self.url(), cachedState);
5812 * @name $browser#onUrlChange
5815 * Register callback function that will be called, when url changes.
5817 * It's only called when the url is changed from outside of angular:
5818 * - user types different url into address bar
5819 * - user clicks on history (forward/back) button
5820 * - user clicks on a link
5822 * It's not called when url is changed by $browser.url() method
5824 * The listener gets called with new url as parameter.
5826 * NOTE: this api is intended for use only by the $location service. Please use the
5827 * {@link ng.$location $location service} to monitor url changes in angular apps.
5829 * @param {function(string)} listener Listener function to be called when url changes.
5830 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous.
5832 self.onUrlChange = function(callback) {
5833 // TODO(vojta): refactor to use node's syntax for events
5834 if (!urlChangeInit) {
5835 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera)
5836 // don't fire popstate when user change the address bar and don't fire hashchange when url
5837 // changed by push/replaceState
5839 // html5 history api - popstate event
5840 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange);
5842 jqLite(window).on('hashchange', cacheStateAndFireUrlChange);
5844 urlChangeInit = true;
5847 urlChangeListeners.push(callback);
5853 * Remove popstate and hashchange handler from window.
5855 * NOTE: this api is intended for use only by $rootScope.
5857 self.$$applicationDestroyed = function() {
5858 jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange);
5862 * Checks whether the url has changed outside of Angular.
5863 * Needs to be exported to be able to check for changes that have been done in sync,
5864 * as hashchange/popstate events fire in async.
5866 self.$$checkUrlChange = fireUrlChange;
5868 //////////////////////////////////////////////////////////////
5870 //////////////////////////////////////////////////////////////
5873 * @name $browser#baseHref
5876 * Returns current <base href>
5877 * (always relative - without domain)
5879 * @returns {string} The current base href
5881 self.baseHref = function() {
5882 var href = baseElement.attr('href');
5883 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : '';
5887 * @name $browser#defer
5888 * @param {function()} fn A function, who's execution should be deferred.
5889 * @param {number=} [delay=0] of milliseconds to defer the function execution.
5890 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
5893 * Executes a fn asynchronously via `setTimeout(fn, delay)`.
5895 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
5896 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed
5897 * via `$browser.defer.flush()`.
5900 self.defer = function(fn, delay) {
5902 outstandingRequestCount++;
5903 timeoutId = setTimeout(function() {
5904 delete pendingDeferIds[timeoutId];
5905 completeOutstandingRequest(fn);
5907 pendingDeferIds[timeoutId] = true;
5913 * @name $browser#defer.cancel
5916 * Cancels a deferred task identified with `deferId`.
5918 * @param {*} deferId Token returned by the `$browser.defer` function.
5919 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
5922 self.defer.cancel = function(deferId) {
5923 if (pendingDeferIds[deferId]) {
5924 delete pendingDeferIds[deferId];
5925 clearTimeout(deferId);
5926 completeOutstandingRequest(noop);
5934 function $BrowserProvider() {
5935 this.$get = ['$window', '$log', '$sniffer', '$document',
5936 function($window, $log, $sniffer, $document) {
5937 return new Browser($window, $document, $log, $sniffer);
5943 * @name $cacheFactory
5946 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to
5951 * var cache = $cacheFactory('cacheId');
5952 * expect($cacheFactory.get('cacheId')).toBe(cache);
5953 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined();
5955 * cache.put("key", "value");
5956 * cache.put("another key", "another value");
5958 * // We've specified no options on creation
5959 * expect(cache.info()).toEqual({id: 'cacheId', size: 2});
5964 * @param {string} cacheId Name or id of the newly created cache.
5965 * @param {object=} options Options object that specifies the cache behavior. Properties:
5967 * - `{number=}` `capacity` — turns the cache into LRU cache.
5969 * @returns {object} Newly created cache object with the following set of methods:
5971 * - `{object}` `info()` — Returns id, size, and options of cache.
5972 * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns
5974 * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss.
5975 * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache.
5976 * - `{void}` `removeAll()` — Removes all cached values.
5977 * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory.
5980 <example module="cacheExampleApp">
5981 <file name="index.html">
5982 <div ng-controller="CacheController">
5983 <input ng-model="newCacheKey" placeholder="Key">
5984 <input ng-model="newCacheValue" placeholder="Value">
5985 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button>
5987 <p ng-if="keys.length">Cached Values</p>
5988 <div ng-repeat="key in keys">
5989 <span ng-bind="key"></span>
5991 <b ng-bind="cache.get(key)"></b>
5995 <div ng-repeat="(key, value) in cache.info()">
5996 <span ng-bind="key"></span>
5998 <b ng-bind="value"></b>
6002 <file name="script.js">
6003 angular.module('cacheExampleApp', []).
6004 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) {
6006 $scope.cache = $cacheFactory('cacheId');
6007 $scope.put = function(key, value) {
6008 if (angular.isUndefined($scope.cache.get(key))) {
6009 $scope.keys.push(key);
6011 $scope.cache.put(key, angular.isUndefined(value) ? null : value);
6015 <file name="style.css">
6022 function $CacheFactoryProvider() {
6024 this.$get = function() {
6027 function cacheFactory(cacheId, options) {
6028 if (cacheId in caches) {
6029 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId);
6033 stats = extend({}, options, {id: cacheId}),
6035 capacity = (options && options.capacity) || Number.MAX_VALUE,
6036 lruHash = createMap(),
6042 * @name $cacheFactory.Cache
6045 * A cache object used to store and retrieve data, primarily used by
6046 * {@link $http $http} and the {@link ng.directive:script script} directive to cache
6047 * templates and other data.
6050 * angular.module('superCache')
6051 * .factory('superCache', ['$cacheFactory', function($cacheFactory) {
6052 * return $cacheFactory('super-cache');
6059 * it('should behave like a cache', inject(function(superCache) {
6060 * superCache.put('key', 'value');
6061 * superCache.put('another key', 'another value');
6063 * expect(superCache.info()).toEqual({
6064 * id: 'super-cache',
6068 * superCache.remove('another key');
6069 * expect(superCache.get('another key')).toBeUndefined();
6071 * superCache.removeAll();
6072 * expect(superCache.info()).toEqual({
6073 * id: 'super-cache',
6079 return caches[cacheId] = {
6083 * @name $cacheFactory.Cache#put
6087 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be
6088 * retrieved later, and incrementing the size of the cache if the key was not already
6089 * present in the cache. If behaving like an LRU cache, it will also remove stale
6090 * entries from the set.
6092 * It will not insert undefined values into the cache.
6094 * @param {string} key the key under which the cached data is stored.
6095 * @param {*} value the value to store alongside the key. If it is undefined, the key
6096 * will not be stored.
6097 * @returns {*} the value stored.
6099 put: function(key, value) {
6100 if (isUndefined(value)) return;
6101 if (capacity < Number.MAX_VALUE) {
6102 var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
6107 if (!(key in data)) size++;
6110 if (size > capacity) {
6111 this.remove(staleEnd.key);
6119 * @name $cacheFactory.Cache#get
6123 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object.
6125 * @param {string} key the key of the data to be retrieved
6126 * @returns {*} the value stored.
6128 get: function(key) {
6129 if (capacity < Number.MAX_VALUE) {
6130 var lruEntry = lruHash[key];
6132 if (!lruEntry) return;
6143 * @name $cacheFactory.Cache#remove
6147 * Removes an entry from the {@link $cacheFactory.Cache Cache} object.
6149 * @param {string} key the key of the entry to be removed
6151 remove: function(key) {
6152 if (capacity < Number.MAX_VALUE) {
6153 var lruEntry = lruHash[key];
6155 if (!lruEntry) return;
6157 if (lruEntry == freshEnd) freshEnd = lruEntry.p;
6158 if (lruEntry == staleEnd) staleEnd = lruEntry.n;
6159 link(lruEntry.n,lruEntry.p);
6161 delete lruHash[key];
6164 if (!(key in data)) return;
6173 * @name $cacheFactory.Cache#removeAll
6177 * Clears the cache object of any entries.
6179 removeAll: function() {
6182 lruHash = createMap();
6183 freshEnd = staleEnd = null;
6189 * @name $cacheFactory.Cache#destroy
6193 * Destroys the {@link $cacheFactory.Cache Cache} object entirely,
6194 * removing it from the {@link $cacheFactory $cacheFactory} set.
6196 destroy: function() {
6200 delete caches[cacheId];
6206 * @name $cacheFactory.Cache#info
6210 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}.
6212 * @returns {object} an object with the following properties:
6214 * <li>**id**: the id of the cache instance</li>
6215 * <li>**size**: the number of entries kept in the cache instance</li>
6216 * <li>**...**: any additional properties from the options object when creating the
6221 return extend({}, stats, {size: size});
6227 * makes the `entry` the freshEnd of the LRU linked list
6229 function refresh(entry) {
6230 if (entry != freshEnd) {
6233 } else if (staleEnd == entry) {
6237 link(entry.n, entry.p);
6238 link(entry, freshEnd);
6246 * bidirectionally links two entries of the LRU linked list
6248 function link(nextEntry, prevEntry) {
6249 if (nextEntry != prevEntry) {
6250 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify
6251 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify
6259 * @name $cacheFactory#info
6262 * Get information about all the caches that have been created
6264 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info`
6266 cacheFactory.info = function() {
6268 forEach(caches, function(cache, cacheId) {
6269 info[cacheId] = cache.info();
6277 * @name $cacheFactory#get
6280 * Get access to a cache object by the `cacheId` used when it was created.
6282 * @param {string} cacheId Name or id of a cache to access.
6283 * @returns {object} Cache object identified by the cacheId or undefined if no such cache.
6285 cacheFactory.get = function(cacheId) {
6286 return caches[cacheId];
6290 return cacheFactory;
6296 * @name $templateCache
6299 * The first time a template is used, it is loaded in the template cache for quick retrieval. You
6300 * can load templates directly into the cache in a `script` tag, or by consuming the
6301 * `$templateCache` service directly.
6303 * Adding via the `script` tag:
6306 * <script type="text/ng-template" id="templateId.html">
6307 * <p>This is the content of the template</p>
6311 * **Note:** the `script` tag containing the template does not need to be included in the `head` of
6312 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE,
6313 * element with ng-app attribute), otherwise the template will be ignored.
6315 * Adding via the `$templateCache` service:
6318 * var myApp = angular.module('myApp', []);
6319 * myApp.run(function($templateCache) {
6320 * $templateCache.put('templateId.html', 'This is the content of the template');
6324 * To retrieve the template later, simply use it in your HTML:
6326 * <div ng-include=" 'templateId.html' "></div>
6329 * or get it via Javascript:
6331 * $templateCache.get('templateId.html')
6334 * See {@link ng.$cacheFactory $cacheFactory}.
6337 function $TemplateCacheProvider() {
6338 this.$get = ['$cacheFactory', function($cacheFactory) {
6339 return $cacheFactory('templates');
6343 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
6344 * Any commits to this file should be reviewed with security in mind. *
6345 * Changes to this file can potentially create security vulnerabilities. *
6346 * An approval from 2 Core members with history of modifying *
6347 * this file is required. *
6349 * Does the change somehow allow for arbitrary javascript to be executed? *
6350 * Or allows for someone to change the prototype of built-in objects? *
6351 * Or gives undesired access to variables likes document or window? *
6352 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
6354 /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
6356 * DOM-related variables:
6358 * - "node" - DOM Node
6359 * - "element" - DOM Element or Node
6360 * - "$node" or "$element" - jqLite-wrapped node or element
6363 * Compiler related stuff:
6365 * - "linkFn" - linking fn of a single directive
6366 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node
6367 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node
6368 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList)
6378 * Compiles an HTML string or DOM into a template and produces a template function, which
6379 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together.
6381 * The compilation is a process of walking the DOM tree and matching DOM elements to
6382 * {@link ng.$compileProvider#directive directives}.
6384 * <div class="alert alert-warning">
6385 * **Note:** This document is an in-depth reference of all directive options.
6386 * For a gentle introduction to directives with examples of common use cases,
6387 * see the {@link guide/directive directive guide}.
6390 * ## Comprehensive Directive API
6392 * There are many different options for a directive.
6394 * The difference resides in the return value of the factory function.
6395 * You can either return a "Directive Definition Object" (see below) that defines the directive properties,
6396 * or just the `postLink` function (all other properties will have the default values).
6398 * <div class="alert alert-success">
6399 * **Best Practice:** It's recommended to use the "directive definition object" form.
6402 * Here's an example directive declared with a Directive Definition Object:
6405 * var myModule = angular.module(...);
6407 * myModule.directive('directiveName', function factory(injectables) {
6408 * var directiveDefinitionObject = {
6410 * template: '<div></div>', // or // function(tElement, tAttrs) { ... },
6412 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... },
6413 * transclude: false,
6415 * templateNamespace: 'html',
6417 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
6418 * controllerAs: 'stringIdentifier',
6419 * bindToController: false,
6420 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
6421 * compile: function compile(tElement, tAttrs, transclude) {
6423 * pre: function preLink(scope, iElement, iAttrs, controller) { ... },
6424 * post: function postLink(scope, iElement, iAttrs, controller) { ... }
6427 * // return function postLink( ... ) { ... }
6431 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... },
6432 * // post: function postLink(scope, iElement, iAttrs, controller) { ... }
6435 * // link: function postLink( ... ) { ... }
6437 * return directiveDefinitionObject;
6441 * <div class="alert alert-warning">
6442 * **Note:** Any unspecified options will use the default value. You can see the default values below.
6445 * Therefore the above can be simplified as:
6448 * var myModule = angular.module(...);
6450 * myModule.directive('directiveName', function factory(injectables) {
6451 * var directiveDefinitionObject = {
6452 * link: function postLink(scope, iElement, iAttrs) { ... }
6454 * return directiveDefinitionObject;
6456 * // return function postLink(scope, iElement, iAttrs) { ... }
6462 * ### Directive Definition Object
6464 * The directive definition object provides instructions to the {@link ng.$compile
6465 * compiler}. The attributes are:
6467 * #### `multiElement`
6468 * When this property is set to true, the HTML compiler will collect DOM nodes between
6469 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
6470 * together as the directive elements. It is recommended that this feature be used on directives
6471 * which are not strictly behavioural (such as {@link ngClick}), and which
6472 * do not manipulate or replace child nodes (such as {@link ngInclude}).
6475 * When there are multiple directives defined on a single DOM element, sometimes it
6476 * is necessary to specify the order in which the directives are applied. The `priority` is used
6477 * to sort the directives before their `compile` functions get called. Priority is defined as a
6478 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions
6479 * are also run in priority order, but post-link functions are run in reverse order. The order
6480 * of directives with the same priority is undefined. The default priority is `0`.
6483 * If set to true then the current `priority` will be the last set of directives
6484 * which will execute (any directives at the current priority will still execute
6485 * as the order of execution on same `priority` is undefined). Note that expressions
6486 * and other directives used in the directive's template will also be excluded from execution.
6489 * The scope property can be `true`, an object or a falsy value:
6491 * * **falsy:** No scope will be created for the directive. The directive will use its parent's scope.
6493 * * **`true`:** A new child scope that prototypically inherits from its parent will be created for
6494 * the directive's element. If multiple directives on the same element request a new scope,
6495 * only one new scope is created. The new scope rule does not apply for the root of the template
6496 * since the root of the template always gets a new scope.
6498 * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's element. The
6499 * 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent
6500 * scope. This is useful when creating reusable components, which should not accidentally read or modify
6501 * data in the parent scope.
6503 * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the
6504 * directive's element. These local properties are useful for aliasing values for templates. The keys in
6505 * the object hash map to the name of the property on the isolate scope; the values define how the property
6506 * is bound to the parent scope, via matching attributes on the directive's element:
6508 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
6509 * always a string since DOM attributes are strings. If no `attr` name is specified then the
6510 * attribute name is assumed to be the same as the local name.
6511 * Given `<widget my-attr="hello {{name}}">` and widget definition
6512 * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect
6513 * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the
6514 * `localName` property on the widget scope. The `name` is read from the parent scope (not
6517 * * `=` or `=attr` - set up bi-directional binding between a local scope property and the
6518 * parent scope property of name defined via the value of the `attr` attribute. If no `attr`
6519 * name is specified then the attribute name is assumed to be the same as the local name.
6520 * Given `<widget my-attr="parentModel">` and widget definition of
6521 * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the
6522 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
6523 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent
6524 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You
6525 * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If
6526 * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use
6527 * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional).
6529 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope.
6530 * If no `attr` name is specified then the attribute name is assumed to be the same as the
6531 * local name. Given `<widget my-attr="count = count + value">` and widget definition of
6532 * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to
6533 * a function wrapper for the `count = count + value` expression. Often it's desirable to
6534 * pass data from the isolated scope via an expression to the parent scope, this can be
6535 * done by passing a map of local variable names and values into the expression wrapper fn.
6536 * For example, if the expression is `increment(amount)` then we can specify the amount value
6537 * by calling the `localFn` as `localFn({amount: 22})`.
6539 * In general it's possible to apply more than one directive to one element, but there might be limitations
6540 * depending on the type of scope required by the directives. The following points will help explain these limitations.
6541 * For simplicity only two directives are taken into account, but it is also applicable for several directives:
6543 * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope
6544 * * **child scope** + **no scope** => Both directives will share one single child scope
6545 * * **child scope** + **child scope** => Both directives will share one single child scope
6546 * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use
6547 * its parent's scope
6548 * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot
6549 * be applied to the same element.
6550 * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives
6551 * cannot be applied to the same element.
6554 * #### `bindToController`
6555 * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will
6556 * allow a component to have its properties bound to the controller, rather than to scope. When the controller
6557 * is instantiated, the initial values of the isolate scope bindings are already available.
6560 * Controller constructor function. The controller is instantiated before the
6561 * pre-linking phase and can be accessed by other directives (see
6562 * `require` attribute). This allows the directives to communicate with each other and augment
6563 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals:
6565 * * `$scope` - Current scope associated with the element
6566 * * `$element` - Current element
6567 * * `$attrs` - Current attributes object for the element
6568 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope:
6569 * `function([scope], cloneLinkingFn, futureParentElement)`.
6570 * * `scope`: optional argument to override the scope.
6571 * * `cloneLinkingFn`: optional argument to create clones of the original transcluded content.
6572 * * `futureParentElement`:
6573 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements.
6574 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`.
6575 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements)
6576 * and when the `cloneLinkinFn` is passed,
6577 * as those elements need to created and cloned in a special way when they are defined outside their
6578 * usual containers (e.g. like `<svg>`).
6579 * * See also the `directive.templateNamespace` property.
6583 * Require another directive and inject its controller as the fourth argument to the linking function. The
6584 * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the
6585 * injected argument will be an array in corresponding order. If no such directive can be
6586 * found, or if the directive does not have a controller, then an error is raised (unless no link function
6587 * is specified, in which case error checking is skipped). The name can be prefixed with:
6589 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
6590 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
6591 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found.
6592 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found.
6593 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass
6594 * `null` to the `link` fn if not found.
6595 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass
6596 * `null` to the `link` fn if not found.
6599 * #### `controllerAs`
6600 * Identifier name for a reference to the controller in the directive's scope.
6601 * This allows the controller to be referenced from the directive template. This is especially
6602 * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible
6603 * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the
6604 * `controllerAs` reference might overwrite a property that already exists on the parent scope.
6608 * String of subset of `EACM` which restricts the directive to a specific directive
6609 * declaration style. If omitted, the defaults (elements and attributes) are used.
6611 * * `E` - Element name (default): `<my-directive></my-directive>`
6612 * * `A` - Attribute (default): `<div my-directive="exp"></div>`
6613 * * `C` - Class: `<div class="my-directive: exp;"></div>`
6614 * * `M` - Comment: `<!-- directive: my-directive exp -->`
6617 * #### `templateNamespace`
6618 * String representing the document type used by the markup in the template.
6619 * AngularJS needs this information as those elements need to be created and cloned
6620 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`.
6622 * * `html` - All root nodes in the template are HTML. Root nodes may also be
6623 * top-level elements such as `<svg>` or `<math>`.
6624 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`).
6625 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`).
6627 * If no `templateNamespace` is specified, then the namespace is considered to be `html`.
6630 * HTML markup that may:
6631 * * Replace the contents of the directive's element (default).
6632 * * Replace the directive's element itself (if `replace` is true - DEPRECATED).
6633 * * Wrap the contents of the directive's element (if `transclude` is true).
6637 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`.
6638 * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile`
6639 * function api below) and returns a string value.
6642 * #### `templateUrl`
6643 * This is similar to `template` but the template is loaded from the specified URL, asynchronously.
6645 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element
6646 * for later when the template has been resolved. In the meantime it will continue to compile and link
6647 * sibling and parent elements as though this element had not contained any directives.
6649 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this
6650 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the
6651 * case when only one deeply nested directive has `templateUrl`.
6653 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache}
6655 * You can specify `templateUrl` as a string representing the URL or as a function which takes two
6656 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns
6657 * a string value representing the url. In either case, the template URL is passed through {@link
6658 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}.
6661 * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0)
6662 * specify what the template should replace. Defaults to `false`.
6664 * * `true` - the template will replace the directive's element.
6665 * * `false` - the template will replace the contents of the directive's element.
6667 * The replacement process migrates all of the attributes / classes from the old element to the new
6668 * one. See the {@link guide/directive#template-expanding-directive
6669 * Directives Guide} for an example.
6671 * There are very few scenarios where element replacement is required for the application function,
6672 * the main one being reusable custom components that are used within SVG contexts
6673 * (because SVG doesn't work with custom elements in the DOM tree).
6676 * Extract the contents of the element where the directive appears and make it available to the directive.
6677 * The contents are compiled and provided to the directive as a **transclusion function**. See the
6678 * {@link $compile#transclusion Transclusion} section below.
6680 * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the
6681 * directive's element or the entire element:
6683 * * `true` - transclude the content (i.e. the child nodes) of the directive's element.
6684 * * `'element'` - transclude the whole of the directive's element including any directives on this
6685 * element that defined at a lower priority than this directive. When used, the `template`
6686 * property is ignored.
6692 * function compile(tElement, tAttrs, transclude) { ... }
6695 * The compile function deals with transforming the template DOM. Since most directives do not do
6696 * template transformation, it is not used often. The compile function takes the following arguments:
6698 * * `tElement` - template element - The element where the directive has been declared. It is
6699 * safe to do template transformation on the element and child elements only.
6701 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared
6702 * between all directive compile functions.
6704 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)`
6706 * <div class="alert alert-warning">
6707 * **Note:** The template instance and the link instance may be different objects if the template has
6708 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that
6709 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
6710 * should be done in a linking function rather than in a compile function.
6713 * <div class="alert alert-warning">
6714 * **Note:** The compile function cannot handle directives that recursively use themselves in their
6715 * own templates or compile functions. Compiling these directives results in an infinite loop and a
6716 * stack overflow errors.
6718 * This can be avoided by manually using $compile in the postLink function to imperatively compile
6719 * a directive's template instead of relying on automatic template compilation via `template` or
6720 * `templateUrl` declaration or manual compilation inside the compile function.
6723 * <div class="alert alert-danger">
6724 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it
6725 * e.g. does not know about the right outer scope. Please use the transclude function that is passed
6726 * to the link function instead.
6729 * A compile function can have a return value which can be either a function or an object.
6731 * * returning a (post-link) function - is equivalent to registering the linking function via the
6732 * `link` property of the config object when the compile function is empty.
6734 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to
6735 * control when a linking function should be called during the linking phase. See info about
6736 * pre-linking and post-linking functions below.
6740 * This property is used only if the `compile` property is not defined.
6743 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }
6746 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is
6747 * executed after the template has been cloned. This is where most of the directive logic will be
6750 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the
6751 * directive for registering {@link ng.$rootScope.Scope#$watch watches}.
6753 * * `iElement` - instance element - The element where the directive is to be used. It is safe to
6754 * manipulate the children of the element only in `postLink` function since the children have
6755 * already been linked.
6757 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
6758 * between all directive linking functions.
6760 * * `controller` - the directive's required controller instance(s) - Instances are shared
6761 * among all directives, which allows the directives to use the controllers as a communication
6762 * channel. The exact value depends on the directive's `require` property:
6763 * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one
6764 * * `string`: the controller instance
6765 * * `array`: array of controller instances
6767 * If a required controller cannot be found, and it is optional, the instance is `null`,
6768 * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown.
6770 * Note that you can also require the directive's own controller - it will be made available like
6771 * any other controller.
6773 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
6774 * This is the same as the `$transclude`
6775 * parameter of directive controllers, see there for details.
6776 * `function([scope], cloneLinkingFn, futureParentElement)`.
6778 * #### Pre-linking function
6780 * Executed before the child elements are linked. Not safe to do DOM transformation since the
6781 * compiler linking function will fail to locate the correct elements for linking.
6783 * #### Post-linking function
6785 * Executed after the child elements are linked.
6787 * Note that child elements that contain `templateUrl` directives will not have been compiled
6788 * and linked since they are waiting for their template to load asynchronously and their own
6789 * compilation and linking has been suspended until that occurs.
6791 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting
6792 * for their async templates to be resolved.
6797 * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and
6798 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS
6799 * scope from where they were taken.
6801 * Transclusion is used (often with {@link ngTransclude}) to insert the
6802 * original contents of a directive's element into a specified place in the template of the directive.
6803 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded
6804 * content has access to the properties on the scope from which it was taken, even if the directive
6805 * has isolated scope.
6806 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}.
6808 * This makes it possible for the widget to have private state for its template, while the transcluded
6809 * content has access to its originating scope.
6811 * <div class="alert alert-warning">
6812 * **Note:** When testing an element transclude directive you must not place the directive at the root of the
6813 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
6814 * Testing Transclusion Directives}.
6817 * #### Transclusion Functions
6819 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion
6820 * function** to the directive's `link` function and `controller`. This transclusion function is a special
6821 * **linking function** that will return the compiled contents linked to a new transclusion scope.
6823 * <div class="alert alert-info">
6824 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since
6825 * ngTransclude will deal with it for us.
6828 * If you want to manually control the insertion and removal of the transcluded content in your directive
6829 * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery
6830 * object that contains the compiled DOM, which is linked to the correct transclusion scope.
6832 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts
6833 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded
6834 * content and the `scope` is the newly created transclusion scope, to which the clone is bound.
6836 * <div class="alert alert-info">
6837 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function
6838 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope.
6841 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone
6842 * attach function**:
6845 * var transcludedContent, transclusionScope;
6847 * $transclude(function(clone, scope) {
6848 * element.append(clone);
6849 * transcludedContent = clone;
6850 * transclusionScope = scope;
6854 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the
6855 * associated transclusion scope:
6858 * transcludedContent.remove();
6859 * transclusionScope.$destroy();
6862 * <div class="alert alert-info">
6863 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive
6864 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it),
6865 * then you are also responsible for calling `$destroy` on the transclusion scope.
6868 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat}
6869 * automatically destroy their transluded clones as necessary so you do not need to worry about this if
6870 * you are simply using {@link ngTransclude} to inject the transclusion into your directive.
6873 * #### Transclusion Scopes
6875 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion
6876 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed
6877 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it
6880 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look
6886 * <div transclusion>
6892 * The `$parent` scope hierarchy will look like this:
6900 * but the scopes will inherit prototypically from different scopes to their `$parent`.
6911 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
6912 * `link()` or `compile()` functions. It has a variety of uses.
6914 * accessing *Normalized attribute names:*
6915 * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'.
6916 * the attributes object allows for normalized access to
6919 * * *Directive inter-communication:* All directives share the same instance of the attributes
6920 * object which allows the directives to use the attributes object as inter directive
6923 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object
6924 * allowing other directives to read the interpolated value.
6926 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes
6927 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also
6928 * the only way to easily get the actual value because during the linking phase the interpolation
6929 * hasn't been evaluated yet and so the value is at this time set to `undefined`.
6932 * function linkingFn(scope, elm, attrs, ctrl) {
6933 * // get the attribute value
6934 * console.log(attrs.ngModel);
6936 * // change the attribute
6937 * attrs.$set('ngModel', 'new value');
6939 * // observe changes to interpolated attribute
6940 * attrs.$observe('ngModel', function(value) {
6941 * console.log('ngModel has changed value to ' + value);
6948 * <div class="alert alert-warning">
6949 * **Note**: Typically directives are registered with `module.directive`. The example below is
6950 * to illustrate how `$compile` works.
6953 <example module="compileExample">
6954 <file name="index.html">
6956 angular.module('compileExample', [], function($compileProvider) {
6957 // configure new 'compile' directive by passing a directive
6958 // factory function. The factory function injects the '$compile'
6959 $compileProvider.directive('compile', function($compile) {
6960 // directive factory creates a link function
6961 return function(scope, element, attrs) {
6964 // watch the 'compile' expression for changes
6965 return scope.$eval(attrs.compile);
6968 // when the 'compile' expression changes
6969 // assign it into the current DOM
6970 element.html(value);
6972 // compile the new DOM and link it to the current
6974 // NOTE: we only compile .childNodes so that
6975 // we don't get into infinite loop compiling ourselves
6976 $compile(element.contents())(scope);
6982 .controller('GreeterController', ['$scope', function($scope) {
6983 $scope.name = 'Angular';
6984 $scope.html = 'Hello {{name}}';
6987 <div ng-controller="GreeterController">
6988 <input ng-model="name"> <br/>
6989 <textarea ng-model="html"></textarea> <br/>
6990 <div compile="html"></div>
6993 <file name="protractor.js" type="protractor">
6994 it('should auto compile', function() {
6995 var textarea = $('textarea');
6996 var output = $('div[compile]');
6997 // The initial state reads 'Hello Angular'.
6998 expect(output.getText()).toBe('Hello Angular');
7000 textarea.sendKeys('{{name}}!');
7001 expect(output.getText()).toBe('Angular!');
7008 * @param {string|DOMElement} element Element or HTML string to compile into a template function.
7009 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
7011 * <div class="alert alert-danger">
7012 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
7013 * e.g. will not use the right outer scope. Please pass the transclude function as a
7014 * `parentBoundTranscludeFn` to the link function instead.
7017 * @param {number} maxPriority only apply directives lower than given priority (Only effects the
7018 * root element(s), not their children)
7019 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template
7020 * (a DOM element/tree) to a scope. Where:
7022 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
7023 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
7024 * `template` and call the `cloneAttachFn` function allowing the caller to attach the
7025 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
7026 * called as: <br/> `cloneAttachFn(clonedElement, scope)` where:
7028 * * `clonedElement` - is a clone of the original `element` passed into the compiler.
7029 * * `scope` - is the current scope with which the linking function is working with.
7031 * * `options` - An optional object hash with linking options. If `options` is provided, then the following
7032 * keys may be used to control linking behavior:
7034 * * `parentBoundTranscludeFn` - the transclude function made available to
7035 * directives; if given, it will be passed through to the link functions of
7036 * directives found in `element` during compilation.
7037 * * `transcludeControllers` - an object hash with keys that map controller names
7038 * to controller instances; if given, it will make the controllers
7039 * available to directives.
7040 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add
7041 * the cloned elements; only needed for transcludes that are allowed to contain non html
7042 * elements (e.g. SVG elements). See also the directive.controller property.
7044 * Calling the linking function returns the element of the template. It is either the original
7045 * element passed in, or the clone of the element if the `cloneAttachFn` is provided.
7047 * After linking the view is not updated until after a call to $digest which typically is done by
7048 * Angular automatically.
7050 * If you need access to the bound view, there are two ways to do it:
7052 * - If you are not asking the linking function to clone the template, create the DOM element(s)
7053 * before you send them to the compiler and keep this reference around.
7055 * var element = $compile('<p>{{total}}</p>')(scope);
7058 * - if on the other hand, you need the element to be cloned, the view reference from the original
7059 * example would not point to the clone, but rather to the original template that was cloned. In
7060 * this case, you can access the clone via the cloneAttachFn:
7062 * var templateElement = angular.element('<p>{{total}}</p>'),
7065 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) {
7066 * //attach the clone to DOM document at the right place
7069 * //now we have reference to the cloned DOM via `clonedElement`
7073 * For information on how the compiler works, see the
7074 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
7077 var $compileMinErr = minErr('$compile');
7081 * @name $compileProvider
7085 $CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider'];
7086 function $CompileProvider($provide, $$sanitizeUriProvider) {
7087 var hasDirectives = {},
7088 Suffix = 'Directive',
7089 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/,
7090 CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/,
7091 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
7092 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
7094 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
7095 // The assumption is that future DOM event attribute names will begin with
7096 // 'on' and be composed of only English letters.
7097 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
7099 function parseIsolateBindings(scope, directiveName, isController) {
7100 var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/;
7104 forEach(scope, function(definition, scopeName) {
7105 var match = definition.match(LOCAL_REGEXP);
7108 throw $compileMinErr('iscp',
7109 "Invalid {3} for directive '{0}'." +
7110 " Definition: {... {1}: '{2}' ...}",
7111 directiveName, scopeName, definition,
7112 (isController ? "controller bindings definition" :
7113 "isolate scope definition"));
7116 bindings[scopeName] = {
7118 collection: match[2] === '*',
7119 optional: match[3] === '?',
7120 attrName: match[4] || scopeName
7127 function parseDirectiveBindings(directive, directiveName) {
7130 bindToController: null
7132 if (isObject(directive.scope)) {
7133 if (directive.bindToController === true) {
7134 bindings.bindToController = parseIsolateBindings(directive.scope,
7135 directiveName, true);
7136 bindings.isolateScope = {};
7138 bindings.isolateScope = parseIsolateBindings(directive.scope,
7139 directiveName, false);
7142 if (isObject(directive.bindToController)) {
7143 bindings.bindToController =
7144 parseIsolateBindings(directive.bindToController, directiveName, true);
7146 if (isObject(bindings.bindToController)) {
7147 var controller = directive.controller;
7148 var controllerAs = directive.controllerAs;
7150 // There is no controller, there may or may not be a controllerAs property
7151 throw $compileMinErr('noctrl',
7152 "Cannot bind to controller without directive '{0}'s controller.",
7154 } else if (!identifierForController(controller, controllerAs)) {
7155 // There is a controller, but no identifier or controllerAs property
7156 throw $compileMinErr('noident',
7157 "Cannot bind to controller without identifier for directive '{0}'.",
7164 function assertValidDirectiveName(name) {
7165 var letter = name.charAt(0);
7166 if (!letter || letter !== lowercase(letter)) {
7167 throw $compileMinErr('baddir', "Directive name '{0}' is invalid. The first character must be a lowercase letter", name);
7169 if (name !== name.trim()) {
7170 throw $compileMinErr('baddir',
7171 "Directive name '{0}' is invalid. The name should not contain leading or trailing whitespaces",
7178 * @name $compileProvider#directive
7182 * Register a new directive with the compiler.
7184 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which
7185 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the
7186 * names and the values are the factories.
7187 * @param {Function|Array} directiveFactory An injectable directive factory function. See
7188 * {@link guide/directive} for more info.
7189 * @returns {ng.$compileProvider} Self for chaining.
7191 this.directive = function registerDirective(name, directiveFactory) {
7192 assertNotHasOwnProperty(name, 'directive');
7193 if (isString(name)) {
7194 assertValidDirectiveName(name);
7195 assertArg(directiveFactory, 'directiveFactory');
7196 if (!hasDirectives.hasOwnProperty(name)) {
7197 hasDirectives[name] = [];
7198 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
7199 function($injector, $exceptionHandler) {
7200 var directives = [];
7201 forEach(hasDirectives[name], function(directiveFactory, index) {
7203 var directive = $injector.invoke(directiveFactory);
7204 if (isFunction(directive)) {
7205 directive = { compile: valueFn(directive) };
7206 } else if (!directive.compile && directive.link) {
7207 directive.compile = valueFn(directive.link);
7209 directive.priority = directive.priority || 0;
7210 directive.index = index;
7211 directive.name = directive.name || name;
7212 directive.require = directive.require || (directive.controller && directive.name);
7213 directive.restrict = directive.restrict || 'EA';
7214 var bindings = directive.$$bindings =
7215 parseDirectiveBindings(directive, directive.name);
7216 if (isObject(bindings.isolateScope)) {
7217 directive.$$isolateBindings = bindings.isolateScope;
7219 directive.$$moduleName = directiveFactory.$$moduleName;
7220 directives.push(directive);
7222 $exceptionHandler(e);
7228 hasDirectives[name].push(directiveFactory);
7230 forEach(name, reverseParams(registerDirective));
7238 * @name $compileProvider#aHrefSanitizationWhitelist
7242 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
7243 * urls during a[href] sanitization.
7245 * The sanitization is a security measure aimed at preventing XSS attacks via html links.
7247 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
7248 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
7249 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
7250 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
7252 * @param {RegExp=} regexp New regexp to whitelist urls with.
7253 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
7254 * chaining otherwise.
7256 this.aHrefSanitizationWhitelist = function(regexp) {
7257 if (isDefined(regexp)) {
7258 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp);
7261 return $$sanitizeUriProvider.aHrefSanitizationWhitelist();
7268 * @name $compileProvider#imgSrcSanitizationWhitelist
7272 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
7273 * urls during img[src] sanitization.
7275 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
7277 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
7278 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
7279 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
7280 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
7282 * @param {RegExp=} regexp New regexp to whitelist urls with.
7283 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
7284 * chaining otherwise.
7286 this.imgSrcSanitizationWhitelist = function(regexp) {
7287 if (isDefined(regexp)) {
7288 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp);
7291 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist();
7297 * @name $compileProvider#debugInfoEnabled
7299 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the
7300 * current debugInfoEnabled state
7301 * @returns {*} current value if used as getter or itself (chaining) if used as setter
7306 * Call this method to enable/disable various debug runtime information in the compiler such as adding
7307 * binding information and a reference to the current scope on to DOM elements.
7308 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope
7309 * * `ng-binding` CSS class
7310 * * `$binding` data property containing an array of the binding expressions
7312 * You may want to disable this in production for a significant performance boost. See
7313 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more.
7315 * The default value is true.
7317 var debugInfoEnabled = true;
7318 this.debugInfoEnabled = function(enabled) {
7319 if (isDefined(enabled)) {
7320 debugInfoEnabled = enabled;
7323 return debugInfoEnabled;
7327 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
7328 '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri',
7329 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse,
7330 $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) {
7332 var Attributes = function(element, attributesToCopy) {
7333 if (attributesToCopy) {
7334 var keys = Object.keys(attributesToCopy);
7337 for (i = 0, l = keys.length; i < l; i++) {
7339 this[key] = attributesToCopy[key];
7345 this.$$element = element;
7348 Attributes.prototype = {
7351 * @name $compile.directive.Attributes#$normalize
7355 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
7356 * `data-`) to its normalized, camelCase form.
7358 * Also there is special case for Moz prefix starting with upper case letter.
7360 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
7362 * @param {string} name Name to normalize
7364 $normalize: directiveNormalize,
7369 * @name $compile.directive.Attributes#$addClass
7373 * Adds the CSS class value specified by the classVal parameter to the element. If animations
7374 * are enabled then an animation will be triggered for the class addition.
7376 * @param {string} classVal The className value that will be added to the element
7378 $addClass: function(classVal) {
7379 if (classVal && classVal.length > 0) {
7380 $animate.addClass(this.$$element, classVal);
7386 * @name $compile.directive.Attributes#$removeClass
7390 * Removes the CSS class value specified by the classVal parameter from the element. If
7391 * animations are enabled then an animation will be triggered for the class removal.
7393 * @param {string} classVal The className value that will be removed from the element
7395 $removeClass: function(classVal) {
7396 if (classVal && classVal.length > 0) {
7397 $animate.removeClass(this.$$element, classVal);
7403 * @name $compile.directive.Attributes#$updateClass
7407 * Adds and removes the appropriate CSS class values to the element based on the difference
7408 * between the new and old CSS class values (specified as newClasses and oldClasses).
7410 * @param {string} newClasses The current CSS className value
7411 * @param {string} oldClasses The former CSS className value
7413 $updateClass: function(newClasses, oldClasses) {
7414 var toAdd = tokenDifference(newClasses, oldClasses);
7415 if (toAdd && toAdd.length) {
7416 $animate.addClass(this.$$element, toAdd);
7419 var toRemove = tokenDifference(oldClasses, newClasses);
7420 if (toRemove && toRemove.length) {
7421 $animate.removeClass(this.$$element, toRemove);
7426 * Set a normalized attribute on the element in a way such that all directives
7427 * can share the attribute. This function properly handles boolean attributes.
7428 * @param {string} key Normalized key. (ie ngAttribute)
7429 * @param {string|boolean} value The value to set. If `null` attribute will be deleted.
7430 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
7432 * @param {string=} attrName Optional none normalized name. Defaults to key.
7434 $set: function(key, value, writeAttr, attrName) {
7435 // TODO: decide whether or not to throw an error if "class"
7436 //is set through this function since it may cause $updateClass to
7439 var node = this.$$element[0],
7440 booleanKey = getBooleanAttrName(node, key),
7441 aliasedKey = getAliasedAttrName(key),
7446 this.$$element.prop(key, value);
7447 attrName = booleanKey;
7448 } else if (aliasedKey) {
7449 this[aliasedKey] = value;
7450 observer = aliasedKey;
7455 // translate normalized key to actual key
7457 this.$attr[key] = attrName;
7459 attrName = this.$attr[key];
7461 this.$attr[key] = attrName = snake_case(key, '-');
7465 nodeName = nodeName_(this.$$element);
7467 if ((nodeName === 'a' && key === 'href') ||
7468 (nodeName === 'img' && key === 'src')) {
7469 // sanitize a[href] and img[src] values
7470 this[key] = value = $$sanitizeUri(value, key === 'src');
7471 } else if (nodeName === 'img' && key === 'srcset') {
7472 // sanitize img[srcset] values
7475 // first check if there are spaces because it's not the same pattern
7476 var trimmedSrcset = trim(value);
7477 // ( 999x ,| 999w ,| ,|, )
7478 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
7479 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
7481 // split srcset into tuple of uri and descriptor except for the last item
7482 var rawUris = trimmedSrcset.split(pattern);
7485 var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
7486 for (var i = 0; i < nbrUrisWith2parts; i++) {
7487 var innerIdx = i * 2;
7489 result += $$sanitizeUri(trim(rawUris[innerIdx]), true);
7490 // add the descriptor
7491 result += (" " + trim(rawUris[innerIdx + 1]));
7494 // split the last item into uri and descriptor
7495 var lastTuple = trim(rawUris[i * 2]).split(/\s/);
7497 // sanitize the last uri
7498 result += $$sanitizeUri(trim(lastTuple[0]), true);
7500 // and add the last descriptor if any
7501 if (lastTuple.length === 2) {
7502 result += (" " + trim(lastTuple[1]));
7504 this[key] = value = result;
7507 if (writeAttr !== false) {
7508 if (value === null || isUndefined(value)) {
7509 this.$$element.removeAttr(attrName);
7511 this.$$element.attr(attrName, value);
7516 var $$observers = this.$$observers;
7517 $$observers && forEach($$observers[observer], function(fn) {
7521 $exceptionHandler(e);
7529 * @name $compile.directive.Attributes#$observe
7533 * Observes an interpolated attribute.
7535 * The observer function will be invoked once during the next `$digest` following
7536 * compilation. The observer is then invoked whenever the interpolated value
7539 * @param {string} key Normalized key. (ie ngAttribute) .
7540 * @param {function(interpolatedValue)} fn Function that will be called whenever
7541 the interpolated value of the attribute changes.
7542 * See the {@link guide/directive#text-and-attribute-bindings Directives} guide for more info.
7543 * @returns {function()} Returns a deregistration function for this observer.
7545 $observe: function(key, fn) {
7547 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())),
7548 listeners = ($$observers[key] || ($$observers[key] = []));
7551 $rootScope.$evalAsync(function() {
7552 if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) {
7553 // no one registered attribute interpolation function, so lets call it manually
7559 arrayRemove(listeners, fn);
7565 function safeAddClass($element, className) {
7567 $element.addClass(className);
7569 // ignore, since it means that we are trying to set class on
7570 // SVG element, where class name is read-only.
7575 var startSymbol = $interpolate.startSymbol(),
7576 endSymbol = $interpolate.endSymbol(),
7577 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
7579 : function denormalizeTemplate(template) {
7580 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
7582 NG_ATTR_BINDING = /^ngAttr[A-Z]/;
7583 var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/;
7585 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) {
7586 var bindings = $element.data('$binding') || [];
7588 if (isArray(binding)) {
7589 bindings = bindings.concat(binding);
7591 bindings.push(binding);
7594 $element.data('$binding', bindings);
7597 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) {
7598 safeAddClass($element, 'ng-binding');
7601 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) {
7602 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope';
7603 $element.data(dataName, scope);
7606 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) {
7607 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope');
7612 //================================
7614 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective,
7615 previousCompileContext) {
7616 if (!($compileNodes instanceof jqLite)) {
7617 // jquery always rewraps, whereas we need to preserve the original selector so that we can
7619 $compileNodes = jqLite($compileNodes);
7621 // We can not compile top level text elements since text nodes can be merged and we will
7622 // not be able to attach scope data to them, so we will wrap them in <span>
7623 forEach($compileNodes, function(node, index) {
7624 if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) {
7625 $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
7628 var compositeLinkFn =
7629 compileNodes($compileNodes, transcludeFn, $compileNodes,
7630 maxPriority, ignoreDirective, previousCompileContext);
7631 compile.$$addScopeClass($compileNodes);
7632 var namespace = null;
7633 return function publicLinkFn(scope, cloneConnectFn, options) {
7634 assertArg(scope, 'scope');
7636 if (previousCompileContext && previousCompileContext.needsNewScope) {
7637 // A parent directive did a replace and a directive on this element asked
7638 // for transclusion, which caused us to lose a layer of element on which
7639 // we could hold the new transclusion scope, so we will create it manually
7641 scope = scope.$parent.$new();
7644 options = options || {};
7645 var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
7646 transcludeControllers = options.transcludeControllers,
7647 futureParentElement = options.futureParentElement;
7649 // When `parentBoundTranscludeFn` is passed, it is a
7650 // `controllersBoundTransclude` function (it was previously passed
7651 // as `transclude` to directive.link) so we must unwrap it to get
7652 // its `boundTranscludeFn`
7653 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) {
7654 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;
7658 namespace = detectNamespaceForChildElements(futureParentElement);
7661 if (namespace !== 'html') {
7662 // When using a directive with replace:true and templateUrl the $compileNodes
7663 // (or a child element inside of them)
7664 // might change, so we need to recreate the namespace adapted compileNodes
7665 // for call to the link function.
7666 // Note: This will already clone the nodes...
7668 wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html())
7670 } else if (cloneConnectFn) {
7671 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
7672 // and sometimes changes the structure of the DOM.
7673 $linkNode = JQLitePrototype.clone.call($compileNodes);
7675 $linkNode = $compileNodes;
7678 if (transcludeControllers) {
7679 for (var controllerName in transcludeControllers) {
7680 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance);
7684 compile.$$addScopeInfo($linkNode, scope);
7686 if (cloneConnectFn) cloneConnectFn($linkNode, scope);
7687 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);
7692 function detectNamespaceForChildElements(parentElement) {
7693 // TODO: Make this detect MathML as well...
7694 var node = parentElement && parentElement[0];
7698 return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html';
7703 * Compile function matches each node in nodeList against the directives. Once all directives
7704 * for a particular node are collected their compile functions are executed. The compile
7705 * functions return values - the linking functions - are combined into a composite linking
7706 * function, which is the a linking function for the node.
7708 * @param {NodeList} nodeList an array of nodes or NodeList to compile
7709 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
7710 * scope argument is auto-generated to the new child of the transcluded parent scope.
7711 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then
7712 * the rootElement must be set the jqLite collection of the compile root. This is
7713 * needed so that the jqLite collection items can be replaced with widgets.
7714 * @param {number=} maxPriority Max directive priority.
7715 * @returns {Function} A composite linking function of all of the matched directives or null.
7717 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective,
7718 previousCompileContext) {
7720 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound;
7722 for (var i = 0; i < nodeList.length; i++) {
7723 attrs = new Attributes();
7725 // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
7726 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined,
7729 nodeLinkFn = (directives.length)
7730 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement,
7731 null, [], [], previousCompileContext)
7734 if (nodeLinkFn && nodeLinkFn.scope) {
7735 compile.$$addScopeClass(attrs.$$element);
7738 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal ||
7739 !(childNodes = nodeList[i].childNodes) ||
7742 : compileNodes(childNodes,
7744 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement)
7745 && nodeLinkFn.transclude) : transcludeFn);
7747 if (nodeLinkFn || childLinkFn) {
7748 linkFns.push(i, nodeLinkFn, childLinkFn);
7750 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn;
7753 //use the previous context only for the first element in the virtual group
7754 previousCompileContext = null;
7757 // return a linking function if we have found anything, null otherwise
7758 return linkFnFound ? compositeLinkFn : null;
7760 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) {
7761 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn;
7765 if (nodeLinkFnFound) {
7766 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our
7767 // offsets don't get screwed up
7768 var nodeListLength = nodeList.length;
7769 stableNodeList = new Array(nodeListLength);
7771 // create a sparse array by only copying the elements which have a linkFn
7772 for (i = 0; i < linkFns.length; i+=3) {
7774 stableNodeList[idx] = nodeList[idx];
7777 stableNodeList = nodeList;
7780 for (i = 0, ii = linkFns.length; i < ii;) {
7781 node = stableNodeList[linkFns[i++]];
7782 nodeLinkFn = linkFns[i++];
7783 childLinkFn = linkFns[i++];
7786 if (nodeLinkFn.scope) {
7787 childScope = scope.$new();
7788 compile.$$addScopeInfo(jqLite(node), childScope);
7793 if (nodeLinkFn.transcludeOnThisElement) {
7794 childBoundTranscludeFn = createBoundTranscludeFn(
7795 scope, nodeLinkFn.transclude, parentBoundTranscludeFn);
7797 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) {
7798 childBoundTranscludeFn = parentBoundTranscludeFn;
7800 } else if (!parentBoundTranscludeFn && transcludeFn) {
7801 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn);
7804 childBoundTranscludeFn = null;
7807 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
7809 } else if (childLinkFn) {
7810 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
7816 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) {
7818 var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
7820 if (!transcludedScope) {
7821 transcludedScope = scope.$new(false, containingScope);
7822 transcludedScope.$$transcluded = true;
7825 return transcludeFn(transcludedScope, cloneFn, {
7826 parentBoundTranscludeFn: previousBoundTranscludeFn,
7827 transcludeControllers: controllers,
7828 futureParentElement: futureParentElement
7832 return boundTranscludeFn;
7836 * Looks for directives on the given node and adds them to the directive collection which is
7839 * @param node Node to search.
7840 * @param directives An array to which the directives are added to. This array is sorted before
7841 * the function returns.
7842 * @param attrs The shared attrs object which is used to populate the normalized attributes.
7843 * @param {number=} maxPriority Max directive priority.
7845 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
7846 var nodeType = node.nodeType,
7847 attrsMap = attrs.$attr,
7852 case NODE_TYPE_ELEMENT: /* Element */
7853 // use the node name: <directive>
7854 addDirective(directives,
7855 directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective);
7857 // iterate over the attributes
7858 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes,
7859 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
7860 var attrStartName = false;
7861 var attrEndName = false;
7865 value = trim(attr.value);
7867 // support ngAttr attribute binding
7868 ngAttrName = directiveNormalize(name);
7869 if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
7870 name = name.replace(PREFIX_REGEXP, '')
7871 .substr(8).replace(/_(.)/g, function(match, letter) {
7872 return letter.toUpperCase();
7876 var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE);
7877 if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) {
7878 attrStartName = name;
7879 attrEndName = name.substr(0, name.length - 5) + 'end';
7880 name = name.substr(0, name.length - 6);
7883 nName = directiveNormalize(name.toLowerCase());
7884 attrsMap[nName] = name;
7885 if (isNgAttr || !attrs.hasOwnProperty(nName)) {
7886 attrs[nName] = value;
7887 if (getBooleanAttrName(node, nName)) {
7888 attrs[nName] = true; // presence means true
7891 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
7892 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
7896 // use class as directive
7897 className = node.className;
7898 if (isObject(className)) {
7899 // Maybe SVGAnimatedString
7900 className = className.animVal;
7902 if (isString(className) && className !== '') {
7903 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
7904 nName = directiveNormalize(match[2]);
7905 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) {
7906 attrs[nName] = trim(match[3]);
7908 className = className.substr(match.index + match[0].length);
7912 case NODE_TYPE_TEXT: /* Text Node */
7914 // Workaround for #11781
7915 while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) {
7916 node.nodeValue = node.nodeValue + node.nextSibling.nodeValue;
7917 node.parentNode.removeChild(node.nextSibling);
7920 addTextInterpolateDirective(directives, node.nodeValue);
7922 case NODE_TYPE_COMMENT: /* Comment */
7924 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
7926 nName = directiveNormalize(match[1]);
7927 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) {
7928 attrs[nName] = trim(match[2]);
7932 // turns out that under some circumstances IE9 throws errors when one attempts to read
7933 // comment's node value.
7934 // Just ignore it and continue. (Can't seem to reproduce in test case.)
7939 directives.sort(byPriority);
7944 * Given a node with an directive-start it collects all of the siblings until it finds
7951 function groupScan(node, attrStart, attrEnd) {
7954 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
7957 throw $compileMinErr('uterdir',
7958 "Unterminated attribute, found '{0}' but no matching '{1}' found.",
7959 attrStart, attrEnd);
7961 if (node.nodeType == NODE_TYPE_ELEMENT) {
7962 if (node.hasAttribute(attrStart)) depth++;
7963 if (node.hasAttribute(attrEnd)) depth--;
7966 node = node.nextSibling;
7967 } while (depth > 0);
7972 return jqLite(nodes);
7976 * Wrapper for linking function which converts normal linking function into a grouped
7981 * @returns {Function}
7983 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
7984 return function(scope, element, attrs, controllers, transcludeFn) {
7985 element = groupScan(element[0], attrStart, attrEnd);
7986 return linkFn(scope, element, attrs, controllers, transcludeFn);
7991 * Once the directives have been collected, their compile functions are executed. This method
7992 * is responsible for inlining directive templates as well as terminating the application
7993 * of the directives if the terminal directive has been reached.
7995 * @param {Array} directives Array of collected directives to execute their compile function.
7996 * this needs to be pre-sorted by priority order.
7997 * @param {Node} compileNode The raw DOM node to apply the compile functions to
7998 * @param {Object} templateAttrs The shared attribute function
7999 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
8000 * scope argument is auto-generated to the new
8001 * child of the transcluded parent scope.
8002 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this
8003 * argument has the root jqLite array so that we can replace nodes
8005 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when
8006 * compiling the transclusion.
8007 * @param {Array.<Function>} preLinkFns
8008 * @param {Array.<Function>} postLinkFns
8009 * @param {Object} previousCompileContext Context used for previous compilation of the current
8011 * @returns {Function} linkFn
8013 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn,
8014 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns,
8015 previousCompileContext) {
8016 previousCompileContext = previousCompileContext || {};
8018 var terminalPriority = -Number.MAX_VALUE,
8019 newScopeDirective = previousCompileContext.newScopeDirective,
8020 controllerDirectives = previousCompileContext.controllerDirectives,
8021 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,
8022 templateDirective = previousCompileContext.templateDirective,
8023 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
8024 hasTranscludeDirective = false,
8025 hasTemplate = false,
8026 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective,
8027 $compileNode = templateAttrs.$$element = jqLite(compileNode),
8031 replaceDirective = originalReplaceDirective,
8032 childTranscludeFn = transcludeFn,
8036 // executes all directives on the current element
8037 for (var i = 0, ii = directives.length; i < ii; i++) {
8038 directive = directives[i];
8039 var attrStart = directive.$$start;
8040 var attrEnd = directive.$$end;
8042 // collect multiblock sections
8044 $compileNode = groupScan(compileNode, attrStart, attrEnd);
8046 $template = undefined;
8048 if (terminalPriority > directive.priority) {
8049 break; // prevent further processing of directives
8052 if (directiveValue = directive.scope) {
8054 // skip the check for directives with async templates, we'll check the derived sync
8055 // directive when the template arrives
8056 if (!directive.templateUrl) {
8057 if (isObject(directiveValue)) {
8058 // This directive is trying to add an isolated scope.
8059 // Check that there is no scope of any kind already
8060 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective,
8061 directive, $compileNode);
8062 newIsolateScopeDirective = directive;
8064 // This directive is trying to add a child scope.
8065 // Check that there is no isolated scope already
8066 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive,
8071 newScopeDirective = newScopeDirective || directive;
8074 directiveName = directive.name;
8076 if (!directive.templateUrl && directive.controller) {
8077 directiveValue = directive.controller;
8078 controllerDirectives = controllerDirectives || createMap();
8079 assertNoDuplicate("'" + directiveName + "' controller",
8080 controllerDirectives[directiveName], directive, $compileNode);
8081 controllerDirectives[directiveName] = directive;
8084 if (directiveValue = directive.transclude) {
8085 hasTranscludeDirective = true;
8087 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion.
8088 // This option should only be used by directives that know how to safely handle element transclusion,
8089 // where the transcluded nodes are added or replaced after linking.
8090 if (!directive.$$tlb) {
8091 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode);
8092 nonTlbTranscludeDirective = directive;
8095 if (directiveValue == 'element') {
8096 hasElementTranscludeDirective = true;
8097 terminalPriority = directive.priority;
8098 $template = $compileNode;
8099 $compileNode = templateAttrs.$$element =
8100 jqLite(document.createComment(' ' + directiveName + ': ' +
8101 templateAttrs[directiveName] + ' '));
8102 compileNode = $compileNode[0];
8103 replaceWith(jqCollection, sliceArgs($template), compileNode);
8105 childTranscludeFn = compile($template, transcludeFn, terminalPriority,
8106 replaceDirective && replaceDirective.name, {
8108 // - controllerDirectives - otherwise we'll create duplicates controllers
8109 // - newIsolateScopeDirective or templateDirective - combining templates with
8110 // element transclusion doesn't make sense.
8112 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion
8113 // on the same element more than once.
8114 nonTlbTranscludeDirective: nonTlbTranscludeDirective
8117 $template = jqLite(jqLiteClone(compileNode)).contents();
8118 $compileNode.empty(); // clear contents
8119 childTranscludeFn = compile($template, transcludeFn, undefined,
8120 undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope});
8124 if (directive.template) {
8126 assertNoDuplicate('template', templateDirective, directive, $compileNode);
8127 templateDirective = directive;
8129 directiveValue = (isFunction(directive.template))
8130 ? directive.template($compileNode, templateAttrs)
8131 : directive.template;
8133 directiveValue = denormalizeTemplate(directiveValue);
8135 if (directive.replace) {
8136 replaceDirective = directive;
8137 if (jqLiteIsTextNode(directiveValue)) {
8140 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
8142 compileNode = $template[0];
8144 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
8145 throw $compileMinErr('tplrt',
8146 "Template for directive '{0}' must have exactly one root element. {1}",
8150 replaceWith(jqCollection, $compileNode, compileNode);
8152 var newTemplateAttrs = {$attr: {}};
8154 // combine directives from the original node and from the template:
8155 // - take the array of directives for this element
8156 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed)
8157 // - collect directives from the template and sort them by priority
8158 // - combine directives as: processed + template + unprocessed
8159 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs);
8160 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1));
8162 if (newIsolateScopeDirective || newScopeDirective) {
8163 // The original directive caused the current element to be replaced but this element
8164 // also needs to have a new scope, so we need to tell the template directives
8165 // that they would need to get their scope from further up, if they require transclusion
8166 markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective);
8168 directives = directives.concat(templateDirectives).concat(unprocessedDirectives);
8169 mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
8171 ii = directives.length;
8173 $compileNode.html(directiveValue);
8177 if (directive.templateUrl) {
8179 assertNoDuplicate('template', templateDirective, directive, $compileNode);
8180 templateDirective = directive;
8182 if (directive.replace) {
8183 replaceDirective = directive;
8186 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
8187 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, {
8188 controllerDirectives: controllerDirectives,
8189 newScopeDirective: (newScopeDirective !== directive) && newScopeDirective,
8190 newIsolateScopeDirective: newIsolateScopeDirective,
8191 templateDirective: templateDirective,
8192 nonTlbTranscludeDirective: nonTlbTranscludeDirective
8194 ii = directives.length;
8195 } else if (directive.compile) {
8197 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
8198 if (isFunction(linkFn)) {
8199 addLinkFns(null, linkFn, attrStart, attrEnd);
8200 } else if (linkFn) {
8201 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd);
8204 $exceptionHandler(e, startingTag($compileNode));
8208 if (directive.terminal) {
8209 nodeLinkFn.terminal = true;
8210 terminalPriority = Math.max(terminalPriority, directive.priority);
8215 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
8216 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective;
8217 nodeLinkFn.templateOnThisElement = hasTemplate;
8218 nodeLinkFn.transclude = childTranscludeFn;
8220 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective;
8222 // might be normal or delayed nodeLinkFn depending on if templateUrl is present
8225 ////////////////////
8227 function addLinkFns(pre, post, attrStart, attrEnd) {
8229 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd);
8230 pre.require = directive.require;
8231 pre.directiveName = directiveName;
8232 if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
8233 pre = cloneAndAnnotateFn(pre, {isolateScope: true});
8235 preLinkFns.push(pre);
8238 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd);
8239 post.require = directive.require;
8240 post.directiveName = directiveName;
8241 if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
8242 post = cloneAndAnnotateFn(post, {isolateScope: true});
8244 postLinkFns.push(post);
8249 function getControllers(directiveName, require, $element, elementControllers) {
8252 if (isString(require)) {
8253 var match = require.match(REQUIRE_PREFIX_REGEXP);
8254 var name = require.substring(match[0].length);
8255 var inheritType = match[1] || match[3];
8256 var optional = match[2] === '?';
8258 //If only parents then start at the parent element
8259 if (inheritType === '^^') {
8260 $element = $element.parent();
8261 //Otherwise attempt getting the controller from elementControllers in case
8262 //the element is transcluded (and has no data) and to avoid .data if possible
8264 value = elementControllers && elementControllers[name];
8265 value = value && value.instance;
8269 var dataName = '$' + name + 'Controller';
8270 value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName);
8273 if (!value && !optional) {
8274 throw $compileMinErr('ctreq',
8275 "Controller '{0}', required by directive '{1}', can't be found!",
8276 name, directiveName);
8278 } else if (isArray(require)) {
8280 for (var i = 0, ii = require.length; i < ii; i++) {
8281 value[i] = getControllers(directiveName, require[i], $element, elementControllers);
8285 return value || null;
8288 function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope) {
8289 var elementControllers = createMap();
8290 for (var controllerKey in controllerDirectives) {
8291 var directive = controllerDirectives[controllerKey];
8293 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
8296 $transclude: transcludeFn
8299 var controller = directive.controller;
8300 if (controller == '@') {
8301 controller = attrs[directive.name];
8304 var controllerInstance = $controller(controller, locals, true, directive.controllerAs);
8306 // For directives with element transclusion the element is a comment,
8307 // but jQuery .data doesn't support attaching data to comment nodes as it's hard to
8308 // clean up (http://bugs.jquery.com/ticket/8335).
8309 // Instead, we save the controllers for the element in a local hash and attach to .data
8310 // later, once we have the actual element.
8311 elementControllers[directive.name] = controllerInstance;
8312 if (!hasElementTranscludeDirective) {
8313 $element.data('$' + directive.name + 'Controller', controllerInstance.instance);
8316 return elementControllers;
8319 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
8320 var linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element,
8321 attrs, removeScopeBindingWatches, removeControllerBindingWatches;
8323 if (compileNode === linkNode) {
8324 attrs = templateAttrs;
8325 $element = templateAttrs.$$element;
8327 $element = jqLite(linkNode);
8328 attrs = new Attributes($element, templateAttrs);
8331 controllerScope = scope;
8332 if (newIsolateScopeDirective) {
8333 isolateScope = scope.$new(true);
8334 } else if (newScopeDirective) {
8335 controllerScope = scope.$parent;
8338 if (boundTranscludeFn) {
8339 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
8340 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
8341 transcludeFn = controllersBoundTransclude;
8342 transcludeFn.$$boundTransclude = boundTranscludeFn;
8345 if (controllerDirectives) {
8346 elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope);
8349 if (newIsolateScopeDirective) {
8350 // Initialize isolate scope bindings for new isolate scope directive.
8351 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
8352 templateDirective === newIsolateScopeDirective.$$originalDirective)));
8353 compile.$$addScopeClass($element, true);
8354 isolateScope.$$isolateBindings =
8355 newIsolateScopeDirective.$$isolateBindings;
8356 removeScopeBindingWatches = initializeDirectiveBindings(scope, attrs, isolateScope,
8357 isolateScope.$$isolateBindings,
8358 newIsolateScopeDirective);
8359 if (removeScopeBindingWatches) {
8360 isolateScope.$on('$destroy', removeScopeBindingWatches);
8364 // Initialize bindToController bindings
8365 for (var name in elementControllers) {
8366 var controllerDirective = controllerDirectives[name];
8367 var controller = elementControllers[name];
8368 var bindings = controllerDirective.$$bindings.bindToController;
8370 if (controller.identifier && bindings) {
8371 removeControllerBindingWatches =
8372 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
8375 var controllerResult = controller();
8376 if (controllerResult !== controller.instance) {
8377 // If the controller constructor has a return value, overwrite the instance
8378 // from setupControllers
8379 controller.instance = controllerResult;
8380 $element.data('$' + controllerDirective.name + 'Controller', controllerResult);
8381 removeControllerBindingWatches && removeControllerBindingWatches();
8382 removeControllerBindingWatches =
8383 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
8388 for (i = 0, ii = preLinkFns.length; i < ii; i++) {
8389 linkFn = preLinkFns[i];
8390 invokeLinkFn(linkFn,
8391 linkFn.isolateScope ? isolateScope : scope,
8394 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
8400 // We only pass the isolate scope, if the isolate directive has a template,
8401 // otherwise the child elements do not belong to the isolate directive.
8402 var scopeToChild = scope;
8403 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) {
8404 scopeToChild = isolateScope;
8406 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
8409 for (i = postLinkFns.length - 1; i >= 0; i--) {
8410 linkFn = postLinkFns[i];
8411 invokeLinkFn(linkFn,
8412 linkFn.isolateScope ? isolateScope : scope,
8415 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
8420 // This is the function that is injected as `$transclude`.
8421 // Note: all arguments are optional!
8422 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) {
8423 var transcludeControllers;
8425 // No scope passed in:
8426 if (!isScope(scope)) {
8427 futureParentElement = cloneAttachFn;
8428 cloneAttachFn = scope;
8432 if (hasElementTranscludeDirective) {
8433 transcludeControllers = elementControllers;
8435 if (!futureParentElement) {
8436 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
8438 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
8443 // Depending upon the context in which a directive finds itself it might need to have a new isolated
8444 // or child scope created. For instance:
8445 // * if the directive has been pulled into a template because another directive with a higher priority
8446 // asked for element transclusion
8447 // * if the directive itself asks for transclusion but it is at the root of a template and the original
8448 // element was replaced. See https://github.com/angular/angular.js/issues/12936
8449 function markDirectiveScope(directives, isolateScope, newScope) {
8450 for (var j = 0, jj = directives.length; j < jj; j++) {
8451 directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope});
8456 * looks up the directive and decorates it with exception handling and proper parameters. We
8457 * call this the boundDirective.
8459 * @param {string} name name of the directive to look up.
8460 * @param {string} location The directive must be found in specific format.
8461 * String containing any of theses characters:
8463 * * `E`: element name
8467 * @returns {boolean} true if directive was added.
8469 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName,
8471 if (name === ignoreDirective) return null;
8473 if (hasDirectives.hasOwnProperty(name)) {
8474 for (var directive, directives = $injector.get(name + Suffix),
8475 i = 0, ii = directives.length; i < ii; i++) {
8477 directive = directives[i];
8478 if ((isUndefined(maxPriority) || maxPriority > directive.priority) &&
8479 directive.restrict.indexOf(location) != -1) {
8480 if (startAttrName) {
8481 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
8483 tDirectives.push(directive);
8486 } catch (e) { $exceptionHandler(e); }
8494 * looks up the directive and returns true if it is a multi-element directive,
8495 * and therefore requires DOM nodes between -start and -end markers to be grouped
8498 * @param {string} name name of the directive to look up.
8499 * @returns true if directive was registered as multi-element.
8501 function directiveIsMultiElement(name) {
8502 if (hasDirectives.hasOwnProperty(name)) {
8503 for (var directive, directives = $injector.get(name + Suffix),
8504 i = 0, ii = directives.length; i < ii; i++) {
8505 directive = directives[i];
8506 if (directive.multiElement) {
8515 * When the element is replaced with HTML template then the new attributes
8516 * on the template need to be merged with the existing attributes in the DOM.
8517 * The desired effect is to have both of the attributes present.
8519 * @param {object} dst destination attributes (original DOM)
8520 * @param {object} src source attributes (from the directive template)
8522 function mergeTemplateAttributes(dst, src) {
8523 var srcAttr = src.$attr,
8524 dstAttr = dst.$attr,
8525 $element = dst.$$element;
8527 // reapply the old attributes to the new element
8528 forEach(dst, function(value, key) {
8529 if (key.charAt(0) != '$') {
8530 if (src[key] && src[key] !== value) {
8531 value += (key === 'style' ? ';' : ' ') + src[key];
8533 dst.$set(key, value, true, srcAttr[key]);
8537 // copy the new attributes on the old attrs object
8538 forEach(src, function(value, key) {
8539 if (key == 'class') {
8540 safeAddClass($element, value);
8541 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value;
8542 } else if (key == 'style') {
8543 $element.attr('style', $element.attr('style') + ';' + value);
8544 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value;
8545 // `dst` will never contain hasOwnProperty as DOM parser won't let it.
8546 // You will get an "InvalidCharacterError: DOM Exception 5" error if you
8547 // have an attribute like "has-own-property" or "data-has-own-property", etc.
8548 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) {
8550 dstAttr[key] = srcAttr[key];
8556 function compileTemplateUrl(directives, $compileNode, tAttrs,
8557 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) {
8559 afterTemplateNodeLinkFn,
8560 afterTemplateChildLinkFn,
8561 beforeTemplateCompileNode = $compileNode[0],
8562 origAsyncDirective = directives.shift(),
8563 derivedSyncDirective = inherit(origAsyncDirective, {
8564 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
8566 templateUrl = (isFunction(origAsyncDirective.templateUrl))
8567 ? origAsyncDirective.templateUrl($compileNode, tAttrs)
8568 : origAsyncDirective.templateUrl,
8569 templateNamespace = origAsyncDirective.templateNamespace;
8571 $compileNode.empty();
8573 $templateRequest(templateUrl)
8574 .then(function(content) {
8575 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn;
8577 content = denormalizeTemplate(content);
8579 if (origAsyncDirective.replace) {
8580 if (jqLiteIsTextNode(content)) {
8583 $template = removeComments(wrapTemplate(templateNamespace, trim(content)));
8585 compileNode = $template[0];
8587 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
8588 throw $compileMinErr('tplrt',
8589 "Template for directive '{0}' must have exactly one root element. {1}",
8590 origAsyncDirective.name, templateUrl);
8593 tempTemplateAttrs = {$attr: {}};
8594 replaceWith($rootElement, $compileNode, compileNode);
8595 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs);
8597 if (isObject(origAsyncDirective.scope)) {
8598 // the original directive that caused the template to be loaded async required
8600 markDirectiveScope(templateDirectives, true);
8602 directives = templateDirectives.concat(directives);
8603 mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
8605 compileNode = beforeTemplateCompileNode;
8606 $compileNode.html(content);
8609 directives.unshift(derivedSyncDirective);
8611 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs,
8612 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns,
8613 previousCompileContext);
8614 forEach($rootElement, function(node, i) {
8615 if (node == compileNode) {
8616 $rootElement[i] = $compileNode[0];
8619 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
8621 while (linkQueue.length) {
8622 var scope = linkQueue.shift(),
8623 beforeTemplateLinkNode = linkQueue.shift(),
8624 linkRootElement = linkQueue.shift(),
8625 boundTranscludeFn = linkQueue.shift(),
8626 linkNode = $compileNode[0];
8628 if (scope.$$destroyed) continue;
8630 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
8631 var oldClasses = beforeTemplateLinkNode.className;
8633 if (!(previousCompileContext.hasElementTranscludeDirective &&
8634 origAsyncDirective.replace)) {
8635 // it was cloned therefore we have to clone as well.
8636 linkNode = jqLiteClone(compileNode);
8638 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
8640 // Copy in CSS classes from original node
8641 safeAddClass(jqLite(linkNode), oldClasses);
8643 if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
8644 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
8646 childBoundTranscludeFn = boundTranscludeFn;
8648 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
8649 childBoundTranscludeFn);
8654 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
8655 var childBoundTranscludeFn = boundTranscludeFn;
8656 if (scope.$$destroyed) return;
8658 linkQueue.push(scope,
8661 childBoundTranscludeFn);
8663 if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
8664 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
8666 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn);
8673 * Sorting function for bound directives.
8675 function byPriority(a, b) {
8676 var diff = b.priority - a.priority;
8677 if (diff !== 0) return diff;
8678 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1;
8679 return a.index - b.index;
8682 function assertNoDuplicate(what, previousDirective, directive, element) {
8684 function wrapModuleNameIfDefined(moduleName) {
8686 (' (module: ' + moduleName + ')') :
8690 if (previousDirective) {
8691 throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}',
8692 previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName),
8693 directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element));
8698 function addTextInterpolateDirective(directives, text) {
8699 var interpolateFn = $interpolate(text, true);
8700 if (interpolateFn) {
8703 compile: function textInterpolateCompileFn(templateNode) {
8704 var templateNodeParent = templateNode.parent(),
8705 hasCompileParent = !!templateNodeParent.length;
8707 // When transcluding a template that has bindings in the root
8708 // we don't have a parent and thus need to add the class during linking fn.
8709 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent);
8711 return function textInterpolateLinkFn(scope, node) {
8712 var parent = node.parent();
8713 if (!hasCompileParent) compile.$$addBindingClass(parent);
8714 compile.$$addBindingInfo(parent, interpolateFn.expressions);
8715 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
8716 node[0].nodeValue = value;
8725 function wrapTemplate(type, template) {
8726 type = lowercase(type || 'html');
8730 var wrapper = document.createElement('div');
8731 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>';
8732 return wrapper.childNodes[0].childNodes;
8739 function getTrustedContext(node, attrNormalizedName) {
8740 if (attrNormalizedName == "srcdoc") {
8743 var tag = nodeName_(node);
8744 // maction[xlink:href] can source SVG. It's not limited to <maction>.
8745 if (attrNormalizedName == "xlinkHref" ||
8746 (tag == "form" && attrNormalizedName == "action") ||
8747 (tag != "img" && (attrNormalizedName == "src" ||
8748 attrNormalizedName == "ngSrc"))) {
8749 return $sce.RESOURCE_URL;
8754 function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) {
8755 var trustedContext = getTrustedContext(node, name);
8756 allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing;
8758 var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing);
8760 // no interpolation found -> ignore
8761 if (!interpolateFn) return;
8764 if (name === "multiple" && nodeName_(node) === "select") {
8765 throw $compileMinErr("selmulti",
8766 "Binding to the 'multiple' attribute is not supported. Element: {0}",
8772 compile: function() {
8774 pre: function attrInterpolatePreLinkFn(scope, element, attr) {
8775 var $$observers = (attr.$$observers || (attr.$$observers = createMap()));
8777 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
8778 throw $compileMinErr('nodomevents',
8779 "Interpolations for HTML DOM event attributes are disallowed. Please use the " +
8780 "ng- versions (such as ng-click instead of onclick) instead.");
8783 // If the attribute has changed since last $interpolate()ed
8784 var newValue = attr[name];
8785 if (newValue !== value) {
8786 // we need to interpolate again since the attribute value has been updated
8787 // (e.g. by another directive's compile function)
8788 // ensure unset/empty values make interpolateFn falsy
8789 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing);
8793 // if attribute was updated so that there is no interpolation going on we don't want to
8794 // register any observers
8795 if (!interpolateFn) return;
8797 // initialize attr object so that it's ready in case we need the value for isolate
8798 // scope initialization, otherwise the value would not be available from isolate
8799 // directive's linking fn during linking phase
8800 attr[name] = interpolateFn(scope);
8802 ($$observers[name] || ($$observers[name] = [])).$$inter = true;
8803 (attr.$$observers && attr.$$observers[name].$$scope || scope).
8804 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) {
8805 //special case for class attribute addition + removal
8806 //so that class changes can tap into the animation
8807 //hooks provided by the $animate service. Be sure to
8808 //skip animations when the first digest occurs (when
8809 //both the new and the old values are the same) since
8810 //the CSS classes are the non-interpolated values
8811 if (name === 'class' && newValue != oldValue) {
8812 attr.$updateClass(newValue, oldValue);
8814 attr.$set(name, newValue);
8825 * This is a special jqLite.replaceWith, which can replace items which
8826 * have no parents, provided that the containing jqLite collection is provided.
8828 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
8829 * in the root of the tree.
8830 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep
8831 * the shell, but replace its DOM node reference.
8832 * @param {Node} newNode The new DOM node.
8834 function replaceWith($rootElement, elementsToRemove, newNode) {
8835 var firstElementToRemove = elementsToRemove[0],
8836 removeCount = elementsToRemove.length,
8837 parent = firstElementToRemove.parentNode,
8841 for (i = 0, ii = $rootElement.length; i < ii; i++) {
8842 if ($rootElement[i] == firstElementToRemove) {
8843 $rootElement[i++] = newNode;
8844 for (var j = i, j2 = j + removeCount - 1,
8845 jj = $rootElement.length;
8846 j < jj; j++, j2++) {
8848 $rootElement[j] = $rootElement[j2];
8850 delete $rootElement[j];
8853 $rootElement.length -= removeCount - 1;
8855 // If the replaced element is also the jQuery .context then replace it
8856 // .context is a deprecated jQuery api, so we should set it only when jQuery set it
8857 // http://api.jquery.com/context/
8858 if ($rootElement.context === firstElementToRemove) {
8859 $rootElement.context = newNode;
8867 parent.replaceChild(newNode, firstElementToRemove);
8870 // TODO(perf): what's this document fragment for? is it needed? can we at least reuse it?
8871 var fragment = document.createDocumentFragment();
8872 fragment.appendChild(firstElementToRemove);
8874 if (jqLite.hasData(firstElementToRemove)) {
8875 // Copy over user data (that includes Angular's $scope etc.). Don't copy private
8876 // data here because there's no public interface in jQuery to do that and copying over
8877 // event listeners (which is the main use of private data) wouldn't work anyway.
8878 jqLite.data(newNode, jqLite.data(firstElementToRemove));
8880 // Remove data of the replaced element. We cannot just call .remove()
8881 // on the element it since that would deallocate scope that is needed
8882 // for the new node. Instead, remove the data "manually".
8884 delete jqLite.cache[firstElementToRemove[jqLite.expando]];
8886 // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after
8887 // the replaced element. The cleanData version monkey-patched by Angular would cause
8888 // the scope to be trashed and we do need the very same scope to work with the new
8889 // element. However, we cannot just cache the non-patched version and use it here as
8890 // that would break if another library patches the method after Angular does (one
8891 // example is jQuery UI). Instead, set a flag indicating scope destroying should be
8892 // skipped this one time.
8893 skipDestroyOnNextJQueryCleanData = true;
8894 jQuery.cleanData([firstElementToRemove]);
8898 for (var k = 1, kk = elementsToRemove.length; k < kk; k++) {
8899 var element = elementsToRemove[k];
8900 jqLite(element).remove(); // must do this way to clean up expando
8901 fragment.appendChild(element);
8902 delete elementsToRemove[k];
8905 elementsToRemove[0] = newNode;
8906 elementsToRemove.length = 1;
8910 function cloneAndAnnotateFn(fn, annotation) {
8911 return extend(function() { return fn.apply(null, arguments); }, fn, annotation);
8915 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) {
8917 linkFn(scope, $element, attrs, controllers, transcludeFn);
8919 $exceptionHandler(e, startingTag($element));
8924 // Set up $watches for isolate scope and controller bindings. This process
8925 // only occurs for isolate scopes and new scopes with controllerAs.
8926 function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
8927 var removeWatchCollection = [];
8928 forEach(bindings, function(definition, scopeName) {
8929 var attrName = definition.attrName,
8930 optional = definition.optional,
8931 mode = definition.mode, // @, =, or &
8933 parentGet, parentSet, compare;
8938 if (!optional && !hasOwnProperty.call(attrs, attrName)) {
8939 destination[scopeName] = attrs[attrName] = void 0;
8941 attrs.$observe(attrName, function(value) {
8942 if (isString(value)) {
8943 destination[scopeName] = value;
8946 attrs.$$observers[attrName].$$scope = scope;
8947 if (isString(attrs[attrName])) {
8948 // If the attribute has been provided then we trigger an interpolation to ensure
8949 // the value is there for use in the link fn
8950 destination[scopeName] = $interpolate(attrs[attrName])(scope);
8955 if (!hasOwnProperty.call(attrs, attrName)) {
8956 if (optional) break;
8957 attrs[attrName] = void 0;
8959 if (optional && !attrs[attrName]) break;
8961 parentGet = $parse(attrs[attrName]);
8962 if (parentGet.literal) {
8965 compare = function(a, b) { return a === b || (a !== a && b !== b); };
8967 parentSet = parentGet.assign || function() {
8968 // reset the change, or we will throw this exception on every $digest
8969 lastValue = destination[scopeName] = parentGet(scope);
8970 throw $compileMinErr('nonassign',
8971 "Expression '{0}' used with directive '{1}' is non-assignable!",
8972 attrs[attrName], directive.name);
8974 lastValue = destination[scopeName] = parentGet(scope);
8975 var parentValueWatch = function parentValueWatch(parentValue) {
8976 if (!compare(parentValue, destination[scopeName])) {
8977 // we are out of sync and need to copy
8978 if (!compare(parentValue, lastValue)) {
8979 // parent changed and it has precedence
8980 destination[scopeName] = parentValue;
8982 // if the parent can be assigned then do so
8983 parentSet(scope, parentValue = destination[scopeName]);
8986 return lastValue = parentValue;
8988 parentValueWatch.$stateful = true;
8990 if (definition.collection) {
8991 removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
8993 removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
8995 removeWatchCollection.push(removeWatch);
8999 // Don't assign Object.prototype method to scope
9000 parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;
9002 // Don't assign noop to destination if expression is not valid
9003 if (parentGet === noop && optional) break;
9005 destination[scopeName] = function(locals) {
9006 return parentGet(scope, locals);
9012 return removeWatchCollection.length && function removeWatches() {
9013 for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) {
9014 removeWatchCollection[i]();
9021 var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
9023 * Converts all accepted directives format into proper directive name.
9024 * @param name Name to normalize
9026 function directiveNormalize(name) {
9027 return camelCase(name.replace(PREFIX_REGEXP, ''));
9032 * @name $compile.directive.Attributes
9035 * A shared object between directive compile / linking functions which contains normalized DOM
9036 * element attributes. The values reflect current binding state `{{ }}`. The normalization is
9037 * needed since all of these are treated as equivalent in Angular:
9040 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
9046 * @name $compile.directive.Attributes#$attr
9049 * A map of DOM element attribute names to the normalized name. This is
9050 * needed to do reverse lookup from normalized name back to actual name.
9056 * @name $compile.directive.Attributes#$set
9060 * Set DOM element attribute value.
9063 * @param {string} name Normalized element attribute name of the property to modify. The name is
9064 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
9065 * property to the original name.
9066 * @param {string} value Value to set the attribute to. The value can be an interpolated string.
9072 * Closure compiler type information
9075 function nodesetLinkingFn(
9076 /* angular.Scope */ scope,
9077 /* NodeList */ nodeList,
9078 /* Element */ rootElement,
9079 /* function(Function) */ boundTranscludeFn
9082 function directiveLinkingFn(
9083 /* nodesetLinkingFn */ nodesetLinkingFn,
9084 /* angular.Scope */ scope,
9086 /* Element */ rootElement,
9087 /* function(Function) */ boundTranscludeFn
9090 function tokenDifference(str1, str2) {
9092 tokens1 = str1.split(/\s+/),
9093 tokens2 = str2.split(/\s+/);
9096 for (var i = 0; i < tokens1.length; i++) {
9097 var token = tokens1[i];
9098 for (var j = 0; j < tokens2.length; j++) {
9099 if (token == tokens2[j]) continue outer;
9101 values += (values.length > 0 ? ' ' : '') + token;
9106 function removeComments(jqNodes) {
9107 jqNodes = jqLite(jqNodes);
9108 var i = jqNodes.length;
9115 var node = jqNodes[i];
9116 if (node.nodeType === NODE_TYPE_COMMENT) {
9117 splice.call(jqNodes, i, 1);
9123 var $controllerMinErr = minErr('$controller');
9126 var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
9127 function identifierForController(controller, ident) {
9128 if (ident && isString(ident)) return ident;
9129 if (isString(controller)) {
9130 var match = CNTRL_REG.exec(controller);
9131 if (match) return match[3];
9138 * @name $controllerProvider
9140 * The {@link ng.$controller $controller service} is used by Angular to create new
9143 * This provider allows controller registration via the
9144 * {@link ng.$controllerProvider#register register} method.
9146 function $ControllerProvider() {
9147 var controllers = {},
9152 * @name $controllerProvider#register
9153 * @param {string|Object} name Controller name, or an object map of controllers where the keys are
9154 * the names and the values are the constructors.
9155 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI
9156 * annotations in the array notation).
9158 this.register = function(name, constructor) {
9159 assertNotHasOwnProperty(name, 'controller');
9160 if (isObject(name)) {
9161 extend(controllers, name);
9163 controllers[name] = constructor;
9169 * @name $controllerProvider#allowGlobals
9170 * @description If called, allows `$controller` to find controller constructors on `window`
9172 this.allowGlobals = function() {
9177 this.$get = ['$injector', '$window', function($injector, $window) {
9182 * @requires $injector
9184 * @param {Function|string} constructor If called with a function then it's considered to be the
9185 * controller constructor function. Otherwise it's considered to be a string which is used
9186 * to retrieve the controller constructor using the following steps:
9188 * * check if a controller with given name is registered via `$controllerProvider`
9189 * * check if evaluating the string on the current scope returns a constructor
9190 * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
9191 * `window` object (not recommended)
9193 * The string can use the `controller as property` syntax, where the controller instance is published
9194 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this
9195 * to work correctly.
9197 * @param {Object} locals Injection locals for Controller.
9198 * @return {Object} Instance of given controller.
9201 * `$controller` service is responsible for instantiating controllers.
9203 * It's just a simple call to {@link auto.$injector $injector}, but extracted into
9204 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788).
9206 return function(expression, locals, later, ident) {
9208 // param `later` --- indicates that the controller's constructor is invoked at a later time.
9209 // If true, $controller will allocate the object with the correct
9210 // prototype chain, but will not invoke the controller until a returned
9211 // callback is invoked.
9212 // param `ident` --- An optional label which overrides the label parsed from the controller
9213 // expression, if any.
9214 var instance, match, constructor, identifier;
9215 later = later === true;
9216 if (ident && isString(ident)) {
9220 if (isString(expression)) {
9221 match = expression.match(CNTRL_REG);
9223 throw $controllerMinErr('ctrlfmt',
9224 "Badly formed controller string '{0}'. " +
9225 "Must match `__name__ as __id__` or `__name__`.", expression);
9227 constructor = match[1],
9228 identifier = identifier || match[3];
9229 expression = controllers.hasOwnProperty(constructor)
9230 ? controllers[constructor]
9231 : getter(locals.$scope, constructor, true) ||
9232 (globals ? getter($window, constructor, true) : undefined);
9234 assertArgFn(expression, constructor, true);
9238 // Instantiate controller later:
9239 // This machinery is used to create an instance of the object before calling the
9240 // controller's constructor itself.
9242 // This allows properties to be added to the controller before the constructor is
9243 // invoked. Primarily, this is used for isolate scope bindings in $compile.
9245 // This feature is not intended for use by applications, and is thus not documented
9247 // Object creation: http://jsperf.com/create-constructor/2
9248 var controllerPrototype = (isArray(expression) ?
9249 expression[expression.length - 1] : expression).prototype;
9250 instance = Object.create(controllerPrototype || null);
9253 addIdentifier(locals, identifier, instance, constructor || expression.name);
9257 return instantiate = extend(function() {
9258 var result = $injector.invoke(expression, instance, locals, constructor);
9259 if (result !== instance && (isObject(result) || isFunction(result))) {
9262 // If result changed, re-assign controllerAs value to scope.
9263 addIdentifier(locals, identifier, instance, constructor || expression.name);
9269 identifier: identifier
9273 instance = $injector.instantiate(expression, locals, constructor);
9276 addIdentifier(locals, identifier, instance, constructor || expression.name);
9282 function addIdentifier(locals, identifier, instance, name) {
9283 if (!(locals && isObject(locals.$scope))) {
9284 throw minErr('$controller')('noscp',
9285 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.",
9289 locals.$scope[identifier] = instance;
9300 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object.
9303 <example module="documentExample">
9304 <file name="index.html">
9305 <div ng-controller="ExampleController">
9306 <p>$document title: <b ng-bind="title"></b></p>
9307 <p>window.document title: <b ng-bind="windowTitle"></b></p>
9310 <file name="script.js">
9311 angular.module('documentExample', [])
9312 .controller('ExampleController', ['$scope', '$document', function($scope, $document) {
9313 $scope.title = $document[0].title;
9314 $scope.windowTitle = angular.element(window.document)[0].title;
9319 function $DocumentProvider() {
9320 this.$get = ['$window', function(window) {
9321 return jqLite(window.document);
9327 * @name $exceptionHandler
9331 * Any uncaught exception in angular expressions is delegated to this service.
9332 * The default implementation simply delegates to `$log.error` which logs it into
9333 * the browser console.
9335 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by
9336 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing.
9341 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() {
9342 * return function(exception, cause) {
9343 * exception.message += ' (caused by "' + cause + '")';
9349 * This example will override the normal action of `$exceptionHandler`, to make angular
9350 * exceptions fail hard when they happen, instead of just logging to the console.
9353 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind`
9354 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler}
9355 * (unless executed during a digest).
9357 * If you wish, you can manually delegate exceptions, e.g.
9358 * `try { ... } catch(e) { $exceptionHandler(e); }`
9360 * @param {Error} exception Exception associated with the error.
9361 * @param {string=} cause optional information about the context in which
9362 * the error was thrown.
9365 function $ExceptionHandlerProvider() {
9366 this.$get = ['$log', function($log) {
9367 return function(exception, cause) {
9368 $log.error.apply($log, arguments);
9373 var $$ForceReflowProvider = function() {
9374 this.$get = ['$document', function($document) {
9375 return function(domNode) {
9376 //the line below will force the browser to perform a repaint so
9377 //that all the animated elements within the animation frame will
9378 //be properly updated and drawn on screen. This is required to
9379 //ensure that the preparation animation is properly flushed so that
9380 //the active state picks up from there. DO NOT REMOVE THIS LINE.
9381 //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH
9382 //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND
9383 //WILL TAKE YEARS AWAY FROM YOUR LIFE.
9385 if (!domNode.nodeType && domNode instanceof jqLite) {
9386 domNode = domNode[0];
9389 domNode = $document[0].body;
9391 return domNode.offsetWidth + 1;
9396 var APPLICATION_JSON = 'application/json';
9397 var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
9398 var JSON_START = /^\[|^\{(?!\{)/;
9403 var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
9404 var $httpMinErr = minErr('$http');
9405 var $httpMinErrLegacyFn = function(method) {
9407 throw $httpMinErr('legacy', 'The method `{0}` on the promise returned from `$http` has been disabled.', method);
9411 function serializeValue(v) {
9413 return isDate(v) ? v.toISOString() : toJson(v);
9419 function $HttpParamSerializerProvider() {
9422 * @name $httpParamSerializer
9425 * Default {@link $http `$http`} params serializer that converts objects to strings
9426 * according to the following rules:
9428 * * `{'foo': 'bar'}` results in `foo=bar`
9429 * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object)
9430 * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element)
9431 * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object)
9433 * Note that serializer will sort the request parameters alphabetically.
9436 this.$get = function() {
9437 return function ngParamSerializer(params) {
9438 if (!params) return '';
9440 forEachSorted(params, function(value, key) {
9441 if (value === null || isUndefined(value)) return;
9442 if (isArray(value)) {
9443 forEach(value, function(v, k) {
9444 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v)));
9447 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
9451 return parts.join('&');
9456 function $HttpParamSerializerJQLikeProvider() {
9459 * @name $httpParamSerializerJQLike
9462 * Alternative {@link $http `$http`} params serializer that follows
9463 * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic.
9464 * The serializer will also sort the params alphabetically.
9466 * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property:
9473 * paramSerializer: '$httpParamSerializerJQLike'
9477 * It is also possible to set it as the default `paramSerializer` in the
9478 * {@link $httpProvider#defaults `$httpProvider`}.
9480 * Additionally, you can inject the serializer and use it explicitly, for example to serialize
9481 * form data for submission:
9484 * .controller(function($http, $httpParamSerializerJQLike) {
9490 * data: $httpParamSerializerJQLike(myData),
9492 * 'Content-Type': 'application/x-www-form-urlencoded'
9500 this.$get = function() {
9501 return function jQueryLikeParamSerializer(params) {
9502 if (!params) return '';
9504 serialize(params, '', true);
9505 return parts.join('&');
9507 function serialize(toSerialize, prefix, topLevel) {
9508 if (toSerialize === null || isUndefined(toSerialize)) return;
9509 if (isArray(toSerialize)) {
9510 forEach(toSerialize, function(value, index) {
9511 serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']');
9513 } else if (isObject(toSerialize) && !isDate(toSerialize)) {
9514 forEachSorted(toSerialize, function(value, key) {
9515 serialize(value, prefix +
9516 (topLevel ? '' : '[') +
9518 (topLevel ? '' : ']'));
9521 parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize)));
9528 function defaultHttpResponseTransform(data, headers) {
9529 if (isString(data)) {
9530 // Strip json vulnerability protection prefix and trim whitespace
9531 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim();
9534 var contentType = headers('Content-Type');
9535 if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) {
9536 data = fromJson(tempData);
9544 function isJsonLike(str) {
9545 var jsonStart = str.match(JSON_START);
9546 return jsonStart && JSON_ENDS[jsonStart[0]].test(str);
9550 * Parse headers into key value object
9552 * @param {string} headers Raw headers as a string
9553 * @returns {Object} Parsed headers as key value object
9555 function parseHeaders(headers) {
9556 var parsed = createMap(), i;
9558 function fillInParsed(key, val) {
9560 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
9564 if (isString(headers)) {
9565 forEach(headers.split('\n'), function(line) {
9566 i = line.indexOf(':');
9567 fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1)));
9569 } else if (isObject(headers)) {
9570 forEach(headers, function(headerVal, headerKey) {
9571 fillInParsed(lowercase(headerKey), trim(headerVal));
9580 * Returns a function that provides access to parsed headers.
9582 * Headers are lazy parsed when first requested.
9585 * @param {(string|Object)} headers Headers to provide access to.
9586 * @returns {function(string=)} Returns a getter function which if called with:
9588 * - if called with single an argument returns a single header value or null
9589 * - if called with no arguments returns an object containing all headers.
9591 function headersGetter(headers) {
9594 return function(name) {
9595 if (!headersObj) headersObj = parseHeaders(headers);
9598 var value = headersObj[lowercase(name)];
9599 if (value === void 0) {
9611 * Chain all given functions
9613 * This function is used for both request and response transforming
9615 * @param {*} data Data to transform.
9616 * @param {function(string=)} headers HTTP headers getter fn.
9617 * @param {number} status HTTP status code of the response.
9618 * @param {(Function|Array.<Function>)} fns Function or an array of functions.
9619 * @returns {*} Transformed data.
9621 function transformData(data, headers, status, fns) {
9622 if (isFunction(fns)) {
9623 return fns(data, headers, status);
9626 forEach(fns, function(fn) {
9627 data = fn(data, headers, status);
9634 function isSuccess(status) {
9635 return 200 <= status && status < 300;
9641 * @name $httpProvider
9643 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service.
9645 function $HttpProvider() {
9648 * @name $httpProvider#defaults
9651 * Object containing default values for all {@link ng.$http $http} requests.
9653 * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`}
9654 * that will provide the cache for all requests who set their `cache` property to `true`.
9655 * If you set the `defaults.cache = false` then only requests that specify their own custom
9656 * cache object will be cached. See {@link $http#caching $http Caching} for more information.
9658 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token.
9659 * Defaults value is `'XSRF-TOKEN'`.
9661 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the
9662 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`.
9664 * - **`defaults.headers`** - {Object} - Default headers for all $http requests.
9665 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on
9666 * setting default headers.
9667 * - **`defaults.headers.common`**
9668 * - **`defaults.headers.post`**
9669 * - **`defaults.headers.put`**
9670 * - **`defaults.headers.patch`**
9673 * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function
9674 * used to the prepare string representation of request parameters (specified as an object).
9675 * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}.
9676 * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}.
9679 var defaults = this.defaults = {
9680 // transform incoming response data
9681 transformResponse: [defaultHttpResponseTransform],
9683 // transform outgoing request data
9684 transformRequest: [function(d) {
9685 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d;
9691 'Accept': 'application/json, text/plain, */*'
9693 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
9694 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
9695 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON)
9698 xsrfCookieName: 'XSRF-TOKEN',
9699 xsrfHeaderName: 'X-XSRF-TOKEN',
9701 paramSerializer: '$httpParamSerializer'
9704 var useApplyAsync = false;
9707 * @name $httpProvider#useApplyAsync
9710 * Configure $http service to combine processing of multiple http responses received at around
9711 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in
9712 * significant performance improvement for bigger applications that make many HTTP requests
9713 * concurrently (common during application bootstrap).
9715 * Defaults to false. If no value is specified, returns the current configured value.
9717 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred
9718 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window
9719 * to load and share the same digest cycle.
9721 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
9722 * otherwise, returns the current configured value.
9724 this.useApplyAsync = function(value) {
9725 if (isDefined(value)) {
9726 useApplyAsync = !!value;
9729 return useApplyAsync;
9732 var useLegacyPromise = true;
9735 * @name $httpProvider#useLegacyPromiseExtensions
9738 * Configure `$http` service to return promises without the shorthand methods `success` and `error`.
9739 * This should be used to make sure that applications work without these methods.
9741 * Defaults to true. If no value is specified, returns the current configured value.
9743 * @param {boolean=} value If true, `$http` will return a promise with the deprecated legacy `success` and `error` methods.
9745 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
9746 * otherwise, returns the current configured value.
9748 this.useLegacyPromiseExtensions = function(value) {
9749 if (isDefined(value)) {
9750 useLegacyPromise = !!value;
9753 return useLegacyPromise;
9758 * @name $httpProvider#interceptors
9761 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http}
9762 * pre-processing of request or postprocessing of responses.
9764 * These service factories are ordered by request, i.e. they are applied in the same order as the
9765 * array, on request, but reverse order, on response.
9767 * {@link ng.$http#interceptors Interceptors detailed info}
9769 var interceptorFactories = this.interceptors = [];
9771 this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector',
9772 function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) {
9774 var defaultCache = $cacheFactory('$http');
9777 * Make sure that default param serializer is exposed as a function
9779 defaults.paramSerializer = isString(defaults.paramSerializer) ?
9780 $injector.get(defaults.paramSerializer) : defaults.paramSerializer;
9783 * Interceptors stored in reverse order. Inner interceptors before outer interceptors.
9784 * The reversal is needed so that we can build up the interception chain around the
9787 var reversedInterceptors = [];
9789 forEach(interceptorFactories, function(interceptorFactory) {
9790 reversedInterceptors.unshift(isString(interceptorFactory)
9791 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory));
9798 * @requires ng.$httpBackend
9799 * @requires $cacheFactory
9800 * @requires $rootScope
9802 * @requires $injector
9805 * The `$http` service is a core Angular service that facilitates communication with the remote
9806 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest)
9807 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP).
9809 * For unit testing applications that use `$http` service, see
9810 * {@link ngMock.$httpBackend $httpBackend mock}.
9812 * For a higher level of abstraction, please check out the {@link ngResource.$resource
9813 * $resource} service.
9815 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by
9816 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage
9817 * it is important to familiarize yourself with these APIs and the guarantees they provide.
9821 * The `$http` service is a function which takes a single argument — a {@link $http#usage configuration object} —
9822 * that is used to generate an HTTP request and returns a {@link ng.$q promise}.
9825 * // Simple GET request example:
9829 * }).then(function successCallback(response) {
9830 * // this callback will be called asynchronously
9831 * // when the response is available
9832 * }, function errorCallback(response) {
9833 * // called asynchronously if an error occurs
9834 * // or server returns response with an error status.
9838 * The response object has these properties:
9840 * - **data** – `{string|Object}` – The response body transformed with the transform
9842 * - **status** – `{number}` – HTTP status code of the response.
9843 * - **headers** – `{function([headerName])}` – Header getter function.
9844 * - **config** – `{Object}` – The configuration object that was used to generate the request.
9845 * - **statusText** – `{string}` – HTTP status text of the response.
9847 * A response status code between 200 and 299 is considered a success status and
9848 * will result in the success callback being called. Note that if the response is a redirect,
9849 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be
9850 * called for such responses.
9853 * ## Shortcut methods
9855 * Shortcut methods are also available. All shortcut methods require passing in the URL, and
9856 * request data must be passed in for POST/PUT requests. An optional config can be passed as the
9860 * $http.get('/someUrl', config).then(successCallback, errorCallback);
9861 * $http.post('/someUrl', data, config).then(successCallback, errorCallback);
9864 * Complete list of shortcut methods:
9866 * - {@link ng.$http#get $http.get}
9867 * - {@link ng.$http#head $http.head}
9868 * - {@link ng.$http#post $http.post}
9869 * - {@link ng.$http#put $http.put}
9870 * - {@link ng.$http#delete $http.delete}
9871 * - {@link ng.$http#jsonp $http.jsonp}
9872 * - {@link ng.$http#patch $http.patch}
9875 * ## Writing Unit Tests that use $http
9876 * When unit testing (using {@link ngMock ngMock}), it is necessary to call
9877 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending
9878 * request using trained responses.
9881 * $httpBackend.expectGET(...);
9883 * $httpBackend.flush();
9886 * ## Deprecation Notice
9887 * <div class="alert alert-danger">
9888 * The `$http` legacy promise methods `success` and `error` have been deprecated.
9889 * Use the standard `then` method instead.
9890 * If {@link $httpProvider#useLegacyPromiseExtensions `$httpProvider.useLegacyPromiseExtensions`} is set to
9891 * `false` then these methods will throw {@link $http:legacy `$http/legacy`} error.
9894 * ## Setting HTTP Headers
9896 * The $http service will automatically add certain HTTP headers to all requests. These defaults
9897 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration
9898 * object, which currently contains this default configuration:
9900 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
9901 * - `Accept: application/json, text/plain, * / *`
9902 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests)
9903 * - `Content-Type: application/json`
9904 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests)
9905 * - `Content-Type: application/json`
9907 * To add or overwrite these defaults, simply add or remove a property from these configuration
9908 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
9909 * with the lowercased HTTP method name as the key, e.g.
9910 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`.
9912 * The defaults can also be set at runtime via the `$http.defaults` object in the same
9913 * fashion. For example:
9916 * module.run(function($http) {
9917 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'
9921 * In addition, you can supply a `headers` property in the config object passed when
9922 * calling `$http(config)`, which overrides the defaults without changing them globally.
9924 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis,
9925 * Use the `headers` property, setting the desired header to `undefined`. For example:
9930 * url: 'http://example.com',
9932 * 'Content-Type': undefined
9934 * data: { test: 'test' }
9937 * $http(req).then(function(){...}, function(){...});
9940 * ## Transforming Requests and Responses
9942 * Both requests and responses can be transformed using transformation functions: `transformRequest`
9943 * and `transformResponse`. These properties can be a single function that returns
9944 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions,
9945 * which allows you to `push` or `unshift` a new transformation function into the transformation chain.
9947 * ### Default Transformations
9949 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and
9950 * `defaults.transformResponse` properties. If a request does not provide its own transformations
9951 * then these will be applied.
9953 * You can augment or replace the default transformations by modifying these properties by adding to or
9954 * replacing the array.
9956 * Angular provides the following default transformations:
9958 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`):
9960 * - If the `data` property of the request configuration object contains an object, serialize it
9963 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`):
9965 * - If XSRF prefix is detected, strip it (see Security Considerations section below).
9966 * - If JSON response is detected, deserialize it using a JSON parser.
9969 * ### Overriding the Default Transformations Per Request
9971 * If you wish override the request/response transformations only for a single request then provide
9972 * `transformRequest` and/or `transformResponse` properties on the configuration object passed
9975 * Note that if you provide these properties on the config object the default transformations will be
9976 * overwritten. If you wish to augment the default transformations then you must include them in your
9977 * local transformation array.
9979 * The following code demonstrates adding a new response transformation to be run after the default response
9980 * transformations have been run.
9983 * function appendTransform(defaults, transform) {
9985 * // We can't guarantee that the default transformation is an array
9986 * defaults = angular.isArray(defaults) ? defaults : [defaults];
9988 * // Append the new transformation to the defaults
9989 * return defaults.concat(transform);
9995 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) {
9996 * return doTransform(value);
10004 * To enable caching, set the request configuration `cache` property to `true` (to use default
10005 * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}).
10006 * When the cache is enabled, `$http` stores the response from the server in the specified
10007 * cache. The next time the same request is made, the response is served from the cache without
10008 * sending a request to the server.
10010 * Note that even if the response is served from cache, delivery of the data is asynchronous in
10011 * the same way that real requests are.
10013 * If there are multiple GET requests for the same URL that should be cached using the same
10014 * cache, but the cache is not populated yet, only one request to the server will be made and
10015 * the remaining requests will be fulfilled using the response from the first request.
10017 * You can change the default cache to a new object (built with
10018 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the
10019 * {@link ng.$http#defaults `$http.defaults.cache`} property. All requests who set
10020 * their `cache` property to `true` will now use this cache object.
10022 * If you set the default cache to `false` then only requests that specify their own custom
10023 * cache object will be cached.
10027 * Before you start creating interceptors, be sure to understand the
10028 * {@link ng.$q $q and deferred/promise APIs}.
10030 * For purposes of global error handling, authentication, or any kind of synchronous or
10031 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be
10032 * able to intercept requests before they are handed to the server and
10033 * responses before they are handed over to the application code that
10034 * initiated these requests. The interceptors leverage the {@link ng.$q
10035 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing.
10037 * The interceptors are service factories that are registered with the `$httpProvider` by
10038 * adding them to the `$httpProvider.interceptors` array. The factory is called and
10039 * injected with dependencies (if specified) and returns the interceptor.
10041 * There are two kinds of interceptors (and two kinds of rejection interceptors):
10043 * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to
10044 * modify the `config` object or create a new one. The function needs to return the `config`
10045 * object directly, or a promise containing the `config` or a new `config` object.
10046 * * `requestError`: interceptor gets called when a previous interceptor threw an error or
10047 * resolved with a rejection.
10048 * * `response`: interceptors get called with http `response` object. The function is free to
10049 * modify the `response` object or create a new one. The function needs to return the `response`
10050 * object directly, or as a promise containing the `response` or a new `response` object.
10051 * * `responseError`: interceptor gets called when a previous interceptor threw an error or
10052 * resolved with a rejection.
10056 * // register the interceptor as a service
10057 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
10059 * // optional method
10060 * 'request': function(config) {
10061 * // do something on success
10065 * // optional method
10066 * 'requestError': function(rejection) {
10067 * // do something on error
10068 * if (canRecover(rejection)) {
10069 * return responseOrNewPromise
10071 * return $q.reject(rejection);
10076 * // optional method
10077 * 'response': function(response) {
10078 * // do something on success
10082 * // optional method
10083 * 'responseError': function(rejection) {
10084 * // do something on error
10085 * if (canRecover(rejection)) {
10086 * return responseOrNewPromise
10088 * return $q.reject(rejection);
10093 * $httpProvider.interceptors.push('myHttpInterceptor');
10096 * // alternatively, register the interceptor via an anonymous factory
10097 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) {
10099 * 'request': function(config) {
10103 * 'response': function(response) {
10110 * ## Security Considerations
10112 * When designing web applications, consider security threats from:
10114 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
10115 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery)
10117 * Both server and the client must cooperate in order to eliminate these threats. Angular comes
10118 * pre-configured with strategies that address these issues, but for this to work backend server
10119 * cooperation is required.
10121 * ### JSON Vulnerability Protection
10123 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
10124 * allows third party website to turn your JSON resource URL into
10125 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To
10126 * counter this your server can prefix all JSON requests with following string `")]}',\n"`.
10127 * Angular will automatically strip the prefix before processing it as JSON.
10129 * For example if your server needs to return:
10134 * which is vulnerable to attack, your server can return:
10140 * Angular will strip the prefix, before processing the JSON.
10143 * ### Cross Site Request Forgery (XSRF) Protection
10145 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which
10146 * an unauthorized site can gain your user's private data. Angular provides a mechanism
10147 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie
10148 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only
10149 * JavaScript that runs on your domain could read the cookie, your server can be assured that
10150 * the XHR came from JavaScript running on your domain. The header will not be set for
10151 * cross-domain requests.
10153 * To take advantage of this, your server needs to set a token in a JavaScript readable session
10154 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the
10155 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure
10156 * that only JavaScript running on your domain could have sent the request. The token must be
10157 * unique for each user and must be verifiable by the server (to prevent the JavaScript from
10158 * making up its own tokens). We recommend that the token is a digest of your site's
10159 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography))
10160 * for added security.
10162 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName
10163 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time,
10164 * or the per-request config object.
10166 * In order to prevent collisions in environments where multiple Angular apps share the
10167 * same domain or subdomain, we recommend that each application uses unique cookie name.
10169 * @param {object} config Object describing the request to be made and how it should be
10170 * processed. The object has following properties:
10172 * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
10173 * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested.
10174 * - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be serialized
10175 * with the `paramSerializer` and appended as GET parameters.
10176 * - **data** – `{string|Object}` – Data to be sent as the request message data.
10177 * - **headers** – `{Object}` – Map of strings or functions which return strings representing
10178 * HTTP headers to send to the server. If the return value of a function is null, the
10179 * header will not be sent. Functions accept a config object as an argument.
10180 * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token.
10181 * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token.
10182 * - **transformRequest** –
10183 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
10184 * transform function or an array of such functions. The transform function takes the http
10185 * request body and headers and returns its transformed (typically serialized) version.
10186 * See {@link ng.$http#overriding-the-default-transformations-per-request
10187 * Overriding the Default Transformations}
10188 * - **transformResponse** –
10189 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` –
10190 * transform function or an array of such functions. The transform function takes the http
10191 * response body, headers and status and returns its transformed (typically deserialized) version.
10192 * See {@link ng.$http#overriding-the-default-transformations-per-request
10193 * Overriding the Default TransformationjqLiks}
10194 * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to
10195 * prepare the string representation of request parameters (specified as an object).
10196 * If specified as string, it is interpreted as function registered with the
10197 * {@link $injector $injector}, which means you can create your own serializer
10198 * by registering it as a {@link auto.$provide#service service}.
10199 * The default serializer is the {@link $httpParamSerializer $httpParamSerializer};
10200 * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike}
10201 * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
10202 * GET request, otherwise if a cache instance built with
10203 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
10205 * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise}
10206 * that should abort the request when resolved.
10207 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the
10208 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials)
10209 * for more information.
10210 * - **responseType** - `{string}` - see
10211 * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype).
10213 * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object
10214 * when the request succeeds or fails.
10217 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending
10218 * requests. This is primarily meant to be used for debugging purposes.
10222 <example module="httpExample">
10223 <file name="index.html">
10224 <div ng-controller="FetchController">
10225 <select ng-model="method" aria-label="Request method">
10226 <option>GET</option>
10227 <option>JSONP</option>
10229 <input type="text" ng-model="url" size="80" aria-label="URL" />
10230 <button id="fetchbtn" ng-click="fetch()">fetch</button><br>
10231 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
10232 <button id="samplejsonpbtn"
10233 ng-click="updateModel('JSONP',
10234 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')">
10237 <button id="invalidjsonpbtn"
10238 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')">
10241 <pre>http status code: {{status}}</pre>
10242 <pre>http response data: {{data}}</pre>
10245 <file name="script.js">
10246 angular.module('httpExample', [])
10247 .controller('FetchController', ['$scope', '$http', '$templateCache',
10248 function($scope, $http, $templateCache) {
10249 $scope.method = 'GET';
10250 $scope.url = 'http-hello.html';
10252 $scope.fetch = function() {
10253 $scope.code = null;
10254 $scope.response = null;
10256 $http({method: $scope.method, url: $scope.url, cache: $templateCache}).
10257 then(function(response) {
10258 $scope.status = response.status;
10259 $scope.data = response.data;
10260 }, function(response) {
10261 $scope.data = response.data || "Request failed";
10262 $scope.status = response.status;
10266 $scope.updateModel = function(method, url) {
10267 $scope.method = method;
10272 <file name="http-hello.html">
10275 <file name="protractor.js" type="protractor">
10276 var status = element(by.binding('status'));
10277 var data = element(by.binding('data'));
10278 var fetchBtn = element(by.id('fetchbtn'));
10279 var sampleGetBtn = element(by.id('samplegetbtn'));
10280 var sampleJsonpBtn = element(by.id('samplejsonpbtn'));
10281 var invalidJsonpBtn = element(by.id('invalidjsonpbtn'));
10283 it('should make an xhr GET request', function() {
10284 sampleGetBtn.click();
10286 expect(status.getText()).toMatch('200');
10287 expect(data.getText()).toMatch(/Hello, \$http!/);
10290 // Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185
10291 // it('should make a JSONP request to angularjs.org', function() {
10292 // sampleJsonpBtn.click();
10293 // fetchBtn.click();
10294 // expect(status.getText()).toMatch('200');
10295 // expect(data.getText()).toMatch(/Super Hero!/);
10298 it('should make JSONP request to invalid URL and invoke the error handler',
10300 invalidJsonpBtn.click();
10302 expect(status.getText()).toMatch('0');
10303 expect(data.getText()).toMatch('Request failed');
10308 function $http(requestConfig) {
10310 if (!angular.isObject(requestConfig)) {
10311 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig);
10314 var config = extend({
10316 transformRequest: defaults.transformRequest,
10317 transformResponse: defaults.transformResponse,
10318 paramSerializer: defaults.paramSerializer
10321 config.headers = mergeHeaders(requestConfig);
10322 config.method = uppercase(config.method);
10323 config.paramSerializer = isString(config.paramSerializer) ?
10324 $injector.get(config.paramSerializer) : config.paramSerializer;
10326 var serverRequest = function(config) {
10327 var headers = config.headers;
10328 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest);
10330 // strip content-type if data is undefined
10331 if (isUndefined(reqData)) {
10332 forEach(headers, function(value, header) {
10333 if (lowercase(header) === 'content-type') {
10334 delete headers[header];
10339 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
10340 config.withCredentials = defaults.withCredentials;
10344 return sendReq(config, reqData).then(transformResponse, transformResponse);
10347 var chain = [serverRequest, undefined];
10348 var promise = $q.when(config);
10350 // apply interceptors
10351 forEach(reversedInterceptors, function(interceptor) {
10352 if (interceptor.request || interceptor.requestError) {
10353 chain.unshift(interceptor.request, interceptor.requestError);
10355 if (interceptor.response || interceptor.responseError) {
10356 chain.push(interceptor.response, interceptor.responseError);
10360 while (chain.length) {
10361 var thenFn = chain.shift();
10362 var rejectFn = chain.shift();
10364 promise = promise.then(thenFn, rejectFn);
10367 if (useLegacyPromise) {
10368 promise.success = function(fn) {
10369 assertArgFn(fn, 'fn');
10371 promise.then(function(response) {
10372 fn(response.data, response.status, response.headers, config);
10377 promise.error = function(fn) {
10378 assertArgFn(fn, 'fn');
10380 promise.then(null, function(response) {
10381 fn(response.data, response.status, response.headers, config);
10386 promise.success = $httpMinErrLegacyFn('success');
10387 promise.error = $httpMinErrLegacyFn('error');
10392 function transformResponse(response) {
10393 // make a copy since the response must be cacheable
10394 var resp = extend({}, response);
10395 resp.data = transformData(response.data, response.headers, response.status,
10396 config.transformResponse);
10397 return (isSuccess(response.status))
10402 function executeHeaderFns(headers, config) {
10403 var headerContent, processedHeaders = {};
10405 forEach(headers, function(headerFn, header) {
10406 if (isFunction(headerFn)) {
10407 headerContent = headerFn(config);
10408 if (headerContent != null) {
10409 processedHeaders[header] = headerContent;
10412 processedHeaders[header] = headerFn;
10416 return processedHeaders;
10419 function mergeHeaders(config) {
10420 var defHeaders = defaults.headers,
10421 reqHeaders = extend({}, config.headers),
10422 defHeaderName, lowercaseDefHeaderName, reqHeaderName;
10424 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]);
10426 // using for-in instead of forEach to avoid unecessary iteration after header has been found
10427 defaultHeadersIteration:
10428 for (defHeaderName in defHeaders) {
10429 lowercaseDefHeaderName = lowercase(defHeaderName);
10431 for (reqHeaderName in reqHeaders) {
10432 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) {
10433 continue defaultHeadersIteration;
10437 reqHeaders[defHeaderName] = defHeaders[defHeaderName];
10440 // execute if header value is a function for merged headers
10441 return executeHeaderFns(reqHeaders, shallowCopy(config));
10445 $http.pendingRequests = [];
10452 * Shortcut method to perform `GET` request.
10454 * @param {string} url Relative or absolute URL specifying the destination of the request
10455 * @param {Object=} config Optional configuration object
10456 * @returns {HttpPromise} Future object
10461 * @name $http#delete
10464 * Shortcut method to perform `DELETE` request.
10466 * @param {string} url Relative or absolute URL specifying the destination of the request
10467 * @param {Object=} config Optional configuration object
10468 * @returns {HttpPromise} Future object
10476 * Shortcut method to perform `HEAD` request.
10478 * @param {string} url Relative or absolute URL specifying the destination of the request
10479 * @param {Object=} config Optional configuration object
10480 * @returns {HttpPromise} Future object
10485 * @name $http#jsonp
10488 * Shortcut method to perform `JSONP` request.
10490 * @param {string} url Relative or absolute URL specifying the destination of the request.
10491 * The name of the callback should be the string `JSON_CALLBACK`.
10492 * @param {Object=} config Optional configuration object
10493 * @returns {HttpPromise} Future object
10495 createShortMethods('get', 'delete', 'head', 'jsonp');
10502 * Shortcut method to perform `POST` request.
10504 * @param {string} url Relative or absolute URL specifying the destination of the request
10505 * @param {*} data Request content
10506 * @param {Object=} config Optional configuration object
10507 * @returns {HttpPromise} Future object
10515 * Shortcut method to perform `PUT` request.
10517 * @param {string} url Relative or absolute URL specifying the destination of the request
10518 * @param {*} data Request content
10519 * @param {Object=} config Optional configuration object
10520 * @returns {HttpPromise} Future object
10525 * @name $http#patch
10528 * Shortcut method to perform `PATCH` request.
10530 * @param {string} url Relative or absolute URL specifying the destination of the request
10531 * @param {*} data Request content
10532 * @param {Object=} config Optional configuration object
10533 * @returns {HttpPromise} Future object
10535 createShortMethodsWithData('post', 'put', 'patch');
10539 * @name $http#defaults
10542 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
10543 * default headers, withCredentials as well as request and response transformations.
10545 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
10547 $http.defaults = defaults;
10553 function createShortMethods(names) {
10554 forEach(arguments, function(name) {
10555 $http[name] = function(url, config) {
10556 return $http(extend({}, config || {}, {
10565 function createShortMethodsWithData(name) {
10566 forEach(arguments, function(name) {
10567 $http[name] = function(url, data, config) {
10568 return $http(extend({}, config || {}, {
10579 * Makes the request.
10581 * !!! ACCESSES CLOSURE VARS:
10582 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests
10584 function sendReq(config, reqData) {
10585 var deferred = $q.defer(),
10586 promise = deferred.promise,
10589 reqHeaders = config.headers,
10590 url = buildUrl(config.url, config.paramSerializer(config.params));
10592 $http.pendingRequests.push(config);
10593 promise.then(removePendingReq, removePendingReq);
10596 if ((config.cache || defaults.cache) && config.cache !== false &&
10597 (config.method === 'GET' || config.method === 'JSONP')) {
10598 cache = isObject(config.cache) ? config.cache
10599 : isObject(defaults.cache) ? defaults.cache
10604 cachedResp = cache.get(url);
10605 if (isDefined(cachedResp)) {
10606 if (isPromiseLike(cachedResp)) {
10607 // cached request has already been sent, but there is no response yet
10608 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult);
10610 // serving from cache
10611 if (isArray(cachedResp)) {
10612 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]);
10614 resolvePromise(cachedResp, 200, {}, 'OK');
10618 // put the promise for the non-transformed response into cache as a placeholder
10619 cache.put(url, promise);
10624 // if we won't have the response in cache, set the xsrf headers and
10625 // send the request to the backend
10626 if (isUndefined(cachedResp)) {
10627 var xsrfValue = urlIsSameOrigin(config.url)
10628 ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName]
10631 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
10634 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
10635 config.withCredentials, config.responseType);
10642 * Callback registered to $httpBackend():
10643 * - caches the response if desired
10644 * - resolves the raw $http promise
10647 function done(status, response, headersString, statusText) {
10649 if (isSuccess(status)) {
10650 cache.put(url, [status, response, parseHeaders(headersString), statusText]);
10652 // remove promise from the cache
10657 function resolveHttpPromise() {
10658 resolvePromise(response, status, headersString, statusText);
10661 if (useApplyAsync) {
10662 $rootScope.$applyAsync(resolveHttpPromise);
10664 resolveHttpPromise();
10665 if (!$rootScope.$$phase) $rootScope.$apply();
10671 * Resolves the raw $http promise.
10673 function resolvePromise(response, status, headers, statusText) {
10674 //status: HTTP response status code, 0, -1 (aborted by timeout / promise)
10675 status = status >= -1 ? status : 0;
10677 (isSuccess(status) ? deferred.resolve : deferred.reject)({
10680 headers: headersGetter(headers),
10682 statusText: statusText
10686 function resolvePromiseWithResult(result) {
10687 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText);
10690 function removePendingReq() {
10691 var idx = $http.pendingRequests.indexOf(config);
10692 if (idx !== -1) $http.pendingRequests.splice(idx, 1);
10697 function buildUrl(url, serializedParams) {
10698 if (serializedParams.length > 0) {
10699 url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams;
10708 * @name $xhrFactory
10711 * Factory function used to create XMLHttpRequest objects.
10713 * Replace or decorate this service to create your own custom XMLHttpRequest objects.
10716 * angular.module('myApp', [])
10717 * .factory('$xhrFactory', function() {
10718 * return function createXhr(method, url) {
10719 * return new window.XMLHttpRequest({mozSystem: true});
10724 * @param {string} method HTTP method of the request (GET, POST, PUT, ..)
10725 * @param {string} url URL of the request.
10727 function $xhrFactoryProvider() {
10728 this.$get = function() {
10729 return function createXhr() {
10730 return new window.XMLHttpRequest();
10737 * @name $httpBackend
10738 * @requires $window
10739 * @requires $document
10740 * @requires $xhrFactory
10743 * HTTP backend used by the {@link ng.$http service} that delegates to
10744 * XMLHttpRequest object or JSONP and deals with browser incompatibilities.
10746 * You should never need to use this service directly, instead use the higher-level abstractions:
10747 * {@link ng.$http $http} or {@link ngResource.$resource $resource}.
10749 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock
10750 * $httpBackend} which can be trained with responses.
10752 function $HttpBackendProvider() {
10753 this.$get = ['$browser', '$window', '$document', '$xhrFactory', function($browser, $window, $document, $xhrFactory) {
10754 return createHttpBackend($browser, $xhrFactory, $browser.defer, $window.angular.callbacks, $document[0]);
10758 function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
10759 // TODO(vojta): fix the signature
10760 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
10761 $browser.$$incOutstandingRequestCount();
10762 url = url || $browser.url();
10764 if (lowercase(method) == 'jsonp') {
10765 var callbackId = '_' + (callbacks.counter++).toString(36);
10766 callbacks[callbackId] = function(data) {
10767 callbacks[callbackId].data = data;
10768 callbacks[callbackId].called = true;
10771 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId),
10772 callbackId, function(status, text) {
10773 completeRequest(callback, status, callbacks[callbackId].data, "", text);
10774 callbacks[callbackId] = noop;
10778 var xhr = createXhr(method, url);
10780 xhr.open(method, url, true);
10781 forEach(headers, function(value, key) {
10782 if (isDefined(value)) {
10783 xhr.setRequestHeader(key, value);
10787 xhr.onload = function requestLoaded() {
10788 var statusText = xhr.statusText || '';
10790 // responseText is the old-school way of retrieving response (supported by IE9)
10791 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
10792 var response = ('response' in xhr) ? xhr.response : xhr.responseText;
10794 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
10795 var status = xhr.status === 1223 ? 204 : xhr.status;
10797 // fix status code when it is 0 (0 status is undocumented).
10798 // Occurs when accessing file resources or on Android 4.1 stock browser
10799 // while retrieving files from application cache.
10800 if (status === 0) {
10801 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0;
10804 completeRequest(callback,
10807 xhr.getAllResponseHeaders(),
10811 var requestError = function() {
10812 // The response is always empty
10813 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
10814 completeRequest(callback, -1, null, null, '');
10817 xhr.onerror = requestError;
10818 xhr.onabort = requestError;
10820 if (withCredentials) {
10821 xhr.withCredentials = true;
10824 if (responseType) {
10826 xhr.responseType = responseType;
10828 // WebKit added support for the json responseType value on 09/03/2013
10829 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are
10830 // known to throw when setting the value "json" as the response type. Other older
10831 // browsers implementing the responseType
10833 // The json response type can be ignored if not supported, because JSON payloads are
10834 // parsed on the client-side regardless.
10835 if (responseType !== 'json') {
10841 xhr.send(isUndefined(post) ? null : post);
10845 var timeoutId = $browserDefer(timeoutRequest, timeout);
10846 } else if (isPromiseLike(timeout)) {
10847 timeout.then(timeoutRequest);
10851 function timeoutRequest() {
10852 jsonpDone && jsonpDone();
10853 xhr && xhr.abort();
10856 function completeRequest(callback, status, response, headersString, statusText) {
10857 // cancel timeout and subsequent timeout promise resolution
10858 if (isDefined(timeoutId)) {
10859 $browserDefer.cancel(timeoutId);
10861 jsonpDone = xhr = null;
10863 callback(status, response, headersString, statusText);
10864 $browser.$$completeOutstandingRequest(noop);
10868 function jsonpReq(url, callbackId, done) {
10869 // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.:
10870 // - fetches local scripts via XHR and evals them
10871 // - adds and immediately removes script elements from the document
10872 var script = rawDocument.createElement('script'), callback = null;
10873 script.type = "text/javascript";
10875 script.async = true;
10877 callback = function(event) {
10878 removeEventListenerFn(script, "load", callback);
10879 removeEventListenerFn(script, "error", callback);
10880 rawDocument.body.removeChild(script);
10883 var text = "unknown";
10886 if (event.type === "load" && !callbacks[callbackId].called) {
10887 event = { type: "error" };
10890 status = event.type === "error" ? 404 : 200;
10894 done(status, text);
10898 addEventListenerFn(script, "load", callback);
10899 addEventListenerFn(script, "error", callback);
10900 rawDocument.body.appendChild(script);
10905 var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate');
10906 $interpolateMinErr.throwNoconcat = function(text) {
10907 throw $interpolateMinErr('noconcat',
10908 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
10909 "interpolations that concatenate multiple expressions when a trusted value is " +
10910 "required. See http://docs.angularjs.org/api/ng.$sce", text);
10913 $interpolateMinErr.interr = function(text, err) {
10914 return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString());
10919 * @name $interpolateProvider
10923 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
10926 <example module="customInterpolationApp">
10927 <file name="index.html">
10929 var customInterpolationApp = angular.module('customInterpolationApp', []);
10931 customInterpolationApp.config(function($interpolateProvider) {
10932 $interpolateProvider.startSymbol('//');
10933 $interpolateProvider.endSymbol('//');
10937 customInterpolationApp.controller('DemoController', function() {
10938 this.label = "This binding is brought you by // interpolation symbols.";
10941 <div ng-app="App" ng-controller="DemoController as demo">
10945 <file name="protractor.js" type="protractor">
10946 it('should interpolate binding with custom symbols', function() {
10947 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
10952 function $InterpolateProvider() {
10953 var startSymbol = '{{';
10954 var endSymbol = '}}';
10958 * @name $interpolateProvider#startSymbol
10960 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
10962 * @param {string=} value new value to set the starting symbol to.
10963 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
10965 this.startSymbol = function(value) {
10967 startSymbol = value;
10970 return startSymbol;
10976 * @name $interpolateProvider#endSymbol
10978 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
10980 * @param {string=} value new value to set the ending symbol to.
10981 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
10983 this.endSymbol = function(value) {
10993 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
10994 var startSymbolLength = startSymbol.length,
10995 endSymbolLength = endSymbol.length,
10996 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'),
10997 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g');
10999 function escape(ch) {
11000 return '\\\\\\' + ch;
11003 function unescapeText(text) {
11004 return text.replace(escapedStartRegexp, startSymbol).
11005 replace(escapedEndRegexp, endSymbol);
11008 function stringify(value) {
11009 if (value == null) { // null || undefined
11012 switch (typeof value) {
11016 value = '' + value;
11019 value = toJson(value);
11027 * @name $interpolate
11035 * Compiles a string with markup into an interpolation function. This service is used by the
11036 * HTML {@link ng.$compile $compile} service for data binding. See
11037 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
11038 * interpolation markup.
11042 * var $interpolate = ...; // injected
11043 * var exp = $interpolate('Hello {{name | uppercase}}!');
11044 * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!');
11047 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is
11048 * `true`, the interpolation function will return `undefined` unless all embedded expressions
11049 * evaluate to a value other than `undefined`.
11052 * var $interpolate = ...; // injected
11053 * var context = {greeting: 'Hello', name: undefined };
11055 * // default "forgiving" mode
11056 * var exp = $interpolate('{{greeting}} {{name}}!');
11057 * expect(exp(context)).toEqual('Hello !');
11059 * // "allOrNothing" mode
11060 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true);
11061 * expect(exp(context)).toBeUndefined();
11062 * context.name = 'Angular';
11063 * expect(exp(context)).toEqual('Hello Angular!');
11066 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior.
11068 * ####Escaped Interpolation
11069 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers
11070 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash).
11071 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression
11074 * This enables web-servers to prevent script injection attacks and defacing attacks, to some
11075 * degree, while also enabling code examples to work without relying on the
11076 * {@link ng.directive:ngNonBindable ngNonBindable} directive.
11078 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data,
11079 * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all
11080 * interpolation start/end markers with their escaped counterparts.**
11082 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered
11083 * output when the $interpolate service processes the text. So, for HTML elements interpolated
11084 * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter
11085 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such,
11086 * this is typically useful only when user-data is used in rendering a template from the server, or
11087 * when otherwise untrusted data is used by a directive.
11090 * <file name="index.html">
11091 * <div ng-init="username='A user'">
11092 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\}
11094 * <p><strong>{{username}}</strong> attempts to inject code which will deface the
11095 * application, but fails to accomplish their task, because the server has correctly
11096 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash)
11098 * <p>Instead, the result of the attempted script injection is visible, and can be removed
11099 * from the database by an administrator.</p>
11104 * @param {string} text The text with markup to interpolate.
11105 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
11106 * embedded expression in order to return an interpolation function. Strings with no
11107 * embedded expression will return null for the interpolation function.
11108 * @param {string=} trustedContext when provided, the returned function passes the interpolated
11109 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult,
11110 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that
11111 * provides Strict Contextual Escaping for details.
11112 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined
11113 * unless all embedded expressions evaluate to a value other than `undefined`.
11114 * @returns {function(context)} an interpolation function which is used to compute the
11115 * interpolated string. The function has these parameters:
11117 * - `context`: evaluation context for all expressions embedded in the interpolated text
11119 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
11120 allOrNothing = !!allOrNothing;
11126 textLength = text.length,
11129 expressionPositions = [];
11131 while (index < textLength) {
11132 if (((startIndex = text.indexOf(startSymbol, index)) != -1) &&
11133 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) {
11134 if (index !== startIndex) {
11135 concat.push(unescapeText(text.substring(index, startIndex)));
11137 exp = text.substring(startIndex + startSymbolLength, endIndex);
11138 expressions.push(exp);
11139 parseFns.push($parse(exp, parseStringifyInterceptor));
11140 index = endIndex + endSymbolLength;
11141 expressionPositions.push(concat.length);
11144 // we did not find an interpolation, so we have to add the remainder to the separators array
11145 if (index !== textLength) {
11146 concat.push(unescapeText(text.substring(index)));
11152 // Concatenating expressions makes it hard to reason about whether some combination of
11153 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a
11154 // single expression be used for iframe[src], object[src], etc., we ensure that the value
11155 // that's used is assigned or constructed by some JS code somewhere that is more testable or
11156 // make it obvious that you bound the value to some user controlled value. This helps reduce
11157 // the load when auditing for XSS issues.
11158 if (trustedContext && concat.length > 1) {
11159 $interpolateMinErr.throwNoconcat(text);
11162 if (!mustHaveExpression || expressions.length) {
11163 var compute = function(values) {
11164 for (var i = 0, ii = expressions.length; i < ii; i++) {
11165 if (allOrNothing && isUndefined(values[i])) return;
11166 concat[expressionPositions[i]] = values[i];
11168 return concat.join('');
11171 var getValue = function(value) {
11172 return trustedContext ?
11173 $sce.getTrusted(trustedContext, value) :
11174 $sce.valueOf(value);
11177 return extend(function interpolationFn(context) {
11179 var ii = expressions.length;
11180 var values = new Array(ii);
11183 for (; i < ii; i++) {
11184 values[i] = parseFns[i](context);
11187 return compute(values);
11189 $exceptionHandler($interpolateMinErr.interr(text, err));
11193 // all of these properties are undocumented for now
11194 exp: text, //just for compatibility with regular watchers created via $watch
11195 expressions: expressions,
11196 $$watchDelegate: function(scope, listener) {
11198 return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) {
11199 var currValue = compute(values);
11200 if (isFunction(listener)) {
11201 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
11203 lastValue = currValue;
11209 function parseStringifyInterceptor(value) {
11211 value = getValue(value);
11212 return allOrNothing && !isDefined(value) ? value : stringify(value);
11214 $exceptionHandler($interpolateMinErr.interr(text, err));
11222 * @name $interpolate#startSymbol
11224 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
11226 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change
11229 * @returns {string} start symbol.
11231 $interpolate.startSymbol = function() {
11232 return startSymbol;
11238 * @name $interpolate#endSymbol
11240 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
11242 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change
11245 * @returns {string} end symbol.
11247 $interpolate.endSymbol = function() {
11251 return $interpolate;
11255 function $IntervalProvider() {
11256 this.$get = ['$rootScope', '$window', '$q', '$$q',
11257 function($rootScope, $window, $q, $$q) {
11258 var intervals = {};
11266 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay`
11269 * The return value of registering an interval function is a promise. This promise will be
11270 * notified upon each tick of the interval, and will be resolved after `count` iterations, or
11271 * run indefinitely if `count` is not defined. The value of the notification will be the
11272 * number of iterations that have run.
11273 * To cancel an interval, call `$interval.cancel(promise)`.
11275 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to
11276 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that
11279 * <div class="alert alert-warning">
11280 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished
11281 * with them. In particular they are not automatically destroyed when a controller's scope or a
11282 * directive's element are destroyed.
11283 * You should take this into consideration and make sure to always cancel the interval at the
11284 * appropriate moment. See the example below for more details on how and when to do this.
11287 * @param {function()} fn A function that should be called repeatedly.
11288 * @param {number} delay Number of milliseconds between each function call.
11289 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
11291 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
11292 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
11293 * @param {...*=} Pass additional parameters to the executed function.
11294 * @returns {promise} A promise which will be notified on each iteration.
11297 * <example module="intervalExample">
11298 * <file name="index.html">
11300 * angular.module('intervalExample', [])
11301 * .controller('ExampleController', ['$scope', '$interval',
11302 * function($scope, $interval) {
11303 * $scope.format = 'M/d/yy h:mm:ss a';
11304 * $scope.blood_1 = 100;
11305 * $scope.blood_2 = 120;
11308 * $scope.fight = function() {
11309 * // Don't start a new fight if we are already fighting
11310 * if ( angular.isDefined(stop) ) return;
11312 * stop = $interval(function() {
11313 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
11314 * $scope.blood_1 = $scope.blood_1 - 3;
11315 * $scope.blood_2 = $scope.blood_2 - 4;
11317 * $scope.stopFight();
11322 * $scope.stopFight = function() {
11323 * if (angular.isDefined(stop)) {
11324 * $interval.cancel(stop);
11325 * stop = undefined;
11329 * $scope.resetFight = function() {
11330 * $scope.blood_1 = 100;
11331 * $scope.blood_2 = 120;
11334 * $scope.$on('$destroy', function() {
11335 * // Make sure that the interval is destroyed too
11336 * $scope.stopFight();
11339 * // Register the 'myCurrentTime' directive factory method.
11340 * // We inject $interval and dateFilter service since the factory method is DI.
11341 * .directive('myCurrentTime', ['$interval', 'dateFilter',
11342 * function($interval, dateFilter) {
11343 * // return the directive link function. (compile function not needed)
11344 * return function(scope, element, attrs) {
11345 * var format, // date format
11346 * stopTime; // so that we can cancel the time updates
11348 * // used to update the UI
11349 * function updateTime() {
11350 * element.text(dateFilter(new Date(), format));
11353 * // watch the expression, and update the UI on change.
11354 * scope.$watch(attrs.myCurrentTime, function(value) {
11359 * stopTime = $interval(updateTime, 1000);
11361 * // listen on DOM destroy (removal) event, and cancel the next UI update
11362 * // to prevent updating time after the DOM element was removed.
11363 * element.on('$destroy', function() {
11364 * $interval.cancel(stopTime);
11371 * <div ng-controller="ExampleController">
11372 * <label>Date format: <input ng-model="format"></label> <hr/>
11373 * Current time is: <span my-current-time="format"></span>
11375 * Blood 1 : <font color='red'>{{blood_1}}</font>
11376 * Blood 2 : <font color='red'>{{blood_2}}</font>
11377 * <button type="button" data-ng-click="fight()">Fight</button>
11378 * <button type="button" data-ng-click="stopFight()">StopFight</button>
11379 * <button type="button" data-ng-click="resetFight()">resetFight</button>
11386 function interval(fn, delay, count, invokeApply) {
11387 var hasParams = arguments.length > 4,
11388 args = hasParams ? sliceArgs(arguments, 4) : [],
11389 setInterval = $window.setInterval,
11390 clearInterval = $window.clearInterval,
11392 skipApply = (isDefined(invokeApply) && !invokeApply),
11393 deferred = (skipApply ? $$q : $q).defer(),
11394 promise = deferred.promise;
11396 count = isDefined(count) ? count : 0;
11398 promise.then(null, null, (!hasParams) ? fn : function() {
11399 fn.apply(null, args);
11402 promise.$$intervalId = setInterval(function tick() {
11403 deferred.notify(iteration++);
11405 if (count > 0 && iteration >= count) {
11406 deferred.resolve(iteration);
11407 clearInterval(promise.$$intervalId);
11408 delete intervals[promise.$$intervalId];
11411 if (!skipApply) $rootScope.$apply();
11415 intervals[promise.$$intervalId] = deferred;
11423 * @name $interval#cancel
11426 * Cancels a task associated with the `promise`.
11428 * @param {Promise=} promise returned by the `$interval` function.
11429 * @returns {boolean} Returns `true` if the task was successfully canceled.
11431 interval.cancel = function(promise) {
11432 if (promise && promise.$$intervalId in intervals) {
11433 intervals[promise.$$intervalId].reject('canceled');
11434 $window.clearInterval(promise.$$intervalId);
11435 delete intervals[promise.$$intervalId];
11450 * $locale service provides localization rules for various Angular components. As of right now the
11451 * only public api is:
11453 * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
11456 var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/,
11457 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
11458 var $locationMinErr = minErr('$location');
11462 * Encode path using encodeUriSegment, ignoring forward slashes
11464 * @param {string} path Path to encode
11465 * @returns {string}
11467 function encodePath(path) {
11468 var segments = path.split('/'),
11469 i = segments.length;
11472 segments[i] = encodeUriSegment(segments[i]);
11475 return segments.join('/');
11478 function parseAbsoluteUrl(absoluteUrl, locationObj) {
11479 var parsedUrl = urlResolve(absoluteUrl);
11481 locationObj.$$protocol = parsedUrl.protocol;
11482 locationObj.$$host = parsedUrl.hostname;
11483 locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
11487 function parseAppUrl(relativeUrl, locationObj) {
11488 var prefixed = (relativeUrl.charAt(0) !== '/');
11490 relativeUrl = '/' + relativeUrl;
11492 var match = urlResolve(relativeUrl);
11493 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ?
11494 match.pathname.substring(1) : match.pathname);
11495 locationObj.$$search = parseKeyValue(match.search);
11496 locationObj.$$hash = decodeURIComponent(match.hash);
11498 // make sure path starts with '/';
11499 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') {
11500 locationObj.$$path = '/' + locationObj.$$path;
11507 * @param {string} begin
11508 * @param {string} whole
11509 * @returns {string} returns text from whole after begin or undefined if it does not begin with
11512 function beginsWith(begin, whole) {
11513 if (whole.indexOf(begin) === 0) {
11514 return whole.substr(begin.length);
11519 function stripHash(url) {
11520 var index = url.indexOf('#');
11521 return index == -1 ? url : url.substr(0, index);
11524 function trimEmptyHash(url) {
11525 return url.replace(/(#.+)|#$/, '$1');
11529 function stripFile(url) {
11530 return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
11533 /* return the server only (scheme://host:port) */
11534 function serverBase(url) {
11535 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2));
11540 * LocationHtml5Url represents an url
11541 * This object is exposed as $location service when HTML5 mode is enabled and supported
11544 * @param {string} appBase application base URL
11545 * @param {string} appBaseNoFile application base URL stripped of any filename
11546 * @param {string} basePrefix url path prefix
11548 function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
11549 this.$$html5 = true;
11550 basePrefix = basePrefix || '';
11551 parseAbsoluteUrl(appBase, this);
11555 * Parse given html5 (regular) url string into properties
11556 * @param {string} url HTML5 url
11559 this.$$parse = function(url) {
11560 var pathUrl = beginsWith(appBaseNoFile, url);
11561 if (!isString(pathUrl)) {
11562 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url,
11566 parseAppUrl(pathUrl, this);
11568 if (!this.$$path) {
11576 * Compose url and update `absUrl` property
11579 this.$$compose = function() {
11580 var search = toKeyValue(this.$$search),
11581 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11583 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11584 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/'
11587 this.$$parseLinkUrl = function(url, relHref) {
11588 if (relHref && relHref[0] === '#') {
11589 // special case for links to hash fragments:
11590 // keep the old url and only replace the hash fragment
11591 this.hash(relHref.slice(1));
11594 var appUrl, prevAppUrl;
11597 if (isDefined(appUrl = beginsWith(appBase, url))) {
11598 prevAppUrl = appUrl;
11599 if (isDefined(appUrl = beginsWith(basePrefix, appUrl))) {
11600 rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
11602 rewrittenUrl = appBase + prevAppUrl;
11604 } else if (isDefined(appUrl = beginsWith(appBaseNoFile, url))) {
11605 rewrittenUrl = appBaseNoFile + appUrl;
11606 } else if (appBaseNoFile == url + '/') {
11607 rewrittenUrl = appBaseNoFile;
11609 if (rewrittenUrl) {
11610 this.$$parse(rewrittenUrl);
11612 return !!rewrittenUrl;
11618 * LocationHashbangUrl represents url
11619 * This object is exposed as $location service when developer doesn't opt into html5 mode.
11620 * It also serves as the base class for html5 mode fallback on legacy browsers.
11623 * @param {string} appBase application base URL
11624 * @param {string} appBaseNoFile application base URL stripped of any filename
11625 * @param {string} hashPrefix hashbang prefix
11627 function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
11629 parseAbsoluteUrl(appBase, this);
11633 * Parse given hashbang url into properties
11634 * @param {string} url Hashbang url
11637 this.$$parse = function(url) {
11638 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
11639 var withoutHashUrl;
11641 if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') {
11643 // The rest of the url starts with a hash so we have
11644 // got either a hashbang path or a plain hash fragment
11645 withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl);
11646 if (isUndefined(withoutHashUrl)) {
11647 // There was no hashbang prefix so we just have a hash fragment
11648 withoutHashUrl = withoutBaseUrl;
11652 // There was no hashbang path nor hash fragment:
11653 // If we are in HTML5 mode we use what is left as the path;
11654 // Otherwise we ignore what is left
11655 if (this.$$html5) {
11656 withoutHashUrl = withoutBaseUrl;
11658 withoutHashUrl = '';
11659 if (isUndefined(withoutBaseUrl)) {
11666 parseAppUrl(withoutHashUrl, this);
11668 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
11673 * In Windows, on an anchor node on documents loaded from
11674 * the filesystem, the browser will return a pathname
11675 * prefixed with the drive name ('/C:/path') when a
11676 * pathname without a drive is set:
11677 * * a.setAttribute('href', '/foo')
11678 * * a.pathname === '/C:/foo' //true
11680 * Inside of Angular, we're always using pathnames that
11681 * do not include drive names for routing.
11683 function removeWindowsDriveName(path, url, base) {
11685 Matches paths for file protocol on windows,
11686 such as /C:/foo/bar, and captures only /foo/bar.
11688 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/;
11690 var firstPathSegmentMatch;
11692 //Get the relative path from the input URL.
11693 if (url.indexOf(base) === 0) {
11694 url = url.replace(base, '');
11697 // The input URL intentionally contains a first path segment that ends with a colon.
11698 if (windowsFilePathExp.exec(url)) {
11702 firstPathSegmentMatch = windowsFilePathExp.exec(path);
11703 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
11708 * Compose hashbang url and update `absUrl` property
11711 this.$$compose = function() {
11712 var search = toKeyValue(this.$$search),
11713 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11715 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11716 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : '');
11719 this.$$parseLinkUrl = function(url, relHref) {
11720 if (stripHash(appBase) == stripHash(url)) {
11730 * LocationHashbangUrl represents url
11731 * This object is exposed as $location service when html5 history api is enabled but the browser
11732 * does not support it.
11735 * @param {string} appBase application base URL
11736 * @param {string} appBaseNoFile application base URL stripped of any filename
11737 * @param {string} hashPrefix hashbang prefix
11739 function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
11740 this.$$html5 = true;
11741 LocationHashbangUrl.apply(this, arguments);
11743 this.$$parseLinkUrl = function(url, relHref) {
11744 if (relHref && relHref[0] === '#') {
11745 // special case for links to hash fragments:
11746 // keep the old url and only replace the hash fragment
11747 this.hash(relHref.slice(1));
11754 if (appBase == stripHash(url)) {
11755 rewrittenUrl = url;
11756 } else if ((appUrl = beginsWith(appBaseNoFile, url))) {
11757 rewrittenUrl = appBase + hashPrefix + appUrl;
11758 } else if (appBaseNoFile === url + '/') {
11759 rewrittenUrl = appBaseNoFile;
11761 if (rewrittenUrl) {
11762 this.$$parse(rewrittenUrl);
11764 return !!rewrittenUrl;
11767 this.$$compose = function() {
11768 var search = toKeyValue(this.$$search),
11769 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11771 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11772 // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#'
11773 this.$$absUrl = appBase + hashPrefix + this.$$url;
11779 var locationPrototype = {
11782 * Are we in html5 mode?
11788 * Has any change been replacing?
11795 * @name $location#absUrl
11798 * This method is getter only.
11800 * Return full url representation with all segments encoded according to rules specified in
11801 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
11805 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11806 * var absUrl = $location.absUrl();
11807 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
11810 * @return {string} full url
11812 absUrl: locationGetter('$$absUrl'),
11816 * @name $location#url
11819 * This method is getter / setter.
11821 * Return url (e.g. `/path?a=b#hash`) when called without any parameter.
11823 * Change path, search and hash, when called with parameter and return `$location`.
11827 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11828 * var url = $location.url();
11829 * // => "/some/path?foo=bar&baz=xoxo"
11832 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
11833 * @return {string} url
11835 url: function(url) {
11836 if (isUndefined(url)) {
11840 var match = PATH_MATCH.exec(url);
11841 if (match[1] || url === '') this.path(decodeURIComponent(match[1]));
11842 if (match[2] || match[1] || url === '') this.search(match[3] || '');
11843 this.hash(match[5] || '');
11850 * @name $location#protocol
11853 * This method is getter only.
11855 * Return protocol of current url.
11859 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11860 * var protocol = $location.protocol();
11864 * @return {string} protocol of current url
11866 protocol: locationGetter('$$protocol'),
11870 * @name $location#host
11873 * This method is getter only.
11875 * Return host of current url.
11877 * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.
11881 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11882 * var host = $location.host();
11883 * // => "example.com"
11885 * // given url http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
11886 * host = $location.host();
11887 * // => "example.com"
11888 * host = location.host;
11889 * // => "example.com:8080"
11892 * @return {string} host of current url.
11894 host: locationGetter('$$host'),
11898 * @name $location#port
11901 * This method is getter only.
11903 * Return port of current url.
11907 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11908 * var port = $location.port();
11912 * @return {Number} port
11914 port: locationGetter('$$port'),
11918 * @name $location#path
11921 * This method is getter / setter.
11923 * Return path of current url when called without any parameter.
11925 * Change path when called with parameter and return `$location`.
11927 * Note: Path should always begin with forward slash (/), this method will add the forward slash
11928 * if it is missing.
11932 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11933 * var path = $location.path();
11934 * // => "/some/path"
11937 * @param {(string|number)=} path New path
11938 * @return {string} path
11940 path: locationGetterSetter('$$path', function(path) {
11941 path = path !== null ? path.toString() : '';
11942 return path.charAt(0) == '/' ? path : '/' + path;
11947 * @name $location#search
11950 * This method is getter / setter.
11952 * Return search part (as object) of current url when called without any parameter.
11954 * Change search part when called with parameter and return `$location`.
11958 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11959 * var searchObject = $location.search();
11960 * // => {foo: 'bar', baz: 'xoxo'}
11962 * // set foo to 'yipee'
11963 * $location.search('foo', 'yipee');
11964 * // $location.search() => {foo: 'yipee', baz: 'xoxo'}
11967 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or
11970 * When called with a single argument the method acts as a setter, setting the `search` component
11971 * of `$location` to the specified value.
11973 * If the argument is a hash object containing an array of values, these values will be encoded
11974 * as duplicate search parameters in the url.
11976 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue`
11977 * will override only a single search property.
11979 * If `paramValue` is an array, it will override the property of the `search` component of
11980 * `$location` specified via the first argument.
11982 * If `paramValue` is `null`, the property specified via the first argument will be deleted.
11984 * If `paramValue` is `true`, the property specified via the first argument will be added with no
11985 * value nor trailing equal sign.
11987 * @return {Object} If called with no arguments returns the parsed `search` object. If called with
11988 * one or more arguments returns `$location` object itself.
11990 search: function(search, paramValue) {
11991 switch (arguments.length) {
11993 return this.$$search;
11995 if (isString(search) || isNumber(search)) {
11996 search = search.toString();
11997 this.$$search = parseKeyValue(search);
11998 } else if (isObject(search)) {
11999 search = copy(search, {});
12000 // remove object undefined or null properties
12001 forEach(search, function(value, key) {
12002 if (value == null) delete search[key];
12005 this.$$search = search;
12007 throw $locationMinErr('isrcharg',
12008 'The first argument of the `$location#search()` call must be a string or an object.');
12012 if (isUndefined(paramValue) || paramValue === null) {
12013 delete this.$$search[search];
12015 this.$$search[search] = paramValue;
12025 * @name $location#hash
12028 * This method is getter / setter.
12030 * Returns the hash fragment when called without any parameters.
12032 * Changes the hash fragment when called with a parameter and returns `$location`.
12036 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
12037 * var hash = $location.hash();
12038 * // => "hashValue"
12041 * @param {(string|number)=} hash New hash fragment
12042 * @return {string} hash
12044 hash: locationGetterSetter('$$hash', function(hash) {
12045 return hash !== null ? hash.toString() : '';
12050 * @name $location#replace
12053 * If called, all changes to $location during the current `$digest` will replace the current history
12054 * record, instead of adding a new one.
12056 replace: function() {
12057 this.$$replace = true;
12062 forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) {
12063 Location.prototype = Object.create(locationPrototype);
12067 * @name $location#state
12070 * This method is getter / setter.
12072 * Return the history state object when called without any parameter.
12074 * Change the history state object when called with one parameter and return `$location`.
12075 * The state object is later passed to `pushState` or `replaceState`.
12077 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting
12078 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
12079 * older browsers (like IE9 or Android < 4.0), don't use this method.
12081 * @param {object=} state State object for pushState or replaceState
12082 * @return {object} state
12084 Location.prototype.state = function(state) {
12085 if (!arguments.length) {
12086 return this.$$state;
12089 if (Location !== LocationHtml5Url || !this.$$html5) {
12090 throw $locationMinErr('nostate', 'History API state support is available only ' +
12091 'in HTML5 mode and only in browsers supporting HTML5 History API');
12093 // The user might modify `stateObject` after invoking `$location.state(stateObject)`
12094 // but we're changing the $$state reference to $browser.state() during the $digest
12095 // so the modification window is narrow.
12096 this.$$state = isUndefined(state) ? null : state;
12103 function locationGetter(property) {
12104 return function() {
12105 return this[property];
12110 function locationGetterSetter(property, preprocess) {
12111 return function(value) {
12112 if (isUndefined(value)) {
12113 return this[property];
12116 this[property] = preprocess(value);
12128 * @requires $rootElement
12131 * The $location service parses the URL in the browser address bar (based on the
12132 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL
12133 * available to your application. Changes to the URL in the address bar are reflected into
12134 * $location service and changes to $location are reflected into the browser address bar.
12136 * **The $location service:**
12138 * - Exposes the current URL in the browser address bar, so you can
12139 * - Watch and observe the URL.
12140 * - Change the URL.
12141 * - Synchronizes the URL with the browser when the user
12142 * - Changes the address bar.
12143 * - Clicks the back or forward button (or clicks a History link).
12144 * - Clicks on a link.
12145 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
12147 * For more information see {@link guide/$location Developer Guide: Using $location}
12152 * @name $locationProvider
12154 * Use the `$locationProvider` to configure how the application deep linking paths are stored.
12156 function $LocationProvider() {
12157 var hashPrefix = '',
12166 * @name $locationProvider#hashPrefix
12168 * @param {string=} prefix Prefix for hash part (containing path and search)
12169 * @returns {*} current value if used as getter or itself (chaining) if used as setter
12171 this.hashPrefix = function(prefix) {
12172 if (isDefined(prefix)) {
12173 hashPrefix = prefix;
12182 * @name $locationProvider#html5Mode
12184 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value.
12185 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported
12187 * - **enabled** – `{boolean}` – (default: false) If true, will rely on `history.pushState` to
12188 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not
12189 * support `pushState`.
12190 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies
12191 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are
12192 * true, and a base tag is not present, an error will be thrown when `$location` is injected.
12193 * See the {@link guide/$location $location guide for more information}
12194 * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled,
12195 * enables/disables url rewriting for relative links.
12197 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
12199 this.html5Mode = function(mode) {
12200 if (isBoolean(mode)) {
12201 html5Mode.enabled = mode;
12203 } else if (isObject(mode)) {
12205 if (isBoolean(mode.enabled)) {
12206 html5Mode.enabled = mode.enabled;
12209 if (isBoolean(mode.requireBase)) {
12210 html5Mode.requireBase = mode.requireBase;
12213 if (isBoolean(mode.rewriteLinks)) {
12214 html5Mode.rewriteLinks = mode.rewriteLinks;
12225 * @name $location#$locationChangeStart
12226 * @eventType broadcast on root scope
12228 * Broadcasted before a URL will change.
12230 * This change can be prevented by calling
12231 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
12232 * details about event object. Upon successful change
12233 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired.
12235 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
12236 * the browser supports the HTML5 History API.
12238 * @param {Object} angularEvent Synthetic event object.
12239 * @param {string} newUrl New URL
12240 * @param {string=} oldUrl URL that was before it was changed.
12241 * @param {string=} newState New history state object
12242 * @param {string=} oldState History state object that was before it was changed.
12247 * @name $location#$locationChangeSuccess
12248 * @eventType broadcast on root scope
12250 * Broadcasted after a URL was changed.
12252 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
12253 * the browser supports the HTML5 History API.
12255 * @param {Object} angularEvent Synthetic event object.
12256 * @param {string} newUrl New URL
12257 * @param {string=} oldUrl URL that was before it was changed.
12258 * @param {string=} newState New history state object
12259 * @param {string=} oldState History state object that was before it was changed.
12262 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window',
12263 function($rootScope, $browser, $sniffer, $rootElement, $window) {
12266 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to ''
12267 initialUrl = $browser.url(),
12270 if (html5Mode.enabled) {
12271 if (!baseHref && html5Mode.requireBase) {
12272 throw $locationMinErr('nobase',
12273 "$location in HTML5 mode requires a <base> tag to be present!");
12275 appBase = serverBase(initialUrl) + (baseHref || '/');
12276 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url;
12278 appBase = stripHash(initialUrl);
12279 LocationMode = LocationHashbangUrl;
12281 var appBaseNoFile = stripFile(appBase);
12283 $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix);
12284 $location.$$parseLinkUrl(initialUrl, initialUrl);
12286 $location.$$state = $browser.state();
12288 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
12290 function setBrowserUrlWithFallback(url, replace, state) {
12291 var oldUrl = $location.url();
12292 var oldState = $location.$$state;
12294 $browser.url(url, replace, state);
12296 // Make sure $location.state() returns referentially identical (not just deeply equal)
12297 // state object; this makes possible quick checking if the state changed in the digest
12298 // loop. Checking deep equality would be too expensive.
12299 $location.$$state = $browser.state();
12301 // Restore old values if pushState fails
12302 $location.url(oldUrl);
12303 $location.$$state = oldState;
12309 $rootElement.on('click', function(event) {
12310 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
12311 // currently we open nice url link and redirect then
12313 if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return;
12315 var elm = jqLite(event.target);
12317 // traverse the DOM up to find first A tag
12318 while (nodeName_(elm[0]) !== 'a') {
12319 // ignore rewriting if no A tag (reached root element, or no parent - removed from document)
12320 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return;
12323 var absHref = elm.prop('href');
12324 // get the actual href attribute - see
12325 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
12326 var relHref = elm.attr('href') || elm.attr('xlink:href');
12328 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') {
12329 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during
12331 absHref = urlResolve(absHref.animVal).href;
12334 // Ignore when url is started with javascript: or mailto:
12335 if (IGNORE_URI_REGEXP.test(absHref)) return;
12337 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) {
12338 if ($location.$$parseLinkUrl(absHref, relHref)) {
12339 // We do a preventDefault for all urls that are part of the angular application,
12340 // in html5mode and also without, so that we are able to abort navigation without
12341 // getting double entries in the location history.
12342 event.preventDefault();
12343 // update location manually
12344 if ($location.absUrl() != $browser.url()) {
12345 $rootScope.$apply();
12346 // hack to work around FF6 bug 684208 when scenario runner clicks on links
12347 $window.angular['ff-684208-preventDefault'] = true;
12354 // rewrite hashbang url <> html5 url
12355 if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) {
12356 $browser.url($location.absUrl(), true);
12359 var initializing = true;
12361 // update $location when $browser url changes
12362 $browser.onUrlChange(function(newUrl, newState) {
12364 if (isUndefined(beginsWith(appBaseNoFile, newUrl))) {
12365 // If we are navigating outside of the app then force a reload
12366 $window.location.href = newUrl;
12370 $rootScope.$evalAsync(function() {
12371 var oldUrl = $location.absUrl();
12372 var oldState = $location.$$state;
12373 var defaultPrevented;
12374 newUrl = trimEmptyHash(newUrl);
12375 $location.$$parse(newUrl);
12376 $location.$$state = newState;
12378 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
12379 newState, oldState).defaultPrevented;
12381 // if the location was changed by a `$locationChangeStart` handler then stop
12382 // processing this location change
12383 if ($location.absUrl() !== newUrl) return;
12385 if (defaultPrevented) {
12386 $location.$$parse(oldUrl);
12387 $location.$$state = oldState;
12388 setBrowserUrlWithFallback(oldUrl, false, oldState);
12390 initializing = false;
12391 afterLocationChange(oldUrl, oldState);
12394 if (!$rootScope.$$phase) $rootScope.$digest();
12398 $rootScope.$watch(function $locationWatch() {
12399 var oldUrl = trimEmptyHash($browser.url());
12400 var newUrl = trimEmptyHash($location.absUrl());
12401 var oldState = $browser.state();
12402 var currentReplace = $location.$$replace;
12403 var urlOrStateChanged = oldUrl !== newUrl ||
12404 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
12406 if (initializing || urlOrStateChanged) {
12407 initializing = false;
12409 $rootScope.$evalAsync(function() {
12410 var newUrl = $location.absUrl();
12411 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
12412 $location.$$state, oldState).defaultPrevented;
12414 // if the location was changed by a `$locationChangeStart` handler then stop
12415 // processing this location change
12416 if ($location.absUrl() !== newUrl) return;
12418 if (defaultPrevented) {
12419 $location.$$parse(oldUrl);
12420 $location.$$state = oldState;
12422 if (urlOrStateChanged) {
12423 setBrowserUrlWithFallback(newUrl, currentReplace,
12424 oldState === $location.$$state ? null : $location.$$state);
12426 afterLocationChange(oldUrl, oldState);
12431 $location.$$replace = false;
12433 // we don't need to return anything because $evalAsync will make the digest loop dirty when
12434 // there is a change
12439 function afterLocationChange(oldUrl, oldState) {
12440 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl,
12441 $location.$$state, oldState);
12449 * @requires $window
12452 * Simple service for logging. Default implementation safely writes the message
12453 * into the browser's console (if present).
12455 * The main purpose of this service is to simplify debugging and troubleshooting.
12457 * The default is to log `debug` messages. You can use
12458 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this.
12461 <example module="logExample">
12462 <file name="script.js">
12463 angular.module('logExample', [])
12464 .controller('LogController', ['$scope', '$log', function($scope, $log) {
12465 $scope.$log = $log;
12466 $scope.message = 'Hello World!';
12469 <file name="index.html">
12470 <div ng-controller="LogController">
12471 <p>Reload this page with open console, enter text and hit the log button...</p>
12473 <input type="text" ng-model="message" /></label>
12474 <button ng-click="$log.log(message)">log</button>
12475 <button ng-click="$log.warn(message)">warn</button>
12476 <button ng-click="$log.info(message)">info</button>
12477 <button ng-click="$log.error(message)">error</button>
12478 <button ng-click="$log.debug(message)">debug</button>
12486 * @name $logProvider
12488 * Use the `$logProvider` to configure how the application logs messages
12490 function $LogProvider() {
12496 * @name $logProvider#debugEnabled
12498 * @param {boolean=} flag enable or disable debug level messages
12499 * @returns {*} current value if used as getter or itself (chaining) if used as setter
12501 this.debugEnabled = function(flag) {
12502 if (isDefined(flag)) {
12510 this.$get = ['$window', function($window) {
12517 * Write a log message
12519 log: consoleLog('log'),
12526 * Write an information message
12528 info: consoleLog('info'),
12535 * Write a warning message
12537 warn: consoleLog('warn'),
12544 * Write an error message
12546 error: consoleLog('error'),
12553 * Write a debug message
12555 debug: (function() {
12556 var fn = consoleLog('debug');
12558 return function() {
12560 fn.apply(self, arguments);
12566 function formatError(arg) {
12567 if (arg instanceof Error) {
12569 arg = (arg.message && arg.stack.indexOf(arg.message) === -1)
12570 ? 'Error: ' + arg.message + '\n' + arg.stack
12572 } else if (arg.sourceURL) {
12573 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
12579 function consoleLog(type) {
12580 var console = $window.console || {},
12581 logFn = console[type] || console.log || noop,
12584 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode.
12585 // The reason behind this is that console.log has type "object" in IE8...
12587 hasApply = !!logFn.apply;
12591 return function() {
12593 forEach(arguments, function(arg) {
12594 args.push(formatError(arg));
12596 return logFn.apply(console, args);
12600 // we are IE which either doesn't have window.console => this is noop and we do nothing,
12601 // or we are IE where console.log doesn't have apply so we log at least first 2 args
12602 return function(arg1, arg2) {
12603 logFn(arg1, arg2 == null ? '' : arg2);
12609 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
12610 * Any commits to this file should be reviewed with security in mind. *
12611 * Changes to this file can potentially create security vulnerabilities. *
12612 * An approval from 2 Core members with history of modifying *
12613 * this file is required. *
12615 * Does the change somehow allow for arbitrary javascript to be executed? *
12616 * Or allows for someone to change the prototype of built-in objects? *
12617 * Or gives undesired access to variables likes document or window? *
12618 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
12620 var $parseMinErr = minErr('$parse');
12622 // Sandboxing Angular Expressions
12623 // ------------------------------
12624 // Angular expressions are generally considered safe because these expressions only have direct
12625 // access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by
12626 // obtaining a reference to native JS functions such as the Function constructor.
12628 // As an example, consider the following Angular expression:
12630 // {}.toString.constructor('alert("evil JS code")')
12632 // This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits
12633 // against the expression language, but not to prevent exploits that were enabled by exposing
12634 // sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good
12635 // practice and therefore we are not even trying to protect against interaction with an object
12636 // explicitly exposed in this way.
12638 // In general, it is not possible to access a Window object from an angular expression unless a
12639 // window or some DOM object that has a reference to window is published onto a Scope.
12640 // Similarly we prevent invocations of function known to be dangerous, as well as assignments to
12643 // See https://docs.angularjs.org/guide/security
12646 function ensureSafeMemberName(name, fullExpression) {
12647 if (name === "__defineGetter__" || name === "__defineSetter__"
12648 || name === "__lookupGetter__" || name === "__lookupSetter__"
12649 || name === "__proto__") {
12650 throw $parseMinErr('isecfld',
12651 'Attempting to access a disallowed field in Angular expressions! '
12652 + 'Expression: {0}', fullExpression);
12657 function getStringValue(name, fullExpression) {
12658 // From the JavaScript docs:
12659 // Property names must be strings. This means that non-string objects cannot be used
12660 // as keys in an object. Any non-string object, including a number, is typecasted
12661 // into a string via the toString method.
12663 // So, to ensure that we are checking the same `name` that JavaScript would use,
12664 // we cast it to a string, if possible.
12665 // Doing `name + ''` can cause a repl error if the result to `toString` is not a string,
12666 // this is, this will handle objects that misbehave.
12668 if (!isString(name)) {
12669 throw $parseMinErr('iseccst',
12670 'Cannot convert object to primitive value! '
12671 + 'Expression: {0}', fullExpression);
12676 function ensureSafeObject(obj, fullExpression) {
12677 // nifty check if obj is Function that is fast and works across iframes and other contexts
12679 if (obj.constructor === obj) {
12680 throw $parseMinErr('isecfn',
12681 'Referencing Function in Angular expressions is disallowed! Expression: {0}',
12683 } else if (// isWindow(obj)
12684 obj.window === obj) {
12685 throw $parseMinErr('isecwindow',
12686 'Referencing the Window in Angular expressions is disallowed! Expression: {0}',
12688 } else if (// isElement(obj)
12689 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) {
12690 throw $parseMinErr('isecdom',
12691 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}',
12693 } else if (// block Object so that we can't get hold of dangerous Object.* methods
12695 throw $parseMinErr('isecobj',
12696 'Referencing Object in Angular expressions is disallowed! Expression: {0}',
12703 var CALL = Function.prototype.call;
12704 var APPLY = Function.prototype.apply;
12705 var BIND = Function.prototype.bind;
12707 function ensureSafeFunction(obj, fullExpression) {
12709 if (obj.constructor === obj) {
12710 throw $parseMinErr('isecfn',
12711 'Referencing Function in Angular expressions is disallowed! Expression: {0}',
12713 } else if (obj === CALL || obj === APPLY || obj === BIND) {
12714 throw $parseMinErr('isecff',
12715 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}',
12721 function ensureSafeAssignContext(obj, fullExpression) {
12723 if (obj === (0).constructor || obj === (false).constructor || obj === ''.constructor ||
12724 obj === {}.constructor || obj === [].constructor || obj === Function.constructor) {
12725 throw $parseMinErr('isecaf',
12726 'Assigning to a constructor is disallowed! Expression: {0}', fullExpression);
12731 var OPERATORS = createMap();
12732 forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; });
12733 var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
12736 /////////////////////////////////////////
12742 var Lexer = function(options) {
12743 this.options = options;
12746 Lexer.prototype = {
12747 constructor: Lexer,
12749 lex: function(text) {
12754 while (this.index < this.text.length) {
12755 var ch = this.text.charAt(this.index);
12756 if (ch === '"' || ch === "'") {
12757 this.readString(ch);
12758 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {
12760 } else if (this.isIdent(ch)) {
12762 } else if (this.is(ch, '(){}[].,;:?')) {
12763 this.tokens.push({index: this.index, text: ch});
12765 } else if (this.isWhitespace(ch)) {
12768 var ch2 = ch + this.peek();
12769 var ch3 = ch2 + this.peek(2);
12770 var op1 = OPERATORS[ch];
12771 var op2 = OPERATORS[ch2];
12772 var op3 = OPERATORS[ch3];
12773 if (op1 || op2 || op3) {
12774 var token = op3 ? ch3 : (op2 ? ch2 : ch);
12775 this.tokens.push({index: this.index, text: token, operator: true});
12776 this.index += token.length;
12778 this.throwError('Unexpected next character ', this.index, this.index + 1);
12782 return this.tokens;
12785 is: function(ch, chars) {
12786 return chars.indexOf(ch) !== -1;
12789 peek: function(i) {
12791 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false;
12794 isNumber: function(ch) {
12795 return ('0' <= ch && ch <= '9') && typeof ch === "string";
12798 isWhitespace: function(ch) {
12799 // IE treats non-breaking space as \u00A0
12800 return (ch === ' ' || ch === '\r' || ch === '\t' ||
12801 ch === '\n' || ch === '\v' || ch === '\u00A0');
12804 isIdent: function(ch) {
12805 return ('a' <= ch && ch <= 'z' ||
12806 'A' <= ch && ch <= 'Z' ||
12807 '_' === ch || ch === '$');
12810 isExpOperator: function(ch) {
12811 return (ch === '-' || ch === '+' || this.isNumber(ch));
12814 throwError: function(error, start, end) {
12815 end = end || this.index;
12816 var colStr = (isDefined(start)
12817 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']'
12819 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].',
12820 error, colStr, this.text);
12823 readNumber: function() {
12825 var start = this.index;
12826 while (this.index < this.text.length) {
12827 var ch = lowercase(this.text.charAt(this.index));
12828 if (ch == '.' || this.isNumber(ch)) {
12831 var peekCh = this.peek();
12832 if (ch == 'e' && this.isExpOperator(peekCh)) {
12834 } else if (this.isExpOperator(ch) &&
12835 peekCh && this.isNumber(peekCh) &&
12836 number.charAt(number.length - 1) == 'e') {
12838 } else if (this.isExpOperator(ch) &&
12839 (!peekCh || !this.isNumber(peekCh)) &&
12840 number.charAt(number.length - 1) == 'e') {
12841 this.throwError('Invalid exponent');
12852 value: Number(number)
12856 readIdent: function() {
12857 var start = this.index;
12858 while (this.index < this.text.length) {
12859 var ch = this.text.charAt(this.index);
12860 if (!(this.isIdent(ch) || this.isNumber(ch))) {
12867 text: this.text.slice(start, this.index),
12872 readString: function(quote) {
12873 var start = this.index;
12876 var rawString = quote;
12877 var escape = false;
12878 while (this.index < this.text.length) {
12879 var ch = this.text.charAt(this.index);
12883 var hex = this.text.substring(this.index + 1, this.index + 5);
12884 if (!hex.match(/[\da-f]{4}/i)) {
12885 this.throwError('Invalid unicode escape [\\u' + hex + ']');
12888 string += String.fromCharCode(parseInt(hex, 16));
12890 var rep = ESCAPE[ch];
12891 string = string + (rep || ch);
12894 } else if (ch === '\\') {
12896 } else if (ch === quote) {
12910 this.throwError('Unterminated quote', start);
12914 var AST = function(lexer, options) {
12915 this.lexer = lexer;
12916 this.options = options;
12919 AST.Program = 'Program';
12920 AST.ExpressionStatement = 'ExpressionStatement';
12921 AST.AssignmentExpression = 'AssignmentExpression';
12922 AST.ConditionalExpression = 'ConditionalExpression';
12923 AST.LogicalExpression = 'LogicalExpression';
12924 AST.BinaryExpression = 'BinaryExpression';
12925 AST.UnaryExpression = 'UnaryExpression';
12926 AST.CallExpression = 'CallExpression';
12927 AST.MemberExpression = 'MemberExpression';
12928 AST.Identifier = 'Identifier';
12929 AST.Literal = 'Literal';
12930 AST.ArrayExpression = 'ArrayExpression';
12931 AST.Property = 'Property';
12932 AST.ObjectExpression = 'ObjectExpression';
12933 AST.ThisExpression = 'ThisExpression';
12935 // Internal use only
12936 AST.NGValueParameter = 'NGValueParameter';
12939 ast: function(text) {
12941 this.tokens = this.lexer.lex(text);
12943 var value = this.program();
12945 if (this.tokens.length !== 0) {
12946 this.throwError('is an unexpected token', this.tokens[0]);
12952 program: function() {
12955 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']'))
12956 body.push(this.expressionStatement());
12957 if (!this.expect(';')) {
12958 return { type: AST.Program, body: body};
12963 expressionStatement: function() {
12964 return { type: AST.ExpressionStatement, expression: this.filterChain() };
12967 filterChain: function() {
12968 var left = this.expression();
12970 while ((token = this.expect('|'))) {
12971 left = this.filter(left);
12976 expression: function() {
12977 return this.assignment();
12980 assignment: function() {
12981 var result = this.ternary();
12982 if (this.expect('=')) {
12983 result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='};
12988 ternary: function() {
12989 var test = this.logicalOR();
12992 if (this.expect('?')) {
12993 alternate = this.expression();
12994 if (this.consume(':')) {
12995 consequent = this.expression();
12996 return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent};
13002 logicalOR: function() {
13003 var left = this.logicalAND();
13004 while (this.expect('||')) {
13005 left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() };
13010 logicalAND: function() {
13011 var left = this.equality();
13012 while (this.expect('&&')) {
13013 left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()};
13018 equality: function() {
13019 var left = this.relational();
13021 while ((token = this.expect('==','!=','===','!=='))) {
13022 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() };
13027 relational: function() {
13028 var left = this.additive();
13030 while ((token = this.expect('<', '>', '<=', '>='))) {
13031 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() };
13036 additive: function() {
13037 var left = this.multiplicative();
13039 while ((token = this.expect('+','-'))) {
13040 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() };
13045 multiplicative: function() {
13046 var left = this.unary();
13048 while ((token = this.expect('*','/','%'))) {
13049 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() };
13054 unary: function() {
13056 if ((token = this.expect('+', '-', '!'))) {
13057 return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() };
13059 return this.primary();
13063 primary: function() {
13065 if (this.expect('(')) {
13066 primary = this.filterChain();
13068 } else if (this.expect('[')) {
13069 primary = this.arrayDeclaration();
13070 } else if (this.expect('{')) {
13071 primary = this.object();
13072 } else if (this.constants.hasOwnProperty(this.peek().text)) {
13073 primary = copy(this.constants[this.consume().text]);
13074 } else if (this.peek().identifier) {
13075 primary = this.identifier();
13076 } else if (this.peek().constant) {
13077 primary = this.constant();
13079 this.throwError('not a primary expression', this.peek());
13083 while ((next = this.expect('(', '[', '.'))) {
13084 if (next.text === '(') {
13085 primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() };
13087 } else if (next.text === '[') {
13088 primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true };
13090 } else if (next.text === '.') {
13091 primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false };
13093 this.throwError('IMPOSSIBLE');
13099 filter: function(baseExpression) {
13100 var args = [baseExpression];
13101 var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true};
13103 while (this.expect(':')) {
13104 args.push(this.expression());
13110 parseArguments: function() {
13112 if (this.peekToken().text !== ')') {
13114 args.push(this.expression());
13115 } while (this.expect(','));
13120 identifier: function() {
13121 var token = this.consume();
13122 if (!token.identifier) {
13123 this.throwError('is not a valid identifier', token);
13125 return { type: AST.Identifier, name: token.text };
13128 constant: function() {
13129 // TODO check that it is a constant
13130 return { type: AST.Literal, value: this.consume().value };
13133 arrayDeclaration: function() {
13135 if (this.peekToken().text !== ']') {
13137 if (this.peek(']')) {
13138 // Support trailing commas per ES5.1.
13141 elements.push(this.expression());
13142 } while (this.expect(','));
13146 return { type: AST.ArrayExpression, elements: elements };
13149 object: function() {
13150 var properties = [], property;
13151 if (this.peekToken().text !== '}') {
13153 if (this.peek('}')) {
13154 // Support trailing commas per ES5.1.
13157 property = {type: AST.Property, kind: 'init'};
13158 if (this.peek().constant) {
13159 property.key = this.constant();
13160 } else if (this.peek().identifier) {
13161 property.key = this.identifier();
13163 this.throwError("invalid key", this.peek());
13166 property.value = this.expression();
13167 properties.push(property);
13168 } while (this.expect(','));
13172 return {type: AST.ObjectExpression, properties: properties };
13175 throwError: function(msg, token) {
13176 throw $parseMinErr('syntax',
13177 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].',
13178 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index));
13181 consume: function(e1) {
13182 if (this.tokens.length === 0) {
13183 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
13186 var token = this.expect(e1);
13188 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek());
13193 peekToken: function() {
13194 if (this.tokens.length === 0) {
13195 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
13197 return this.tokens[0];
13200 peek: function(e1, e2, e3, e4) {
13201 return this.peekAhead(0, e1, e2, e3, e4);
13204 peekAhead: function(i, e1, e2, e3, e4) {
13205 if (this.tokens.length > i) {
13206 var token = this.tokens[i];
13207 var t = token.text;
13208 if (t === e1 || t === e2 || t === e3 || t === e4 ||
13209 (!e1 && !e2 && !e3 && !e4)) {
13216 expect: function(e1, e2, e3, e4) {
13217 var token = this.peek(e1, e2, e3, e4);
13219 this.tokens.shift();
13226 /* `undefined` is not a constant, it is an identifier,
13227 * but using it as an identifier is not supported
13230 'true': { type: AST.Literal, value: true },
13231 'false': { type: AST.Literal, value: false },
13232 'null': { type: AST.Literal, value: null },
13233 'undefined': {type: AST.Literal, value: undefined },
13234 'this': {type: AST.ThisExpression }
13238 function ifDefined(v, d) {
13239 return typeof v !== 'undefined' ? v : d;
13242 function plusFn(l, r) {
13243 if (typeof l === 'undefined') return r;
13244 if (typeof r === 'undefined') return l;
13248 function isStateless($filter, filterName) {
13249 var fn = $filter(filterName);
13250 return !fn.$stateful;
13253 function findConstantAndWatchExpressions(ast, $filter) {
13256 switch (ast.type) {
13258 allConstants = true;
13259 forEach(ast.body, function(expr) {
13260 findConstantAndWatchExpressions(expr.expression, $filter);
13261 allConstants = allConstants && expr.expression.constant;
13263 ast.constant = allConstants;
13266 ast.constant = true;
13269 case AST.UnaryExpression:
13270 findConstantAndWatchExpressions(ast.argument, $filter);
13271 ast.constant = ast.argument.constant;
13272 ast.toWatch = ast.argument.toWatch;
13274 case AST.BinaryExpression:
13275 findConstantAndWatchExpressions(ast.left, $filter);
13276 findConstantAndWatchExpressions(ast.right, $filter);
13277 ast.constant = ast.left.constant && ast.right.constant;
13278 ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch);
13280 case AST.LogicalExpression:
13281 findConstantAndWatchExpressions(ast.left, $filter);
13282 findConstantAndWatchExpressions(ast.right, $filter);
13283 ast.constant = ast.left.constant && ast.right.constant;
13284 ast.toWatch = ast.constant ? [] : [ast];
13286 case AST.ConditionalExpression:
13287 findConstantAndWatchExpressions(ast.test, $filter);
13288 findConstantAndWatchExpressions(ast.alternate, $filter);
13289 findConstantAndWatchExpressions(ast.consequent, $filter);
13290 ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant;
13291 ast.toWatch = ast.constant ? [] : [ast];
13293 case AST.Identifier:
13294 ast.constant = false;
13295 ast.toWatch = [ast];
13297 case AST.MemberExpression:
13298 findConstantAndWatchExpressions(ast.object, $filter);
13299 if (ast.computed) {
13300 findConstantAndWatchExpressions(ast.property, $filter);
13302 ast.constant = ast.object.constant && (!ast.computed || ast.property.constant);
13303 ast.toWatch = [ast];
13305 case AST.CallExpression:
13306 allConstants = ast.filter ? isStateless($filter, ast.callee.name) : false;
13308 forEach(ast.arguments, function(expr) {
13309 findConstantAndWatchExpressions(expr, $filter);
13310 allConstants = allConstants && expr.constant;
13311 if (!expr.constant) {
13312 argsToWatch.push.apply(argsToWatch, expr.toWatch);
13315 ast.constant = allConstants;
13316 ast.toWatch = ast.filter && isStateless($filter, ast.callee.name) ? argsToWatch : [ast];
13318 case AST.AssignmentExpression:
13319 findConstantAndWatchExpressions(ast.left, $filter);
13320 findConstantAndWatchExpressions(ast.right, $filter);
13321 ast.constant = ast.left.constant && ast.right.constant;
13322 ast.toWatch = [ast];
13324 case AST.ArrayExpression:
13325 allConstants = true;
13327 forEach(ast.elements, function(expr) {
13328 findConstantAndWatchExpressions(expr, $filter);
13329 allConstants = allConstants && expr.constant;
13330 if (!expr.constant) {
13331 argsToWatch.push.apply(argsToWatch, expr.toWatch);
13334 ast.constant = allConstants;
13335 ast.toWatch = argsToWatch;
13337 case AST.ObjectExpression:
13338 allConstants = true;
13340 forEach(ast.properties, function(property) {
13341 findConstantAndWatchExpressions(property.value, $filter);
13342 allConstants = allConstants && property.value.constant;
13343 if (!property.value.constant) {
13344 argsToWatch.push.apply(argsToWatch, property.value.toWatch);
13347 ast.constant = allConstants;
13348 ast.toWatch = argsToWatch;
13350 case AST.ThisExpression:
13351 ast.constant = false;
13357 function getInputs(body) {
13358 if (body.length != 1) return;
13359 var lastExpression = body[0].expression;
13360 var candidate = lastExpression.toWatch;
13361 if (candidate.length !== 1) return candidate;
13362 return candidate[0] !== lastExpression ? candidate : undefined;
13365 function isAssignable(ast) {
13366 return ast.type === AST.Identifier || ast.type === AST.MemberExpression;
13369 function assignableAST(ast) {
13370 if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) {
13371 return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='};
13375 function isLiteral(ast) {
13376 return ast.body.length === 0 ||
13377 ast.body.length === 1 && (
13378 ast.body[0].expression.type === AST.Literal ||
13379 ast.body[0].expression.type === AST.ArrayExpression ||
13380 ast.body[0].expression.type === AST.ObjectExpression);
13383 function isConstant(ast) {
13384 return ast.constant;
13387 function ASTCompiler(astBuilder, $filter) {
13388 this.astBuilder = astBuilder;
13389 this.$filter = $filter;
13392 ASTCompiler.prototype = {
13393 compile: function(expression, expensiveChecks) {
13395 var ast = this.astBuilder.ast(expression);
13399 expensiveChecks: expensiveChecks,
13400 fn: {vars: [], body: [], own: {}},
13401 assign: {vars: [], body: [], own: {}},
13404 findConstantAndWatchExpressions(ast, self.$filter);
13407 this.stage = 'assign';
13408 if ((assignable = assignableAST(ast))) {
13409 this.state.computing = 'assign';
13410 var result = this.nextId();
13411 this.recurse(assignable, result);
13412 this.return_(result);
13413 extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l');
13415 var toWatch = getInputs(ast.body);
13416 self.stage = 'inputs';
13417 forEach(toWatch, function(watch, key) {
13418 var fnKey = 'fn' + key;
13419 self.state[fnKey] = {vars: [], body: [], own: {}};
13420 self.state.computing = fnKey;
13421 var intoId = self.nextId();
13422 self.recurse(watch, intoId);
13423 self.return_(intoId);
13424 self.state.inputs.push(fnKey);
13425 watch.watchId = key;
13427 this.state.computing = 'fn';
13428 this.stage = 'main';
13431 // The build and minification steps remove the string "use strict" from the code, but this is done using a regex.
13432 // This is a workaround for this until we do a better job at only removing the prefix only when we should.
13433 '"' + this.USE + ' ' + this.STRICT + '";\n' +
13434 this.filterPrefix() +
13435 'var fn=' + this.generateFunction('fn', 's,l,a,i') +
13441 var fn = (new Function('$filter',
13442 'ensureSafeMemberName',
13443 'ensureSafeObject',
13444 'ensureSafeFunction',
13446 'ensureSafeAssignContext',
13452 ensureSafeMemberName,
13454 ensureSafeFunction,
13456 ensureSafeAssignContext,
13461 this.state = this.stage = undefined;
13462 fn.literal = isLiteral(ast);
13463 fn.constant = isConstant(ast);
13471 watchFns: function() {
13473 var fns = this.state.inputs;
13475 forEach(fns, function(name) {
13476 result.push('var ' + name + '=' + self.generateFunction(name, 's'));
13479 result.push('fn.inputs=[' + fns.join(',') + '];');
13481 return result.join('');
13484 generateFunction: function(name, params) {
13485 return 'function(' + params + '){' +
13486 this.varsPrefix(name) +
13491 filterPrefix: function() {
13494 forEach(this.state.filters, function(id, filter) {
13495 parts.push(id + '=$filter(' + self.escape(filter) + ')');
13497 if (parts.length) return 'var ' + parts.join(',') + ';';
13501 varsPrefix: function(section) {
13502 return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : '';
13505 body: function(section) {
13506 return this.state[section].body.join('');
13509 recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
13510 var left, right, self = this, args, expression;
13511 recursionFn = recursionFn || noop;
13512 if (!skipWatchIdCheck && isDefined(ast.watchId)) {
13513 intoId = intoId || this.nextId();
13515 this.lazyAssign(intoId, this.computedMember('i', ast.watchId)),
13516 this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true)
13520 switch (ast.type) {
13522 forEach(ast.body, function(expression, pos) {
13523 self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; });
13524 if (pos !== ast.body.length - 1) {
13525 self.current().body.push(right, ';');
13527 self.return_(right);
13532 expression = this.escape(ast.value);
13533 this.assign(intoId, expression);
13534 recursionFn(expression);
13536 case AST.UnaryExpression:
13537 this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; });
13538 expression = ast.operator + '(' + this.ifDefined(right, 0) + ')';
13539 this.assign(intoId, expression);
13540 recursionFn(expression);
13542 case AST.BinaryExpression:
13543 this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; });
13544 this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; });
13545 if (ast.operator === '+') {
13546 expression = this.plus(left, right);
13547 } else if (ast.operator === '-') {
13548 expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0);
13550 expression = '(' + left + ')' + ast.operator + '(' + right + ')';
13552 this.assign(intoId, expression);
13553 recursionFn(expression);
13555 case AST.LogicalExpression:
13556 intoId = intoId || this.nextId();
13557 self.recurse(ast.left, intoId);
13558 self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId));
13559 recursionFn(intoId);
13561 case AST.ConditionalExpression:
13562 intoId = intoId || this.nextId();
13563 self.recurse(ast.test, intoId);
13564 self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId));
13565 recursionFn(intoId);
13567 case AST.Identifier:
13568 intoId = intoId || this.nextId();
13570 nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s');
13571 nameId.computed = false;
13572 nameId.name = ast.name;
13574 ensureSafeMemberName(ast.name);
13575 self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)),
13577 self.if_(self.stage === 'inputs' || 's', function() {
13578 if (create && create !== 1) {
13580 self.not(self.nonComputedMember('s', ast.name)),
13581 self.lazyAssign(self.nonComputedMember('s', ast.name), '{}'));
13583 self.assign(intoId, self.nonComputedMember('s', ast.name));
13585 }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name))
13587 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) {
13588 self.addEnsureSafeObject(intoId);
13590 recursionFn(intoId);
13592 case AST.MemberExpression:
13593 left = nameId && (nameId.context = this.nextId()) || this.nextId();
13594 intoId = intoId || this.nextId();
13595 self.recurse(ast.object, left, undefined, function() {
13596 self.if_(self.notNull(left), function() {
13597 if (ast.computed) {
13598 right = self.nextId();
13599 self.recurse(ast.property, right);
13600 self.getStringValue(right);
13601 self.addEnsureSafeMemberName(right);
13602 if (create && create !== 1) {
13603 self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}'));
13605 expression = self.ensureSafeObject(self.computedMember(left, right));
13606 self.assign(intoId, expression);
13608 nameId.computed = true;
13609 nameId.name = right;
13612 ensureSafeMemberName(ast.property.name);
13613 if (create && create !== 1) {
13614 self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}'));
13616 expression = self.nonComputedMember(left, ast.property.name);
13617 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) {
13618 expression = self.ensureSafeObject(expression);
13620 self.assign(intoId, expression);
13622 nameId.computed = false;
13623 nameId.name = ast.property.name;
13627 self.assign(intoId, 'undefined');
13629 recursionFn(intoId);
13632 case AST.CallExpression:
13633 intoId = intoId || this.nextId();
13635 right = self.filter(ast.callee.name);
13637 forEach(ast.arguments, function(expr) {
13638 var argument = self.nextId();
13639 self.recurse(expr, argument);
13640 args.push(argument);
13642 expression = right + '(' + args.join(',') + ')';
13643 self.assign(intoId, expression);
13644 recursionFn(intoId);
13646 right = self.nextId();
13649 self.recurse(ast.callee, right, left, function() {
13650 self.if_(self.notNull(right), function() {
13651 self.addEnsureSafeFunction(right);
13652 forEach(ast.arguments, function(expr) {
13653 self.recurse(expr, self.nextId(), undefined, function(argument) {
13654 args.push(self.ensureSafeObject(argument));
13658 if (!self.state.expensiveChecks) {
13659 self.addEnsureSafeObject(left.context);
13661 expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')';
13663 expression = right + '(' + args.join(',') + ')';
13665 expression = self.ensureSafeObject(expression);
13666 self.assign(intoId, expression);
13668 self.assign(intoId, 'undefined');
13670 recursionFn(intoId);
13674 case AST.AssignmentExpression:
13675 right = this.nextId();
13677 if (!isAssignable(ast.left)) {
13678 throw $parseMinErr('lval', 'Trying to assing a value to a non l-value');
13680 this.recurse(ast.left, undefined, left, function() {
13681 self.if_(self.notNull(left.context), function() {
13682 self.recurse(ast.right, right);
13683 self.addEnsureSafeObject(self.member(left.context, left.name, left.computed));
13684 self.addEnsureSafeAssignContext(left.context);
13685 expression = self.member(left.context, left.name, left.computed) + ast.operator + right;
13686 self.assign(intoId, expression);
13687 recursionFn(intoId || expression);
13691 case AST.ArrayExpression:
13693 forEach(ast.elements, function(expr) {
13694 self.recurse(expr, self.nextId(), undefined, function(argument) {
13695 args.push(argument);
13698 expression = '[' + args.join(',') + ']';
13699 this.assign(intoId, expression);
13700 recursionFn(expression);
13702 case AST.ObjectExpression:
13704 forEach(ast.properties, function(property) {
13705 self.recurse(property.value, self.nextId(), undefined, function(expr) {
13706 args.push(self.escape(
13707 property.key.type === AST.Identifier ? property.key.name :
13708 ('' + property.key.value)) +
13712 expression = '{' + args.join(',') + '}';
13713 this.assign(intoId, expression);
13714 recursionFn(expression);
13716 case AST.ThisExpression:
13717 this.assign(intoId, 's');
13720 case AST.NGValueParameter:
13721 this.assign(intoId, 'v');
13727 getHasOwnProperty: function(element, property) {
13728 var key = element + '.' + property;
13729 var own = this.current().own;
13730 if (!own.hasOwnProperty(key)) {
13731 own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')');
13736 assign: function(id, value) {
13738 this.current().body.push(id, '=', value, ';');
13742 filter: function(filterName) {
13743 if (!this.state.filters.hasOwnProperty(filterName)) {
13744 this.state.filters[filterName] = this.nextId(true);
13746 return this.state.filters[filterName];
13749 ifDefined: function(id, defaultValue) {
13750 return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')';
13753 plus: function(left, right) {
13754 return 'plus(' + left + ',' + right + ')';
13757 return_: function(id) {
13758 this.current().body.push('return ', id, ';');
13761 if_: function(test, alternate, consequent) {
13762 if (test === true) {
13765 var body = this.current().body;
13766 body.push('if(', test, '){');
13770 body.push('else{');
13777 not: function(expression) {
13778 return '!(' + expression + ')';
13781 notNull: function(expression) {
13782 return expression + '!=null';
13785 nonComputedMember: function(left, right) {
13786 return left + '.' + right;
13789 computedMember: function(left, right) {
13790 return left + '[' + right + ']';
13793 member: function(left, right, computed) {
13794 if (computed) return this.computedMember(left, right);
13795 return this.nonComputedMember(left, right);
13798 addEnsureSafeObject: function(item) {
13799 this.current().body.push(this.ensureSafeObject(item), ';');
13802 addEnsureSafeMemberName: function(item) {
13803 this.current().body.push(this.ensureSafeMemberName(item), ';');
13806 addEnsureSafeFunction: function(item) {
13807 this.current().body.push(this.ensureSafeFunction(item), ';');
13810 addEnsureSafeAssignContext: function(item) {
13811 this.current().body.push(this.ensureSafeAssignContext(item), ';');
13814 ensureSafeObject: function(item) {
13815 return 'ensureSafeObject(' + item + ',text)';
13818 ensureSafeMemberName: function(item) {
13819 return 'ensureSafeMemberName(' + item + ',text)';
13822 ensureSafeFunction: function(item) {
13823 return 'ensureSafeFunction(' + item + ',text)';
13826 getStringValue: function(item) {
13827 this.assign(item, 'getStringValue(' + item + ',text)');
13830 ensureSafeAssignContext: function(item) {
13831 return 'ensureSafeAssignContext(' + item + ',text)';
13834 lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
13836 return function() {
13837 self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck);
13841 lazyAssign: function(id, value) {
13843 return function() {
13844 self.assign(id, value);
13848 stringEscapeRegex: /[^ a-zA-Z0-9]/g,
13850 stringEscapeFn: function(c) {
13851 return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4);
13854 escape: function(value) {
13855 if (isString(value)) return "'" + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + "'";
13856 if (isNumber(value)) return value.toString();
13857 if (value === true) return 'true';
13858 if (value === false) return 'false';
13859 if (value === null) return 'null';
13860 if (typeof value === 'undefined') return 'undefined';
13862 throw $parseMinErr('esc', 'IMPOSSIBLE');
13865 nextId: function(skip, init) {
13866 var id = 'v' + (this.state.nextId++);
13868 this.current().vars.push(id + (init ? '=' + init : ''));
13873 current: function() {
13874 return this.state[this.state.computing];
13879 function ASTInterpreter(astBuilder, $filter) {
13880 this.astBuilder = astBuilder;
13881 this.$filter = $filter;
13884 ASTInterpreter.prototype = {
13885 compile: function(expression, expensiveChecks) {
13887 var ast = this.astBuilder.ast(expression);
13888 this.expression = expression;
13889 this.expensiveChecks = expensiveChecks;
13890 findConstantAndWatchExpressions(ast, self.$filter);
13893 if ((assignable = assignableAST(ast))) {
13894 assign = this.recurse(assignable);
13896 var toWatch = getInputs(ast.body);
13900 forEach(toWatch, function(watch, key) {
13901 var input = self.recurse(watch);
13902 watch.input = input;
13903 inputs.push(input);
13904 watch.watchId = key;
13907 var expressions = [];
13908 forEach(ast.body, function(expression) {
13909 expressions.push(self.recurse(expression.expression));
13911 var fn = ast.body.length === 0 ? function() {} :
13912 ast.body.length === 1 ? expressions[0] :
13913 function(scope, locals) {
13915 forEach(expressions, function(exp) {
13916 lastValue = exp(scope, locals);
13921 fn.assign = function(scope, value, locals) {
13922 return assign(scope, locals, value);
13926 fn.inputs = inputs;
13928 fn.literal = isLiteral(ast);
13929 fn.constant = isConstant(ast);
13933 recurse: function(ast, context, create) {
13934 var left, right, self = this, args, expression;
13936 return this.inputs(ast.input, ast.watchId);
13938 switch (ast.type) {
13940 return this.value(ast.value, context);
13941 case AST.UnaryExpression:
13942 right = this.recurse(ast.argument);
13943 return this['unary' + ast.operator](right, context);
13944 case AST.BinaryExpression:
13945 left = this.recurse(ast.left);
13946 right = this.recurse(ast.right);
13947 return this['binary' + ast.operator](left, right, context);
13948 case AST.LogicalExpression:
13949 left = this.recurse(ast.left);
13950 right = this.recurse(ast.right);
13951 return this['binary' + ast.operator](left, right, context);
13952 case AST.ConditionalExpression:
13953 return this['ternary?:'](
13954 this.recurse(ast.test),
13955 this.recurse(ast.alternate),
13956 this.recurse(ast.consequent),
13959 case AST.Identifier:
13960 ensureSafeMemberName(ast.name, self.expression);
13961 return self.identifier(ast.name,
13962 self.expensiveChecks || isPossiblyDangerousMemberName(ast.name),
13963 context, create, self.expression);
13964 case AST.MemberExpression:
13965 left = this.recurse(ast.object, false, !!create);
13966 if (!ast.computed) {
13967 ensureSafeMemberName(ast.property.name, self.expression);
13968 right = ast.property.name;
13970 if (ast.computed) right = this.recurse(ast.property);
13971 return ast.computed ?
13972 this.computedMember(left, right, context, create, self.expression) :
13973 this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression);
13974 case AST.CallExpression:
13976 forEach(ast.arguments, function(expr) {
13977 args.push(self.recurse(expr));
13979 if (ast.filter) right = this.$filter(ast.callee.name);
13980 if (!ast.filter) right = this.recurse(ast.callee, true);
13981 return ast.filter ?
13982 function(scope, locals, assign, inputs) {
13984 for (var i = 0; i < args.length; ++i) {
13985 values.push(args[i](scope, locals, assign, inputs));
13987 var value = right.apply(undefined, values, inputs);
13988 return context ? {context: undefined, name: undefined, value: value} : value;
13990 function(scope, locals, assign, inputs) {
13991 var rhs = right(scope, locals, assign, inputs);
13993 if (rhs.value != null) {
13994 ensureSafeObject(rhs.context, self.expression);
13995 ensureSafeFunction(rhs.value, self.expression);
13997 for (var i = 0; i < args.length; ++i) {
13998 values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression));
14000 value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression);
14002 return context ? {value: value} : value;
14004 case AST.AssignmentExpression:
14005 left = this.recurse(ast.left, true, 1);
14006 right = this.recurse(ast.right);
14007 return function(scope, locals, assign, inputs) {
14008 var lhs = left(scope, locals, assign, inputs);
14009 var rhs = right(scope, locals, assign, inputs);
14010 ensureSafeObject(lhs.value, self.expression);
14011 ensureSafeAssignContext(lhs.context);
14012 lhs.context[lhs.name] = rhs;
14013 return context ? {value: rhs} : rhs;
14015 case AST.ArrayExpression:
14017 forEach(ast.elements, function(expr) {
14018 args.push(self.recurse(expr));
14020 return function(scope, locals, assign, inputs) {
14022 for (var i = 0; i < args.length; ++i) {
14023 value.push(args[i](scope, locals, assign, inputs));
14025 return context ? {value: value} : value;
14027 case AST.ObjectExpression:
14029 forEach(ast.properties, function(property) {
14030 args.push({key: property.key.type === AST.Identifier ?
14031 property.key.name :
14032 ('' + property.key.value),
14033 value: self.recurse(property.value)
14036 return function(scope, locals, assign, inputs) {
14038 for (var i = 0; i < args.length; ++i) {
14039 value[args[i].key] = args[i].value(scope, locals, assign, inputs);
14041 return context ? {value: value} : value;
14043 case AST.ThisExpression:
14044 return function(scope) {
14045 return context ? {value: scope} : scope;
14047 case AST.NGValueParameter:
14048 return function(scope, locals, assign, inputs) {
14049 return context ? {value: assign} : assign;
14054 'unary+': function(argument, context) {
14055 return function(scope, locals, assign, inputs) {
14056 var arg = argument(scope, locals, assign, inputs);
14057 if (isDefined(arg)) {
14062 return context ? {value: arg} : arg;
14065 'unary-': function(argument, context) {
14066 return function(scope, locals, assign, inputs) {
14067 var arg = argument(scope, locals, assign, inputs);
14068 if (isDefined(arg)) {
14073 return context ? {value: arg} : arg;
14076 'unary!': function(argument, context) {
14077 return function(scope, locals, assign, inputs) {
14078 var arg = !argument(scope, locals, assign, inputs);
14079 return context ? {value: arg} : arg;
14082 'binary+': function(left, right, context) {
14083 return function(scope, locals, assign, inputs) {
14084 var lhs = left(scope, locals, assign, inputs);
14085 var rhs = right(scope, locals, assign, inputs);
14086 var arg = plusFn(lhs, rhs);
14087 return context ? {value: arg} : arg;
14090 'binary-': function(left, right, context) {
14091 return function(scope, locals, assign, inputs) {
14092 var lhs = left(scope, locals, assign, inputs);
14093 var rhs = right(scope, locals, assign, inputs);
14094 var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0);
14095 return context ? {value: arg} : arg;
14098 'binary*': function(left, right, context) {
14099 return function(scope, locals, assign, inputs) {
14100 var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs);
14101 return context ? {value: arg} : arg;
14104 'binary/': function(left, right, context) {
14105 return function(scope, locals, assign, inputs) {
14106 var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs);
14107 return context ? {value: arg} : arg;
14110 'binary%': function(left, right, context) {
14111 return function(scope, locals, assign, inputs) {
14112 var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs);
14113 return context ? {value: arg} : arg;
14116 'binary===': function(left, right, context) {
14117 return function(scope, locals, assign, inputs) {
14118 var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs);
14119 return context ? {value: arg} : arg;
14122 'binary!==': function(left, right, context) {
14123 return function(scope, locals, assign, inputs) {
14124 var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs);
14125 return context ? {value: arg} : arg;
14128 'binary==': function(left, right, context) {
14129 return function(scope, locals, assign, inputs) {
14130 var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs);
14131 return context ? {value: arg} : arg;
14134 'binary!=': function(left, right, context) {
14135 return function(scope, locals, assign, inputs) {
14136 var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs);
14137 return context ? {value: arg} : arg;
14140 'binary<': function(left, right, context) {
14141 return function(scope, locals, assign, inputs) {
14142 var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs);
14143 return context ? {value: arg} : arg;
14146 'binary>': function(left, right, context) {
14147 return function(scope, locals, assign, inputs) {
14148 var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs);
14149 return context ? {value: arg} : arg;
14152 'binary<=': function(left, right, context) {
14153 return function(scope, locals, assign, inputs) {
14154 var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs);
14155 return context ? {value: arg} : arg;
14158 'binary>=': function(left, right, context) {
14159 return function(scope, locals, assign, inputs) {
14160 var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs);
14161 return context ? {value: arg} : arg;
14164 'binary&&': function(left, right, context) {
14165 return function(scope, locals, assign, inputs) {
14166 var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs);
14167 return context ? {value: arg} : arg;
14170 'binary||': function(left, right, context) {
14171 return function(scope, locals, assign, inputs) {
14172 var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs);
14173 return context ? {value: arg} : arg;
14176 'ternary?:': function(test, alternate, consequent, context) {
14177 return function(scope, locals, assign, inputs) {
14178 var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs);
14179 return context ? {value: arg} : arg;
14182 value: function(value, context) {
14183 return function() { return context ? {context: undefined, name: undefined, value: value} : value; };
14185 identifier: function(name, expensiveChecks, context, create, expression) {
14186 return function(scope, locals, assign, inputs) {
14187 var base = locals && (name in locals) ? locals : scope;
14188 if (create && create !== 1 && base && !(base[name])) {
14191 var value = base ? base[name] : undefined;
14192 if (expensiveChecks) {
14193 ensureSafeObject(value, expression);
14196 return {context: base, name: name, value: value};
14202 computedMember: function(left, right, context, create, expression) {
14203 return function(scope, locals, assign, inputs) {
14204 var lhs = left(scope, locals, assign, inputs);
14208 rhs = right(scope, locals, assign, inputs);
14209 rhs = getStringValue(rhs);
14210 ensureSafeMemberName(rhs, expression);
14211 if (create && create !== 1 && lhs && !(lhs[rhs])) {
14215 ensureSafeObject(value, expression);
14218 return {context: lhs, name: rhs, value: value};
14224 nonComputedMember: function(left, right, expensiveChecks, context, create, expression) {
14225 return function(scope, locals, assign, inputs) {
14226 var lhs = left(scope, locals, assign, inputs);
14227 if (create && create !== 1 && lhs && !(lhs[right])) {
14230 var value = lhs != null ? lhs[right] : undefined;
14231 if (expensiveChecks || isPossiblyDangerousMemberName(right)) {
14232 ensureSafeObject(value, expression);
14235 return {context: lhs, name: right, value: value};
14241 inputs: function(input, watchId) {
14242 return function(scope, value, locals, inputs) {
14243 if (inputs) return inputs[watchId];
14244 return input(scope, value, locals);
14252 var Parser = function(lexer, $filter, options) {
14253 this.lexer = lexer;
14254 this.$filter = $filter;
14255 this.options = options;
14256 this.ast = new AST(this.lexer);
14257 this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) :
14258 new ASTCompiler(this.ast, $filter);
14261 Parser.prototype = {
14262 constructor: Parser,
14264 parse: function(text) {
14265 return this.astCompiler.compile(text, this.options.expensiveChecks);
14269 var getterFnCacheDefault = createMap();
14270 var getterFnCacheExpensive = createMap();
14272 function isPossiblyDangerousMemberName(name) {
14273 return name == 'constructor';
14276 var objectValueOf = Object.prototype.valueOf;
14278 function getValueOf(value) {
14279 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value);
14282 ///////////////////////////////////
14291 * Converts Angular {@link guide/expression expression} into a function.
14294 * var getter = $parse('user.name');
14295 * var setter = getter.assign;
14296 * var context = {user:{name:'angular'}};
14297 * var locals = {user:{name:'local'}};
14299 * expect(getter(context)).toEqual('angular');
14300 * setter(context, 'newValue');
14301 * expect(context.user.name).toEqual('newValue');
14302 * expect(getter(context, locals)).toEqual('local');
14306 * @param {string} expression String expression to compile.
14307 * @returns {function(context, locals)} a function which represents the compiled expression:
14309 * * `context` – `{object}` – an object against which any expressions embedded in the strings
14310 * are evaluated against (typically a scope object).
14311 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
14314 * The returned function also has the following properties:
14315 * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript
14317 * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript
14318 * constant literals.
14319 * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be
14320 * set to a function to change its value on the given context.
14327 * @name $parseProvider
14330 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse}
14333 function $ParseProvider() {
14334 var cacheDefault = createMap();
14335 var cacheExpensive = createMap();
14337 this.$get = ['$filter', function($filter) {
14338 var noUnsafeEval = csp().noUnsafeEval;
14339 var $parseOptions = {
14341 expensiveChecks: false
14343 $parseOptionsExpensive = {
14345 expensiveChecks: true
14348 return function $parse(exp, interceptorFn, expensiveChecks) {
14349 var parsedExpression, oneTime, cacheKey;
14351 switch (typeof exp) {
14356 var cache = (expensiveChecks ? cacheExpensive : cacheDefault);
14357 parsedExpression = cache[cacheKey];
14359 if (!parsedExpression) {
14360 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {
14362 exp = exp.substring(2);
14364 var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions;
14365 var lexer = new Lexer(parseOptions);
14366 var parser = new Parser(lexer, $filter, parseOptions);
14367 parsedExpression = parser.parse(exp);
14368 if (parsedExpression.constant) {
14369 parsedExpression.$$watchDelegate = constantWatchDelegate;
14370 } else if (oneTime) {
14371 parsedExpression.$$watchDelegate = parsedExpression.literal ?
14372 oneTimeLiteralWatchDelegate : oneTimeWatchDelegate;
14373 } else if (parsedExpression.inputs) {
14374 parsedExpression.$$watchDelegate = inputsWatchDelegate;
14376 cache[cacheKey] = parsedExpression;
14378 return addInterceptor(parsedExpression, interceptorFn);
14381 return addInterceptor(exp, interceptorFn);
14388 function expressionInputDirtyCheck(newValue, oldValueOfValue) {
14390 if (newValue == null || oldValueOfValue == null) { // null/undefined
14391 return newValue === oldValueOfValue;
14394 if (typeof newValue === 'object') {
14396 // attempt to convert the value to a primitive type
14397 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can
14398 // be cheaply dirty-checked
14399 newValue = getValueOf(newValue);
14401 if (typeof newValue === 'object') {
14402 // objects/arrays are not supported - deep-watching them would be too expensive
14406 // fall-through to the primitive equality check
14410 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue);
14413 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) {
14414 var inputExpressions = parsedExpression.inputs;
14417 if (inputExpressions.length === 1) {
14418 var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails
14419 inputExpressions = inputExpressions[0];
14420 return scope.$watch(function expressionInputWatch(scope) {
14421 var newInputValue = inputExpressions(scope);
14422 if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) {
14423 lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]);
14424 oldInputValueOf = newInputValue && getValueOf(newInputValue);
14427 }, listener, objectEquality, prettyPrintExpression);
14430 var oldInputValueOfValues = [];
14431 var oldInputValues = [];
14432 for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
14433 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails
14434 oldInputValues[i] = null;
14437 return scope.$watch(function expressionInputsWatch(scope) {
14438 var changed = false;
14440 for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
14441 var newInputValue = inputExpressions[i](scope);
14442 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) {
14443 oldInputValues[i] = newInputValue;
14444 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue);
14449 lastResult = parsedExpression(scope, undefined, undefined, oldInputValues);
14453 }, listener, objectEquality, prettyPrintExpression);
14456 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14457 var unwatch, lastValue;
14458 return unwatch = scope.$watch(function oneTimeWatch(scope) {
14459 return parsedExpression(scope);
14460 }, function oneTimeListener(value, old, scope) {
14462 if (isFunction(listener)) {
14463 listener.apply(this, arguments);
14465 if (isDefined(value)) {
14466 scope.$$postDigest(function() {
14467 if (isDefined(lastValue)) {
14472 }, objectEquality);
14475 function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14476 var unwatch, lastValue;
14477 return unwatch = scope.$watch(function oneTimeWatch(scope) {
14478 return parsedExpression(scope);
14479 }, function oneTimeListener(value, old, scope) {
14481 if (isFunction(listener)) {
14482 listener.call(this, value, old, scope);
14484 if (isAllDefined(value)) {
14485 scope.$$postDigest(function() {
14486 if (isAllDefined(lastValue)) unwatch();
14489 }, objectEquality);
14491 function isAllDefined(value) {
14492 var allDefined = true;
14493 forEach(value, function(val) {
14494 if (!isDefined(val)) allDefined = false;
14500 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14502 return unwatch = scope.$watch(function constantWatch(scope) {
14503 return parsedExpression(scope);
14504 }, function constantListener(value, old, scope) {
14505 if (isFunction(listener)) {
14506 listener.apply(this, arguments);
14509 }, objectEquality);
14512 function addInterceptor(parsedExpression, interceptorFn) {
14513 if (!interceptorFn) return parsedExpression;
14514 var watchDelegate = parsedExpression.$$watchDelegate;
14515 var useInputs = false;
14518 watchDelegate !== oneTimeLiteralWatchDelegate &&
14519 watchDelegate !== oneTimeWatchDelegate;
14521 var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) {
14522 var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs);
14523 return interceptorFn(value, scope, locals);
14524 } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) {
14525 var value = parsedExpression(scope, locals, assign, inputs);
14526 var result = interceptorFn(value, scope, locals);
14527 // we only return the interceptor's result if the
14528 // initial value is defined (for bind-once)
14529 return isDefined(value) ? result : value;
14532 // Propagate $$watchDelegates other then inputsWatchDelegate
14533 if (parsedExpression.$$watchDelegate &&
14534 parsedExpression.$$watchDelegate !== inputsWatchDelegate) {
14535 fn.$$watchDelegate = parsedExpression.$$watchDelegate;
14536 } else if (!interceptorFn.$stateful) {
14537 // If there is an interceptor, but no watchDelegate then treat the interceptor like
14538 // we treat filters - it is assumed to be a pure function unless flagged with $stateful
14539 fn.$$watchDelegate = inputsWatchDelegate;
14540 useInputs = !parsedExpression.inputs;
14541 fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression];
14552 * @requires $rootScope
14555 * A service that helps you run functions asynchronously, and use their return values (or exceptions)
14556 * when they are done processing.
14558 * This is an implementation of promises/deferred objects inspired by
14559 * [Kris Kowal's Q](https://github.com/kriskowal/q).
14561 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred
14562 * implementations, and the other which resembles ES6 promises to some degree.
14566 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver`
14567 * function as the first argument. This is similar to the native Promise implementation from ES6 Harmony,
14568 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
14570 * While the constructor-style use is supported, not all of the supporting methods from ES6 Harmony promises are
14573 * It can be used like so:
14576 * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
14577 * // are available in the current lexical scope (they could have been injected or passed in).
14579 * function asyncGreet(name) {
14580 * // perform some asynchronous operation, resolve or reject the promise when appropriate.
14581 * return $q(function(resolve, reject) {
14582 * setTimeout(function() {
14583 * if (okToGreet(name)) {
14584 * resolve('Hello, ' + name + '!');
14586 * reject('Greeting ' + name + ' is not allowed.');
14592 * var promise = asyncGreet('Robin Hood');
14593 * promise.then(function(greeting) {
14594 * alert('Success: ' + greeting);
14595 * }, function(reason) {
14596 * alert('Failed: ' + reason);
14600 * Note: progress/notify callbacks are not currently supported via the ES6-style interface.
14602 * Note: unlike ES6 behaviour, an exception thrown in the constructor function will NOT implicitly reject the promise.
14604 * However, the more traditional CommonJS-style usage is still available, and documented below.
14606 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
14607 * interface for interacting with an object that represents the result of an action that is
14608 * performed asynchronously, and may or may not be finished at any given point in time.
14610 * From the perspective of dealing with error handling, deferred and promise APIs are to
14611 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
14614 * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
14615 * // are available in the current lexical scope (they could have been injected or passed in).
14617 * function asyncGreet(name) {
14618 * var deferred = $q.defer();
14620 * setTimeout(function() {
14621 * deferred.notify('About to greet ' + name + '.');
14623 * if (okToGreet(name)) {
14624 * deferred.resolve('Hello, ' + name + '!');
14626 * deferred.reject('Greeting ' + name + ' is not allowed.');
14630 * return deferred.promise;
14633 * var promise = asyncGreet('Robin Hood');
14634 * promise.then(function(greeting) {
14635 * alert('Success: ' + greeting);
14636 * }, function(reason) {
14637 * alert('Failed: ' + reason);
14638 * }, function(update) {
14639 * alert('Got notification: ' + update);
14643 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff
14644 * comes in the way of guarantees that promise and deferred APIs make, see
14645 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md.
14647 * Additionally the promise api allows for composition that is very hard to do with the
14648 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
14649 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the
14650 * section on serial or parallel joining of promises.
14652 * # The Deferred API
14654 * A new instance of deferred is constructed by calling `$q.defer()`.
14656 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs
14657 * that can be used for signaling the successful or unsuccessful completion, as well as the status
14662 * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection
14663 * constructed via `$q.reject`, the promise will be rejected instead.
14664 * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
14665 * resolving it with a rejection constructed via `$q.reject`.
14666 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called
14667 * multiple times before the promise is either resolved or rejected.
14671 * - promise – `{Promise}` – promise object associated with this deferred.
14674 * # The Promise API
14676 * A new promise instance is created when a deferred instance is created and can be retrieved by
14677 * calling `deferred.promise`.
14679 * The purpose of the promise object is to allow for interested parties to get access to the result
14680 * of the deferred task when it completes.
14684 * - `then(successCallback, errorCallback, notifyCallback)` – regardless of when the promise was or
14685 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously
14686 * as soon as the result is available. The callbacks are called with a single argument: the result
14687 * or rejection reason. Additionally, the notify callback may be called zero or more times to
14688 * provide a progress indication, before the promise is resolved or rejected.
14690 * This method *returns a new promise* which is resolved or rejected via the return value of the
14691 * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
14692 * with the value which is resolved in that promise using
14693 * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)).
14694 * It also notifies via the return value of the `notifyCallback` method. The promise cannot be
14695 * resolved or rejected from the notifyCallback method.
14697 * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)`
14699 * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise,
14700 * but to do so without modifying the final value. This is useful to release resources or do some
14701 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full
14702 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for
14703 * more information.
14705 * # Chaining promises
14707 * Because calling the `then` method of a promise returns a new derived promise, it is easily
14708 * possible to create a chain of promises:
14711 * promiseB = promiseA.then(function(result) {
14712 * return result + 1;
14715 * // promiseB will be resolved immediately after promiseA is resolved and its value
14716 * // will be the result of promiseA incremented by 1
14719 * It is possible to create chains of any length and since a promise can be resolved with another
14720 * promise (which will defer its resolution further), it is possible to pause/defer resolution of
14721 * the promises at any point in the chain. This makes it possible to implement powerful APIs like
14722 * $http's response interceptors.
14725 * # Differences between Kris Kowal's Q and $q
14727 * There are two main differences:
14729 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
14730 * mechanism in angular, which means faster propagation of resolution or rejection into your
14731 * models and avoiding unnecessary browser repaints, which would result in flickering UI.
14732 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
14733 * all the important functionality needed for common async tasks.
14738 * it('should simulate promise', inject(function($q, $rootScope) {
14739 * var deferred = $q.defer();
14740 * var promise = deferred.promise;
14741 * var resolvedValue;
14743 * promise.then(function(value) { resolvedValue = value; });
14744 * expect(resolvedValue).toBeUndefined();
14746 * // Simulate resolving of promise
14747 * deferred.resolve(123);
14748 * // Note that the 'then' function does not get called synchronously.
14749 * // This is because we want the promise API to always be async, whether or not
14750 * // it got called synchronously or asynchronously.
14751 * expect(resolvedValue).toBeUndefined();
14753 * // Propagate promise resolution to 'then' functions using $apply().
14754 * $rootScope.$apply();
14755 * expect(resolvedValue).toEqual(123);
14759 * @param {function(function, function)} resolver Function which is responsible for resolving or
14760 * rejecting the newly created promise. The first parameter is a function which resolves the
14761 * promise, the second parameter is a function which rejects the promise.
14763 * @returns {Promise} The newly created promise.
14765 function $QProvider() {
14767 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
14768 return qFactory(function(callback) {
14769 $rootScope.$evalAsync(callback);
14770 }, $exceptionHandler);
14774 function $$QProvider() {
14775 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) {
14776 return qFactory(function(callback) {
14777 $browser.defer(callback);
14778 }, $exceptionHandler);
14783 * Constructs a promise manager.
14785 * @param {function(function)} nextTick Function for executing functions in the next turn.
14786 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for
14787 * debugging purposes.
14788 * @returns {object} Promise manager.
14790 function qFactory(nextTick, exceptionHandler) {
14791 var $qMinErr = minErr('$q', TypeError);
14792 function callOnce(self, resolveFn, rejectFn) {
14793 var called = false;
14794 function wrap(fn) {
14795 return function(value) {
14796 if (called) return;
14798 fn.call(self, value);
14802 return [wrap(resolveFn), wrap(rejectFn)];
14807 * @name ng.$q#defer
14811 * Creates a `Deferred` object which represents a task which will finish in the future.
14813 * @returns {Deferred} Returns a new instance of deferred.
14815 var defer = function() {
14816 return new Deferred();
14819 function Promise() {
14820 this.$$state = { status: 0 };
14823 extend(Promise.prototype, {
14824 then: function(onFulfilled, onRejected, progressBack) {
14825 if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) {
14828 var result = new Deferred();
14830 this.$$state.pending = this.$$state.pending || [];
14831 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);
14832 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state);
14834 return result.promise;
14837 "catch": function(callback) {
14838 return this.then(null, callback);
14841 "finally": function(callback, progressBack) {
14842 return this.then(function(value) {
14843 return handleCallback(value, true, callback);
14844 }, function(error) {
14845 return handleCallback(error, false, callback);
14850 //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native
14851 function simpleBind(context, fn) {
14852 return function(value) {
14853 fn.call(context, value);
14857 function processQueue(state) {
14858 var fn, deferred, pending;
14860 pending = state.pending;
14861 state.processScheduled = false;
14862 state.pending = undefined;
14863 for (var i = 0, ii = pending.length; i < ii; ++i) {
14864 deferred = pending[i][0];
14865 fn = pending[i][state.status];
14867 if (isFunction(fn)) {
14868 deferred.resolve(fn(state.value));
14869 } else if (state.status === 1) {
14870 deferred.resolve(state.value);
14872 deferred.reject(state.value);
14875 deferred.reject(e);
14876 exceptionHandler(e);
14881 function scheduleProcessQueue(state) {
14882 if (state.processScheduled || !state.pending) return;
14883 state.processScheduled = true;
14884 nextTick(function() { processQueue(state); });
14887 function Deferred() {
14888 this.promise = new Promise();
14889 //Necessary to support unbound execution :/
14890 this.resolve = simpleBind(this, this.resolve);
14891 this.reject = simpleBind(this, this.reject);
14892 this.notify = simpleBind(this, this.notify);
14895 extend(Deferred.prototype, {
14896 resolve: function(val) {
14897 if (this.promise.$$state.status) return;
14898 if (val === this.promise) {
14899 this.$$reject($qMinErr(
14901 "Expected promise to be resolved with value other than itself '{0}'",
14904 this.$$resolve(val);
14909 $$resolve: function(val) {
14912 fns = callOnce(this, this.$$resolve, this.$$reject);
14914 if ((isObject(val) || isFunction(val))) then = val && val.then;
14915 if (isFunction(then)) {
14916 this.promise.$$state.status = -1;
14917 then.call(val, fns[0], fns[1], this.notify);
14919 this.promise.$$state.value = val;
14920 this.promise.$$state.status = 1;
14921 scheduleProcessQueue(this.promise.$$state);
14925 exceptionHandler(e);
14929 reject: function(reason) {
14930 if (this.promise.$$state.status) return;
14931 this.$$reject(reason);
14934 $$reject: function(reason) {
14935 this.promise.$$state.value = reason;
14936 this.promise.$$state.status = 2;
14937 scheduleProcessQueue(this.promise.$$state);
14940 notify: function(progress) {
14941 var callbacks = this.promise.$$state.pending;
14943 if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) {
14944 nextTick(function() {
14945 var callback, result;
14946 for (var i = 0, ii = callbacks.length; i < ii; i++) {
14947 result = callbacks[i][0];
14948 callback = callbacks[i][3];
14950 result.notify(isFunction(callback) ? callback(progress) : progress);
14952 exceptionHandler(e);
14966 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be
14967 * used to forward rejection in a chain of promises. If you are dealing with the last promise in
14968 * a promise chain, you don't need to worry about it.
14970 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of
14971 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via
14972 * a promise error callback and you want to forward the error to the promise derived from the
14973 * current promise, you have to "rethrow" the error by returning a rejection constructed via
14977 * promiseB = promiseA.then(function(result) {
14978 * // success: do something and resolve promiseB
14979 * // with the old or a new result
14981 * }, function(reason) {
14982 * // error: handle the error if possible and
14983 * // resolve promiseB with newPromiseOrValue,
14984 * // otherwise forward the rejection to promiseB
14985 * if (canHandle(reason)) {
14986 * // handle the error and recover
14987 * return newPromiseOrValue;
14989 * return $q.reject(reason);
14993 * @param {*} reason Constant, message, exception or an object representing the rejection reason.
14994 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
14996 var reject = function(reason) {
14997 var result = new Deferred();
14998 result.reject(reason);
14999 return result.promise;
15002 var makePromise = function makePromise(value, resolved) {
15003 var result = new Deferred();
15005 result.resolve(value);
15007 result.reject(value);
15009 return result.promise;
15012 var handleCallback = function handleCallback(value, isResolved, callback) {
15013 var callbackOutput = null;
15015 if (isFunction(callback)) callbackOutput = callback();
15017 return makePromise(e, false);
15019 if (isPromiseLike(callbackOutput)) {
15020 return callbackOutput.then(function() {
15021 return makePromise(value, isResolved);
15022 }, function(error) {
15023 return makePromise(error, false);
15026 return makePromise(value, isResolved);
15036 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
15037 * This is useful when you are dealing with an object that might or might not be a promise, or if
15038 * the promise comes from a source that can't be trusted.
15040 * @param {*} value Value or a promise
15041 * @param {Function=} successCallback
15042 * @param {Function=} errorCallback
15043 * @param {Function=} progressCallback
15044 * @returns {Promise} Returns a promise of the passed value or promise
15048 var when = function(value, callback, errback, progressBack) {
15049 var result = new Deferred();
15050 result.resolve(value);
15051 return result.promise.then(callback, errback, progressBack);
15060 * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6.
15062 * @param {*} value Value or a promise
15063 * @param {Function=} successCallback
15064 * @param {Function=} errorCallback
15065 * @param {Function=} progressCallback
15066 * @returns {Promise} Returns a promise of the passed value or promise
15068 var resolve = when;
15076 * Combines multiple promises into a single promise that is resolved when all of the input
15077 * promises are resolved.
15079 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
15080 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values,
15081 * each value corresponding to the promise at the same index/key in the `promises` array/hash.
15082 * If any of the promises is resolved with a rejection, this resulting promise will be rejected
15083 * with the same rejection value.
15086 function all(promises) {
15087 var deferred = new Deferred(),
15089 results = isArray(promises) ? [] : {};
15091 forEach(promises, function(promise, key) {
15093 when(promise).then(function(value) {
15094 if (results.hasOwnProperty(key)) return;
15095 results[key] = value;
15096 if (!(--counter)) deferred.resolve(results);
15097 }, function(reason) {
15098 if (results.hasOwnProperty(key)) return;
15099 deferred.reject(reason);
15103 if (counter === 0) {
15104 deferred.resolve(results);
15107 return deferred.promise;
15110 var $Q = function Q(resolver) {
15111 if (!isFunction(resolver)) {
15112 throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver);
15115 if (!(this instanceof Q)) {
15116 // More useful when $Q is the Promise itself.
15117 return new Q(resolver);
15120 var deferred = new Deferred();
15122 function resolveFn(value) {
15123 deferred.resolve(value);
15126 function rejectFn(reason) {
15127 deferred.reject(reason);
15130 resolver(resolveFn, rejectFn);
15132 return deferred.promise;
15136 $Q.reject = reject;
15138 $Q.resolve = resolve;
15144 function $$RAFProvider() { //rAF
15145 this.$get = ['$window', '$timeout', function($window, $timeout) {
15146 var requestAnimationFrame = $window.requestAnimationFrame ||
15147 $window.webkitRequestAnimationFrame;
15149 var cancelAnimationFrame = $window.cancelAnimationFrame ||
15150 $window.webkitCancelAnimationFrame ||
15151 $window.webkitCancelRequestAnimationFrame;
15153 var rafSupported = !!requestAnimationFrame;
15154 var raf = rafSupported
15156 var id = requestAnimationFrame(fn);
15157 return function() {
15158 cancelAnimationFrame(id);
15162 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666
15163 return function() {
15164 $timeout.cancel(timer);
15168 raf.supported = rafSupported;
15177 * The design decisions behind the scope are heavily favored for speed and memory consumption.
15179 * The typical use of scope is to watch the expressions, which most of the time return the same
15180 * value as last time so we optimize the operation.
15182 * Closures construction is expensive in terms of speed as well as memory:
15183 * - No closures, instead use prototypical inheritance for API
15184 * - Internal state needs to be stored on scope directly, which means that private state is
15185 * exposed as $$____ properties
15187 * Loop operations are optimized by using while(count--) { ... }
15188 * - This means that in order to keep the same order of execution as addition we have to add
15189 * items to the array at the beginning (unshift) instead of at the end (push)
15191 * Child scopes are created and removed often
15192 * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists
15194 * There are fewer watches than observers. This is why you don't want the observer to be implemented
15195 * in the same way as watch. Watch requires return of the initialization function which is expensive
15202 * @name $rootScopeProvider
15205 * Provider for the $rootScope service.
15210 * @name $rootScopeProvider#digestTtl
15213 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and
15214 * assuming that the model is unstable.
15216 * The current default is 10 iterations.
15218 * In complex applications it's possible that the dependencies between `$watch`s will result in
15219 * several digest iterations. However if an application needs more than the default 10 digest
15220 * iterations for its model to stabilize then you should investigate what is causing the model to
15221 * continuously change during the digest.
15223 * Increasing the TTL could have performance implications, so you should not change it without
15224 * proper justification.
15226 * @param {number} limit The number of digest iterations.
15235 * Every application has a single root {@link ng.$rootScope.Scope scope}.
15236 * All other scopes are descendant scopes of the root scope. Scopes provide separation
15237 * between the model and the view, via a mechanism for watching the model for changes.
15238 * They also provide event emission/broadcast and subscription facility. See the
15239 * {@link guide/scope developer guide on scopes}.
15241 function $RootScopeProvider() {
15243 var $rootScopeMinErr = minErr('$rootScope');
15244 var lastDirtyWatch = null;
15245 var applyAsyncId = null;
15247 this.digestTtl = function(value) {
15248 if (arguments.length) {
15254 function createChildScopeClass(parent) {
15255 function ChildScope() {
15256 this.$$watchers = this.$$nextSibling =
15257 this.$$childHead = this.$$childTail = null;
15258 this.$$listeners = {};
15259 this.$$listenerCount = {};
15260 this.$$watchersCount = 0;
15261 this.$id = nextUid();
15262 this.$$ChildScope = null;
15264 ChildScope.prototype = parent;
15268 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser',
15269 function($injector, $exceptionHandler, $parse, $browser) {
15271 function destroyChildScope($event) {
15272 $event.currentScope.$$destroyed = true;
15275 function cleanUpScope($scope) {
15278 // There is a memory leak in IE9 if all child scopes are not disconnected
15279 // completely when a scope is destroyed. So this code will recurse up through
15280 // all this scopes children
15282 // See issue https://github.com/angular/angular.js/issues/10706
15283 $scope.$$childHead && cleanUpScope($scope.$$childHead);
15284 $scope.$$nextSibling && cleanUpScope($scope.$$nextSibling);
15287 // The code below works around IE9 and V8's memory leaks
15290 // - https://code.google.com/p/v8/issues/detail?id=2073#c26
15291 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909
15292 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
15294 $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead =
15295 $scope.$$childTail = $scope.$root = $scope.$$watchers = null;
15300 * @name $rootScope.Scope
15303 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
15304 * {@link auto.$injector $injector}. Child scopes are created using the
15305 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
15306 * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for
15307 * an in-depth introduction and usage examples.
15311 * A scope can inherit from a parent scope, as in this example:
15313 var parent = $rootScope;
15314 var child = parent.$new();
15316 parent.salutation = "Hello";
15317 expect(child.salutation).toEqual('Hello');
15319 child.salutation = "Welcome";
15320 expect(child.salutation).toEqual('Welcome');
15321 expect(parent.salutation).toEqual('Hello');
15324 * When interacting with `Scope` in tests, additional helper methods are available on the
15325 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional
15329 * @param {Object.<string, function()>=} providers Map of service factory which need to be
15330 * provided for the current scope. Defaults to {@link ng}.
15331 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should
15332 * append/override services provided by `providers`. This is handy
15333 * when unit-testing and having the need to override a default
15335 * @returns {Object} Newly created scope.
15339 this.$id = nextUid();
15340 this.$$phase = this.$parent = this.$$watchers =
15341 this.$$nextSibling = this.$$prevSibling =
15342 this.$$childHead = this.$$childTail = null;
15344 this.$$destroyed = false;
15345 this.$$listeners = {};
15346 this.$$listenerCount = {};
15347 this.$$watchersCount = 0;
15348 this.$$isolateBindings = null;
15353 * @name $rootScope.Scope#$id
15356 * Unique scope ID (monotonically increasing) useful for debugging.
15361 * @name $rootScope.Scope#$parent
15364 * Reference to the parent scope.
15369 * @name $rootScope.Scope#$root
15372 * Reference to the root scope.
15375 Scope.prototype = {
15376 constructor: Scope,
15379 * @name $rootScope.Scope#$new
15383 * Creates a new child {@link ng.$rootScope.Scope scope}.
15385 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event.
15386 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
15388 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is
15389 * desired for the scope and its child scopes to be permanently detached from the parent and
15390 * thus stop participating in model change detection and listener notification by invoking.
15392 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the
15393 * parent scope. The scope is isolated, as it can not see parent scope properties.
15394 * When creating widgets, it is useful for the widget to not accidentally read parent
15397 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent`
15398 * of the newly created scope. Defaults to `this` scope if not provided.
15399 * This is used when creating a transclude scope to correctly place it
15400 * in the scope hierarchy while maintaining the correct prototypical
15403 * @returns {Object} The newly created child scope.
15406 $new: function(isolate, parent) {
15409 parent = parent || this;
15412 child = new Scope();
15413 child.$root = this.$root;
15415 // Only create a child scope class if somebody asks for one,
15416 // but cache it to allow the VM to optimize lookups.
15417 if (!this.$$ChildScope) {
15418 this.$$ChildScope = createChildScopeClass(this);
15420 child = new this.$$ChildScope();
15422 child.$parent = parent;
15423 child.$$prevSibling = parent.$$childTail;
15424 if (parent.$$childHead) {
15425 parent.$$childTail.$$nextSibling = child;
15426 parent.$$childTail = child;
15428 parent.$$childHead = parent.$$childTail = child;
15431 // When the new scope is not isolated or we inherit from `this`, and
15432 // the parent scope is destroyed, the property `$$destroyed` is inherited
15433 // prototypically. In all other cases, this property needs to be set
15434 // when the parent scope is destroyed.
15435 // The listener needs to be added after the parent is set
15436 if (isolate || parent != this) child.$on('$destroy', destroyChildScope);
15443 * @name $rootScope.Scope#$watch
15447 * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
15449 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest
15450 * $digest()} and should return the value that will be watched. (`watchExpression` should not change
15451 * its value when executed multiple times with the same input because it may be executed multiple
15452 * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be
15453 * [idempotent](http://en.wikipedia.org/wiki/Idempotence).
15454 * - The `listener` is called only when the value from the current `watchExpression` and the
15455 * previous call to `watchExpression` are not equal (with the exception of the initial run,
15456 * see below). Inequality is determined according to reference inequality,
15457 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators)
15458 * via the `!==` Javascript operator, unless `objectEquality == true`
15460 * - When `objectEquality == true`, inequality of the `watchExpression` is determined
15461 * according to the {@link angular.equals} function. To save the value of the object for
15462 * later comparison, the {@link angular.copy} function is used. This therefore means that
15463 * watching complex objects will have adverse memory and performance implications.
15464 * - The watch `listener` may change the model, which may trigger other `listener`s to fire.
15465 * This is achieved by rerunning the watchers until no changes are detected. The rerun
15466 * iteration limit is 10 to prevent an infinite loop deadlock.
15469 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
15470 * you can register a `watchExpression` function with no `listener`. (Be prepared for
15471 * multiple calls to your `watchExpression` because it will execute multiple times in a
15472 * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.)
15474 * After a watcher is registered with the scope, the `listener` fn is called asynchronously
15475 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
15476 * watcher. In rare cases, this is undesirable because the listener is called when the result
15477 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
15478 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the
15479 * listener was called due to initialization.
15485 // let's assume that scope was dependency injected as the $rootScope
15486 var scope = $rootScope;
15487 scope.name = 'misko';
15490 expect(scope.counter).toEqual(0);
15491 scope.$watch('name', function(newValue, oldValue) {
15492 scope.counter = scope.counter + 1;
15494 expect(scope.counter).toEqual(0);
15497 // the listener is always called during the first $digest loop after it was registered
15498 expect(scope.counter).toEqual(1);
15501 // but now it will not be called unless the value changes
15502 expect(scope.counter).toEqual(1);
15504 scope.name = 'adam';
15506 expect(scope.counter).toEqual(2);
15510 // Using a function as a watchExpression
15512 scope.foodCounter = 0;
15513 expect(scope.foodCounter).toEqual(0);
15515 // This function returns the value being watched. It is called for each turn of the $digest loop
15516 function() { return food; },
15517 // This is the change listener, called when the value returned from the above function changes
15518 function(newValue, oldValue) {
15519 if ( newValue !== oldValue ) {
15520 // Only increment the counter if the value changed
15521 scope.foodCounter = scope.foodCounter + 1;
15525 // No digest has been run so the counter will be zero
15526 expect(scope.foodCounter).toEqual(0);
15528 // Run the digest but since food has not changed count will still be zero
15530 expect(scope.foodCounter).toEqual(0);
15532 // Update food and run digest. Now the counter will increment
15533 food = 'cheeseburger';
15535 expect(scope.foodCounter).toEqual(1);
15541 * @param {(function()|string)} watchExpression Expression that is evaluated on each
15542 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers
15543 * a call to the `listener`.
15545 * - `string`: Evaluated as {@link guide/expression expression}
15546 * - `function(scope)`: called with current `scope` as a parameter.
15547 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value
15548 * of `watchExpression` changes.
15550 * - `newVal` contains the current value of the `watchExpression`
15551 * - `oldVal` contains the previous value of the `watchExpression`
15552 * - `scope` refers to the current scope
15553 * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of
15554 * comparing for reference equality.
15555 * @returns {function()} Returns a deregistration function for this listener.
15557 $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) {
15558 var get = $parse(watchExp);
15560 if (get.$$watchDelegate) {
15561 return get.$$watchDelegate(this, listener, objectEquality, get, watchExp);
15564 array = scope.$$watchers,
15567 last: initWatchVal,
15569 exp: prettyPrintExpression || watchExp,
15570 eq: !!objectEquality
15573 lastDirtyWatch = null;
15575 if (!isFunction(listener)) {
15580 array = scope.$$watchers = [];
15582 // we use unshift since we use a while loop in $digest for speed.
15583 // the while loop reads in reverse order.
15584 array.unshift(watcher);
15585 incrementWatchersCount(this, 1);
15587 return function deregisterWatch() {
15588 if (arrayRemove(array, watcher) >= 0) {
15589 incrementWatchersCount(scope, -1);
15591 lastDirtyWatch = null;
15597 * @name $rootScope.Scope#$watchGroup
15601 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`.
15602 * If any one expression in the collection changes the `listener` is executed.
15604 * - The items in the `watchExpressions` array are observed via standard $watch operation and are examined on every
15605 * call to $digest() to see if any items changes.
15606 * - The `listener` is called whenever any expression in the `watchExpressions` array changes.
15608 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually
15609 * watched using {@link ng.$rootScope.Scope#$watch $watch()}
15611 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any
15612 * expression in `watchExpressions` changes
15613 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching
15614 * those of `watchExpression`
15615 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching
15616 * those of `watchExpression`
15617 * The `scope` refers to the current scope.
15618 * @returns {function()} Returns a de-registration function for all listeners.
15620 $watchGroup: function(watchExpressions, listener) {
15621 var oldValues = new Array(watchExpressions.length);
15622 var newValues = new Array(watchExpressions.length);
15623 var deregisterFns = [];
15625 var changeReactionScheduled = false;
15626 var firstRun = true;
15628 if (!watchExpressions.length) {
15629 // No expressions means we call the listener ASAP
15630 var shouldCall = true;
15631 self.$evalAsync(function() {
15632 if (shouldCall) listener(newValues, newValues, self);
15634 return function deregisterWatchGroup() {
15635 shouldCall = false;
15639 if (watchExpressions.length === 1) {
15640 // Special case size of one
15641 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) {
15642 newValues[0] = value;
15643 oldValues[0] = oldValue;
15644 listener(newValues, (value === oldValue) ? newValues : oldValues, scope);
15648 forEach(watchExpressions, function(expr, i) {
15649 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) {
15650 newValues[i] = value;
15651 oldValues[i] = oldValue;
15652 if (!changeReactionScheduled) {
15653 changeReactionScheduled = true;
15654 self.$evalAsync(watchGroupAction);
15657 deregisterFns.push(unwatchFn);
15660 function watchGroupAction() {
15661 changeReactionScheduled = false;
15665 listener(newValues, newValues, self);
15667 listener(newValues, oldValues, self);
15671 return function deregisterWatchGroup() {
15672 while (deregisterFns.length) {
15673 deregisterFns.shift()();
15681 * @name $rootScope.Scope#$watchCollection
15685 * Shallow watches the properties of an object and fires whenever any of the properties change
15686 * (for arrays, this implies watching the array items; for object maps, this implies watching
15687 * the properties). If a change is detected, the `listener` callback is fired.
15689 * - The `obj` collection is observed via standard $watch operation and is examined on every
15690 * call to $digest() to see if any items have been added, removed, or moved.
15691 * - The `listener` is called whenever anything within the `obj` has changed. Examples include
15692 * adding, removing, and moving items belonging to an object or array.
15697 $scope.names = ['igor', 'matias', 'misko', 'james'];
15698 $scope.dataCount = 4;
15700 $scope.$watchCollection('names', function(newNames, oldNames) {
15701 $scope.dataCount = newNames.length;
15704 expect($scope.dataCount).toEqual(4);
15707 //still at 4 ... no changes
15708 expect($scope.dataCount).toEqual(4);
15710 $scope.names.pop();
15713 //now there's been a change
15714 expect($scope.dataCount).toEqual(3);
15718 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The
15719 * expression value should evaluate to an object or an array which is observed on each
15720 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the
15721 * collection will trigger a call to the `listener`.
15723 * @param {function(newCollection, oldCollection, scope)} listener a callback function called
15724 * when a change is detected.
15725 * - The `newCollection` object is the newly modified data obtained from the `obj` expression
15726 * - The `oldCollection` object is a copy of the former collection data.
15727 * Due to performance considerations, the`oldCollection` value is computed only if the
15728 * `listener` function declares two or more arguments.
15729 * - The `scope` argument refers to the current scope.
15731 * @returns {function()} Returns a de-registration function for this listener. When the
15732 * de-registration function is executed, the internal watch operation is terminated.
15734 $watchCollection: function(obj, listener) {
15735 $watchCollectionInterceptor.$stateful = true;
15738 // the current value, updated on each dirty-check run
15740 // a shallow copy of the newValue from the last dirty-check run,
15741 // updated to match newValue during dirty-check run
15743 // a shallow copy of the newValue from when the last change happened
15745 // only track veryOldValue if the listener is asking for it
15746 var trackVeryOldValue = (listener.length > 1);
15747 var changeDetected = 0;
15748 var changeDetector = $parse(obj, $watchCollectionInterceptor);
15749 var internalArray = [];
15750 var internalObject = {};
15751 var initRun = true;
15754 function $watchCollectionInterceptor(_value) {
15756 var newLength, key, bothNaN, newItem, oldItem;
15758 // If the new value is undefined, then return undefined as the watch may be a one-time watch
15759 if (isUndefined(newValue)) return;
15761 if (!isObject(newValue)) { // if primitive
15762 if (oldValue !== newValue) {
15763 oldValue = newValue;
15766 } else if (isArrayLike(newValue)) {
15767 if (oldValue !== internalArray) {
15768 // we are transitioning from something which was not an array into array.
15769 oldValue = internalArray;
15770 oldLength = oldValue.length = 0;
15774 newLength = newValue.length;
15776 if (oldLength !== newLength) {
15777 // if lengths do not match we need to trigger change notification
15779 oldValue.length = oldLength = newLength;
15781 // copy the items to oldValue and look for changes.
15782 for (var i = 0; i < newLength; i++) {
15783 oldItem = oldValue[i];
15784 newItem = newValue[i];
15786 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
15787 if (!bothNaN && (oldItem !== newItem)) {
15789 oldValue[i] = newItem;
15793 if (oldValue !== internalObject) {
15794 // we are transitioning from something which was not an object into object.
15795 oldValue = internalObject = {};
15799 // copy the items to oldValue and look for changes.
15801 for (key in newValue) {
15802 if (hasOwnProperty.call(newValue, key)) {
15804 newItem = newValue[key];
15805 oldItem = oldValue[key];
15807 if (key in oldValue) {
15808 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
15809 if (!bothNaN && (oldItem !== newItem)) {
15811 oldValue[key] = newItem;
15815 oldValue[key] = newItem;
15820 if (oldLength > newLength) {
15821 // we used to have more keys, need to find them and destroy them.
15823 for (key in oldValue) {
15824 if (!hasOwnProperty.call(newValue, key)) {
15826 delete oldValue[key];
15831 return changeDetected;
15834 function $watchCollectionAction() {
15837 listener(newValue, newValue, self);
15839 listener(newValue, veryOldValue, self);
15842 // make a copy for the next time a collection is changed
15843 if (trackVeryOldValue) {
15844 if (!isObject(newValue)) {
15846 veryOldValue = newValue;
15847 } else if (isArrayLike(newValue)) {
15848 veryOldValue = new Array(newValue.length);
15849 for (var i = 0; i < newValue.length; i++) {
15850 veryOldValue[i] = newValue[i];
15852 } else { // if object
15854 for (var key in newValue) {
15855 if (hasOwnProperty.call(newValue, key)) {
15856 veryOldValue[key] = newValue[key];
15863 return this.$watch(changeDetector, $watchCollectionAction);
15868 * @name $rootScope.Scope#$digest
15872 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and
15873 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change
15874 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers}
15875 * until no more listeners are firing. This means that it is possible to get into an infinite
15876 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of
15877 * iterations exceeds 10.
15879 * Usually, you don't call `$digest()` directly in
15880 * {@link ng.directive:ngController controllers} or in
15881 * {@link ng.$compileProvider#directive directives}.
15882 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within
15883 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`.
15885 * If you want to be notified whenever `$digest()` is called,
15886 * you can register a `watchExpression` function with
15887 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`.
15889 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle.
15894 scope.name = 'misko';
15897 expect(scope.counter).toEqual(0);
15898 scope.$watch('name', function(newValue, oldValue) {
15899 scope.counter = scope.counter + 1;
15901 expect(scope.counter).toEqual(0);
15904 // the listener is always called during the first $digest loop after it was registered
15905 expect(scope.counter).toEqual(1);
15908 // but now it will not be called unless the value changes
15909 expect(scope.counter).toEqual(1);
15911 scope.name = 'adam';
15913 expect(scope.counter).toEqual(2);
15917 $digest: function() {
15918 var watch, value, last,
15922 next, current, target = this,
15924 logIdx, logMsg, asyncTask;
15926 beginPhase('$digest');
15927 // Check for changes to browser url that happened in sync before the call to $digest
15928 $browser.$$checkUrlChange();
15930 if (this === $rootScope && applyAsyncId !== null) {
15931 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then
15932 // cancel the scheduled $apply and flush the queue of expressions to be evaluated.
15933 $browser.defer.cancel(applyAsyncId);
15937 lastDirtyWatch = null;
15939 do { // "while dirty" loop
15943 while (asyncQueue.length) {
15945 asyncTask = asyncQueue.shift();
15946 asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals);
15948 $exceptionHandler(e);
15950 lastDirtyWatch = null;
15953 traverseScopesLoop:
15954 do { // "traverse the scopes" loop
15955 if ((watchers = current.$$watchers)) {
15956 // process our watches
15957 length = watchers.length;
15960 watch = watchers[length];
15961 // Most common watches are on primitives, in which case we can short
15962 // circuit it with === operator, only when === fails do we use .equals
15964 if ((value = watch.get(current)) !== (last = watch.last) &&
15966 ? equals(value, last)
15967 : (typeof value === 'number' && typeof last === 'number'
15968 && isNaN(value) && isNaN(last)))) {
15970 lastDirtyWatch = watch;
15971 watch.last = watch.eq ? copy(value, null) : value;
15972 watch.fn(value, ((last === initWatchVal) ? value : last), current);
15975 if (!watchLog[logIdx]) watchLog[logIdx] = [];
15976 watchLog[logIdx].push({
15977 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
15982 } else if (watch === lastDirtyWatch) {
15983 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers
15984 // have already been tested.
15986 break traverseScopesLoop;
15990 $exceptionHandler(e);
15995 // Insanity Warning: scope depth-first traversal
15996 // yes, this code is a bit crazy, but it works and we have tests to prove it!
15997 // this piece should be kept in sync with the traversal in $broadcast
15998 if (!(next = ((current.$$watchersCount && current.$$childHead) ||
15999 (current !== target && current.$$nextSibling)))) {
16000 while (current !== target && !(next = current.$$nextSibling)) {
16001 current = current.$parent;
16004 } while ((current = next));
16006 // `break traverseScopesLoop;` takes us to here
16008 if ((dirty || asyncQueue.length) && !(ttl--)) {
16010 throw $rootScopeMinErr('infdig',
16011 '{0} $digest() iterations reached. Aborting!\n' +
16012 'Watchers fired in the last 5 iterations: {1}',
16016 } while (dirty || asyncQueue.length);
16020 while (postDigestQueue.length) {
16022 postDigestQueue.shift()();
16024 $exceptionHandler(e);
16032 * @name $rootScope.Scope#$destroy
16033 * @eventType broadcast on scope being destroyed
16036 * Broadcasted when a scope and its children are being destroyed.
16038 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
16039 * clean up DOM bindings before an element is removed from the DOM.
16044 * @name $rootScope.Scope#$destroy
16048 * Removes the current scope (and all of its children) from the parent scope. Removal implies
16049 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
16050 * propagate to the current scope and its children. Removal also implies that the current
16051 * scope is eligible for garbage collection.
16053 * The `$destroy()` is usually used by directives such as
16054 * {@link ng.directive:ngRepeat ngRepeat} for managing the
16055 * unrolling of the loop.
16057 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope.
16058 * Application code can register a `$destroy` event handler that will give it a chance to
16059 * perform any necessary cleanup.
16061 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
16062 * clean up DOM bindings before an element is removed from the DOM.
16064 $destroy: function() {
16065 // We can't destroy a scope that has been already destroyed.
16066 if (this.$$destroyed) return;
16067 var parent = this.$parent;
16069 this.$broadcast('$destroy');
16070 this.$$destroyed = true;
16072 if (this === $rootScope) {
16073 //Remove handlers attached to window when $rootScope is removed
16074 $browser.$$applicationDestroyed();
16077 incrementWatchersCount(this, -this.$$watchersCount);
16078 for (var eventName in this.$$listenerCount) {
16079 decrementListenerCount(this, this.$$listenerCount[eventName], eventName);
16082 // sever all the references to parent scopes (after this cleanup, the current scope should
16083 // not be retained by any of our references and should be eligible for garbage collection)
16084 if (parent && parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
16085 if (parent && parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
16086 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
16087 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
16089 // Disable listeners, watchers and apply/digest methods
16090 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop;
16091 this.$on = this.$watch = this.$watchGroup = function() { return noop; };
16092 this.$$listeners = {};
16094 // Disconnect the next sibling to prevent `cleanUpScope` destroying those too
16095 this.$$nextSibling = null;
16096 cleanUpScope(this);
16101 * @name $rootScope.Scope#$eval
16105 * Executes the `expression` on the current scope and returns the result. Any exceptions in
16106 * the expression are propagated (uncaught). This is useful when evaluating Angular
16111 var scope = ng.$rootScope.Scope();
16115 expect(scope.$eval('a+b')).toEqual(3);
16116 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
16119 * @param {(string|function())=} expression An angular expression to be executed.
16121 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16122 * - `function(scope)`: execute the function with the current `scope` parameter.
16124 * @param {(object)=} locals Local variables object, useful for overriding values in scope.
16125 * @returns {*} The result of evaluating the expression.
16127 $eval: function(expr, locals) {
16128 return $parse(expr)(this, locals);
16133 * @name $rootScope.Scope#$evalAsync
16137 * Executes the expression on the current scope at a later point in time.
16139 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only
16142 * - it will execute after the function that scheduled the evaluation (preferably before DOM
16144 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after
16145 * `expression` execution.
16147 * Any exceptions from the execution of the expression are forwarded to the
16148 * {@link ng.$exceptionHandler $exceptionHandler} service.
16150 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle
16151 * will be scheduled. However, it is encouraged to always call code that changes the model
16152 * from within an `$apply` call. That includes code evaluated via `$evalAsync`.
16154 * @param {(string|function())=} expression An angular expression to be executed.
16156 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16157 * - `function(scope)`: execute the function with the current `scope` parameter.
16159 * @param {(object)=} locals Local variables object, useful for overriding values in scope.
16161 $evalAsync: function(expr, locals) {
16162 // if we are outside of an $digest loop and this is the first time we are scheduling async
16163 // task also schedule async auto-flush
16164 if (!$rootScope.$$phase && !asyncQueue.length) {
16165 $browser.defer(function() {
16166 if (asyncQueue.length) {
16167 $rootScope.$digest();
16172 asyncQueue.push({scope: this, expression: expr, locals: locals});
16175 $$postDigest: function(fn) {
16176 postDigestQueue.push(fn);
16181 * @name $rootScope.Scope#$apply
16185 * `$apply()` is used to execute an expression in angular from outside of the angular
16186 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries).
16187 * Because we are calling into the angular framework we need to perform proper scope life
16188 * cycle of {@link ng.$exceptionHandler exception handling},
16189 * {@link ng.$rootScope.Scope#$digest executing watches}.
16193 * # Pseudo-Code of `$apply()`
16195 function $apply(expr) {
16197 return $eval(expr);
16199 $exceptionHandler(e);
16207 * Scope's `$apply()` method transitions through the following stages:
16209 * 1. The {@link guide/expression expression} is executed using the
16210 * {@link ng.$rootScope.Scope#$eval $eval()} method.
16211 * 2. Any exceptions from the execution of the expression are forwarded to the
16212 * {@link ng.$exceptionHandler $exceptionHandler} service.
16213 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the
16214 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
16217 * @param {(string|function())=} exp An angular expression to be executed.
16219 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16220 * - `function(scope)`: execute the function with current `scope` parameter.
16222 * @returns {*} The result of evaluating the expression.
16224 $apply: function(expr) {
16226 beginPhase('$apply');
16228 return this.$eval(expr);
16233 $exceptionHandler(e);
16236 $rootScope.$digest();
16238 $exceptionHandler(e);
16246 * @name $rootScope.Scope#$applyAsync
16250 * Schedule the invocation of $apply to occur at a later time. The actual time difference
16251 * varies across browsers, but is typically around ~10 milliseconds.
16253 * This can be used to queue up multiple expressions which need to be evaluated in the same
16256 * @param {(string|function())=} exp An angular expression to be executed.
16258 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16259 * - `function(scope)`: execute the function with current `scope` parameter.
16261 $applyAsync: function(expr) {
16263 expr && applyAsyncQueue.push($applyAsyncExpression);
16264 scheduleApplyAsync();
16266 function $applyAsyncExpression() {
16273 * @name $rootScope.Scope#$on
16277 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for
16278 * discussion of event life cycle.
16280 * The event listener function format is: `function(event, args...)`. The `event` object
16281 * passed into the listener has the following attributes:
16283 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or
16285 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the
16286 * event propagates through the scope hierarchy, this property is set to null.
16287 * - `name` - `{string}`: name of the event.
16288 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel
16289 * further event propagation (available only for events that were `$emit`-ed).
16290 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag
16292 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
16294 * @param {string} name Event name to listen on.
16295 * @param {function(event, ...args)} listener Function to call when the event is emitted.
16296 * @returns {function()} Returns a deregistration function for this listener.
16298 $on: function(name, listener) {
16299 var namedListeners = this.$$listeners[name];
16300 if (!namedListeners) {
16301 this.$$listeners[name] = namedListeners = [];
16303 namedListeners.push(listener);
16305 var current = this;
16307 if (!current.$$listenerCount[name]) {
16308 current.$$listenerCount[name] = 0;
16310 current.$$listenerCount[name]++;
16311 } while ((current = current.$parent));
16314 return function() {
16315 var indexOfListener = namedListeners.indexOf(listener);
16316 if (indexOfListener !== -1) {
16317 namedListeners[indexOfListener] = null;
16318 decrementListenerCount(self, 1, name);
16326 * @name $rootScope.Scope#$emit
16330 * Dispatches an event `name` upwards through the scope hierarchy notifying the
16331 * registered {@link ng.$rootScope.Scope#$on} listeners.
16333 * The event life cycle starts at the scope on which `$emit` was called. All
16334 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
16335 * notified. Afterwards, the event traverses upwards toward the root scope and calls all
16336 * registered listeners along the way. The event will stop propagating if one of the listeners
16339 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
16340 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
16342 * @param {string} name Event name to emit.
16343 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
16344 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}).
16346 $emit: function(name, args) {
16350 stopPropagation = false,
16353 targetScope: scope,
16354 stopPropagation: function() {stopPropagation = true;},
16355 preventDefault: function() {
16356 event.defaultPrevented = true;
16358 defaultPrevented: false
16360 listenerArgs = concat([event], arguments, 1),
16364 namedListeners = scope.$$listeners[name] || empty;
16365 event.currentScope = scope;
16366 for (i = 0, length = namedListeners.length; i < length; i++) {
16368 // if listeners were deregistered, defragment the array
16369 if (!namedListeners[i]) {
16370 namedListeners.splice(i, 1);
16376 //allow all listeners attached to the current scope to run
16377 namedListeners[i].apply(null, listenerArgs);
16379 $exceptionHandler(e);
16382 //if any listener on the current scope stops propagation, prevent bubbling
16383 if (stopPropagation) {
16384 event.currentScope = null;
16388 scope = scope.$parent;
16391 event.currentScope = null;
16399 * @name $rootScope.Scope#$broadcast
16403 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the
16404 * registered {@link ng.$rootScope.Scope#$on} listeners.
16406 * The event life cycle starts at the scope on which `$broadcast` was called. All
16407 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
16408 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current
16409 * scope and calls all registered listeners along the way. The event cannot be canceled.
16411 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
16412 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
16414 * @param {string} name Event name to broadcast.
16415 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
16416 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
16418 $broadcast: function(name, args) {
16424 targetScope: target,
16425 preventDefault: function() {
16426 event.defaultPrevented = true;
16428 defaultPrevented: false
16431 if (!target.$$listenerCount[name]) return event;
16433 var listenerArgs = concat([event], arguments, 1),
16434 listeners, i, length;
16436 //down while you can, then up and next sibling or up and next sibling until back at root
16437 while ((current = next)) {
16438 event.currentScope = current;
16439 listeners = current.$$listeners[name] || [];
16440 for (i = 0, length = listeners.length; i < length; i++) {
16441 // if listeners were deregistered, defragment the array
16442 if (!listeners[i]) {
16443 listeners.splice(i, 1);
16450 listeners[i].apply(null, listenerArgs);
16452 $exceptionHandler(e);
16456 // Insanity Warning: scope depth-first traversal
16457 // yes, this code is a bit crazy, but it works and we have tests to prove it!
16458 // this piece should be kept in sync with the traversal in $digest
16459 // (though it differs due to having the extra check for $$listenerCount)
16460 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
16461 (current !== target && current.$$nextSibling)))) {
16462 while (current !== target && !(next = current.$$nextSibling)) {
16463 current = current.$parent;
16468 event.currentScope = null;
16473 var $rootScope = new Scope();
16475 //The internal queues. Expose them on the $rootScope for debugging/testing purposes.
16476 var asyncQueue = $rootScope.$$asyncQueue = [];
16477 var postDigestQueue = $rootScope.$$postDigestQueue = [];
16478 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = [];
16483 function beginPhase(phase) {
16484 if ($rootScope.$$phase) {
16485 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase);
16488 $rootScope.$$phase = phase;
16491 function clearPhase() {
16492 $rootScope.$$phase = null;
16495 function incrementWatchersCount(current, count) {
16497 current.$$watchersCount += count;
16498 } while ((current = current.$parent));
16501 function decrementListenerCount(current, count, name) {
16503 current.$$listenerCount[name] -= count;
16505 if (current.$$listenerCount[name] === 0) {
16506 delete current.$$listenerCount[name];
16508 } while ((current = current.$parent));
16512 * function used as an initial value for watchers.
16513 * because it's unique we can easily tell it apart from other values
16515 function initWatchVal() {}
16517 function flushApplyAsync() {
16518 while (applyAsyncQueue.length) {
16520 applyAsyncQueue.shift()();
16522 $exceptionHandler(e);
16525 applyAsyncId = null;
16528 function scheduleApplyAsync() {
16529 if (applyAsyncId === null) {
16530 applyAsyncId = $browser.defer(function() {
16531 $rootScope.$apply(flushApplyAsync);
16540 * Private service to sanitize uris for links and images. Used by $compile and $sanitize.
16542 function $$SanitizeUriProvider() {
16543 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/,
16544 imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/;
16548 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
16549 * urls during a[href] sanitization.
16551 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
16553 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
16554 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
16555 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
16556 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
16558 * @param {RegExp=} regexp New regexp to whitelist urls with.
16559 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
16560 * chaining otherwise.
16562 this.aHrefSanitizationWhitelist = function(regexp) {
16563 if (isDefined(regexp)) {
16564 aHrefSanitizationWhitelist = regexp;
16567 return aHrefSanitizationWhitelist;
16573 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
16574 * urls during img[src] sanitization.
16576 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
16578 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
16579 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
16580 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
16581 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
16583 * @param {RegExp=} regexp New regexp to whitelist urls with.
16584 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
16585 * chaining otherwise.
16587 this.imgSrcSanitizationWhitelist = function(regexp) {
16588 if (isDefined(regexp)) {
16589 imgSrcSanitizationWhitelist = regexp;
16592 return imgSrcSanitizationWhitelist;
16595 this.$get = function() {
16596 return function sanitizeUri(uri, isImage) {
16597 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
16599 normalizedVal = urlResolve(uri).href;
16600 if (normalizedVal !== '' && !normalizedVal.match(regex)) {
16601 return 'unsafe:' + normalizedVal;
16608 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
16609 * Any commits to this file should be reviewed with security in mind. *
16610 * Changes to this file can potentially create security vulnerabilities. *
16611 * An approval from 2 Core members with history of modifying *
16612 * this file is required. *
16614 * Does the change somehow allow for arbitrary javascript to be executed? *
16615 * Or allows for someone to change the prototype of built-in objects? *
16616 * Or gives undesired access to variables likes document or window? *
16617 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
16619 var $sceMinErr = minErr('$sce');
16621 var SCE_CONTEXTS = {
16625 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a
16626 // url. (e.g. ng-include, script src, templateUrl)
16627 RESOURCE_URL: 'resourceUrl',
16631 // Helper functions follow.
16633 function adjustMatcher(matcher) {
16634 if (matcher === 'self') {
16636 } else if (isString(matcher)) {
16637 // Strings match exactly except for 2 wildcards - '*' and '**'.
16638 // '*' matches any character except those from the set ':/.?&'.
16639 // '**' matches any character (like .* in a RegExp).
16640 // More than 2 *'s raises an error as it's ill defined.
16641 if (matcher.indexOf('***') > -1) {
16642 throw $sceMinErr('iwcard',
16643 'Illegal sequence *** in string matcher. String: {0}', matcher);
16645 matcher = escapeForRegexp(matcher).
16646 replace('\\*\\*', '.*').
16647 replace('\\*', '[^:/.?&;]*');
16648 return new RegExp('^' + matcher + '$');
16649 } else if (isRegExp(matcher)) {
16650 // The only other type of matcher allowed is a Regexp.
16651 // Match entire URL / disallow partial matches.
16652 // Flags are reset (i.e. no global, ignoreCase or multiline)
16653 return new RegExp('^' + matcher.source + '$');
16655 throw $sceMinErr('imatcher',
16656 'Matchers may only be "self", string patterns or RegExp objects');
16661 function adjustMatchers(matchers) {
16662 var adjustedMatchers = [];
16663 if (isDefined(matchers)) {
16664 forEach(matchers, function(matcher) {
16665 adjustedMatchers.push(adjustMatcher(matcher));
16668 return adjustedMatchers;
16674 * @name $sceDelegate
16679 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict
16680 * Contextual Escaping (SCE)} services to AngularJS.
16682 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of
16683 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is
16684 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to
16685 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things
16686 * work because `$sce` delegates to `$sceDelegate` for these operations.
16688 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service.
16690 * The default instance of `$sceDelegate` should work out of the box with little pain. While you
16691 * can override it completely to change the behavior of `$sce`, the common case would
16692 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting
16693 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as
16694 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist
16695 * $sceDelegateProvider.resourceUrlWhitelist} and {@link
16696 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
16701 * @name $sceDelegateProvider
16704 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate
16705 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure
16706 * that the URLs used for sourcing Angular templates are safe. Refer {@link
16707 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and
16708 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
16710 * For the general details about this service in Angular, read the main page for {@link ng.$sce
16711 * Strict Contextual Escaping (SCE)}.
16713 * **Example**: Consider the following case. <a name="example"></a>
16715 * - your app is hosted at url `http://myapp.example.com/`
16716 * - but some of your templates are hosted on other domains you control such as
16717 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc.
16718 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`.
16720 * Here is what a secure configuration for this scenario might look like:
16723 * angular.module('myApp', []).config(function($sceDelegateProvider) {
16724 * $sceDelegateProvider.resourceUrlWhitelist([
16725 * // Allow same origin resource loads.
16727 * // Allow loading from our assets domain. Notice the difference between * and **.
16728 * 'http://srv*.assets.example.com/**'
16731 * // The blacklist overrides the whitelist so the open redirect here is blocked.
16732 * $sceDelegateProvider.resourceUrlBlacklist([
16733 * 'http://myapp.example.com/clickThru**'
16739 function $SceDelegateProvider() {
16740 this.SCE_CONTEXTS = SCE_CONTEXTS;
16742 // Resource URLs can also be trusted by policy.
16743 var resourceUrlWhitelist = ['self'],
16744 resourceUrlBlacklist = [];
16748 * @name $sceDelegateProvider#resourceUrlWhitelist
16751 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value
16752 * provided. This must be an array or null. A snapshot of this array is used so further
16753 * changes to the array are ignored.
16755 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
16756 * allowed in this array.
16758 * Note: **an empty whitelist array will block all URLs**!
16760 * @return {Array} the currently set whitelist array.
16762 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only
16763 * same origin resource requests.
16766 * Sets/Gets the whitelist of trusted resource URLs.
16768 this.resourceUrlWhitelist = function(value) {
16769 if (arguments.length) {
16770 resourceUrlWhitelist = adjustMatchers(value);
16772 return resourceUrlWhitelist;
16777 * @name $sceDelegateProvider#resourceUrlBlacklist
16780 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value
16781 * provided. This must be an array or null. A snapshot of this array is used so further
16782 * changes to the array are ignored.
16784 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
16785 * allowed in this array.
16787 * The typical usage for the blacklist is to **block
16788 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as
16789 * these would otherwise be trusted but actually return content from the redirected domain.
16791 * Finally, **the blacklist overrides the whitelist** and has the final say.
16793 * @return {Array} the currently set blacklist array.
16795 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there
16796 * is no blacklist.)
16799 * Sets/Gets the blacklist of trusted resource URLs.
16802 this.resourceUrlBlacklist = function(value) {
16803 if (arguments.length) {
16804 resourceUrlBlacklist = adjustMatchers(value);
16806 return resourceUrlBlacklist;
16809 this.$get = ['$injector', function($injector) {
16811 var htmlSanitizer = function htmlSanitizer(html) {
16812 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
16815 if ($injector.has('$sanitize')) {
16816 htmlSanitizer = $injector.get('$sanitize');
16820 function matchUrl(matcher, parsedUrl) {
16821 if (matcher === 'self') {
16822 return urlIsSameOrigin(parsedUrl);
16824 // definitely a regex. See adjustMatchers()
16825 return !!matcher.exec(parsedUrl.href);
16829 function isResourceUrlAllowedByPolicy(url) {
16830 var parsedUrl = urlResolve(url.toString());
16831 var i, n, allowed = false;
16832 // Ensure that at least one item from the whitelist allows this url.
16833 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) {
16834 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) {
16840 // Ensure that no item from the blacklist blocked this url.
16841 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) {
16842 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) {
16851 function generateHolderType(Base) {
16852 var holderType = function TrustedValueHolderType(trustedValue) {
16853 this.$$unwrapTrustedValue = function() {
16854 return trustedValue;
16858 holderType.prototype = new Base();
16860 holderType.prototype.valueOf = function sceValueOf() {
16861 return this.$$unwrapTrustedValue();
16863 holderType.prototype.toString = function sceToString() {
16864 return this.$$unwrapTrustedValue().toString();
16869 var trustedValueHolderBase = generateHolderType(),
16872 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase);
16873 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase);
16874 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase);
16875 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase);
16876 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]);
16880 * @name $sceDelegate#trustAs
16883 * Returns an object that is trusted by angular for use in specified strict
16884 * contextual escaping contexts (such as ng-bind-html, ng-include, any src
16885 * attribute interpolation, any dom event binding attribute interpolation
16886 * such as for onclick, etc.) that uses the provided value.
16887 * See {@link ng.$sce $sce} for enabling strict contextual escaping.
16889 * @param {string} type The kind of context in which this value is safe for use. e.g. url,
16890 * resourceUrl, html, js and css.
16891 * @param {*} value The value that that should be considered trusted/safe.
16892 * @returns {*} A value that can be used to stand in for the provided `value` in places
16893 * where Angular expects a $sce.trustAs() return value.
16895 function trustAs(type, trustedValue) {
16896 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
16897 if (!Constructor) {
16898 throw $sceMinErr('icontext',
16899 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}',
16900 type, trustedValue);
16902 if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') {
16903 return trustedValue;
16905 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting
16906 // mutable objects, we ensure here that the value passed in is actually a string.
16907 if (typeof trustedValue !== 'string') {
16908 throw $sceMinErr('itype',
16909 'Attempted to trust a non-string value in a content requiring a string: Context: {0}',
16912 return new Constructor(trustedValue);
16917 * @name $sceDelegate#valueOf
16920 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs
16921 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link
16922 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}.
16924 * If the passed parameter is not a value that had been returned by {@link
16925 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is.
16927 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}
16928 * call or anything else.
16929 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs
16930 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns
16931 * `value` unchanged.
16933 function valueOf(maybeTrusted) {
16934 if (maybeTrusted instanceof trustedValueHolderBase) {
16935 return maybeTrusted.$$unwrapTrustedValue();
16937 return maybeTrusted;
16943 * @name $sceDelegate#getTrusted
16946 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and
16947 * returns the originally supplied value if the queried context type is a supertype of the
16948 * created type. If this condition isn't satisfied, throws an exception.
16950 * @param {string} type The kind of context in which this value is to be used.
16951 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs
16952 * `$sceDelegate.trustAs`} call.
16953 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs
16954 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception.
16956 function getTrusted(type, maybeTrusted) {
16957 if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') {
16958 return maybeTrusted;
16960 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
16961 if (constructor && maybeTrusted instanceof constructor) {
16962 return maybeTrusted.$$unwrapTrustedValue();
16964 // If we get here, then we may only take one of two actions.
16965 // 1. sanitize the value for the requested type, or
16966 // 2. throw an exception.
16967 if (type === SCE_CONTEXTS.RESOURCE_URL) {
16968 if (isResourceUrlAllowedByPolicy(maybeTrusted)) {
16969 return maybeTrusted;
16971 throw $sceMinErr('insecurl',
16972 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}',
16973 maybeTrusted.toString());
16975 } else if (type === SCE_CONTEXTS.HTML) {
16976 return htmlSanitizer(maybeTrusted);
16978 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
16981 return { trustAs: trustAs,
16982 getTrusted: getTrusted,
16983 valueOf: valueOf };
16990 * @name $sceProvider
16993 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service.
16994 * - enable/disable Strict Contextual Escaping (SCE) in a module
16995 * - override the default implementation with a custom delegate
16997 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}.
17000 /* jshint maxlen: false*/
17009 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS.
17011 * # Strict Contextual Escaping
17013 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain
17014 * contexts to result in a value that is marked as safe to use for that context. One example of
17015 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer
17016 * to these contexts as privileged or SCE contexts.
17018 * As of version 1.2, Angular ships with SCE enabled by default.
17020 * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow
17021 * one to execute arbitrary javascript by the use of the expression() syntax. Refer
17022 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
17023 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>`
17024 * to the top of your HTML document.
17026 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for
17027 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier.
17029 * Here's an example of a binding in a privileged context:
17032 * <input ng-model="userHtml" aria-label="User input">
17033 * <div ng-bind-html="userHtml"></div>
17036 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE
17037 * disabled, this application allows the user to render arbitrary HTML into the DIV.
17038 * In a more realistic example, one may be rendering user comments, blog articles, etc. via
17039 * bindings. (HTML is just one example of a context where rendering user controlled input creates
17040 * security vulnerabilities.)
17042 * For the case of HTML, you might use a library, either on the client side, or on the server side,
17043 * to sanitize unsafe HTML before binding to the value and rendering it in the document.
17045 * How would you ensure that every place that used these types of bindings was bound to a value that
17046 * was sanitized by your library (or returned as safe for rendering by your server?) How can you
17047 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
17048 * properties/fields and forgot to update the binding to the sanitized value?
17050 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can
17051 * determine that something explicitly says it's safe to use a value for binding in that
17052 * context. You can then audit your code (a simple grep would do) to ensure that this is only done
17053 * for those values that you can easily tell are safe - because they were received from your server,
17054 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps
17055 * allowing only the files in a specific directory to do this. Ensuring that the internal API
17056 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task.
17058 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs}
17059 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to
17060 * obtain values that will be accepted by SCE / privileged contexts.
17063 * ## How does it work?
17065 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted
17066 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link
17067 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the
17068 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals.
17070 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link
17071 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly
17075 * var ngBindHtmlDirective = ['$sce', function($sce) {
17076 * return function(scope, element, attr) {
17077 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
17078 * element.html(value || '');
17084 * ## Impact on loading templates
17086 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as
17087 * `templateUrl`'s specified by {@link guide/directive directives}.
17089 * By default, Angular only loads templates from the same domain and protocol as the application
17090 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl
17091 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or
17092 * protocols, you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist
17093 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value.
17097 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
17098 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
17099 * policy apply in addition to this and may further restrict whether the template is successfully
17100 * loaded. This means that without the right CORS policy, loading templates from a different domain
17101 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some
17104 * ## This feels like too much overhead
17106 * It's important to remember that SCE only applies to interpolation expressions.
17108 * If your expressions are constant literals, they're automatically trusted and you don't need to
17109 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g.
17110 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works.
17112 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them
17113 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here.
17115 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load
17116 * templates in `ng-include` from your application's domain without having to even know about SCE.
17117 * It blocks loading templates from other domains or loading templates over http from an https
17118 * served document. You can change these by setting your own custom {@link
17119 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link
17120 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs.
17122 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an
17123 * application that's secure and can be audited to verify that with much more ease than bolting
17124 * security onto an application later.
17126 * <a name="contexts"></a>
17127 * ## What trusted context types are supported?
17129 * | Context | Notes |
17130 * |---------------------|----------------|
17131 * | `$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. |
17132 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. |
17133 * | `$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. |
17134 * | `$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. |
17135 * | `$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. |
17137 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a>
17139 * Each element in these arrays must be one of the following:
17142 * - The special **string**, `'self'`, can be used to match against all URLs of the **same
17143 * domain** as the application document using the **same protocol**.
17144 * - **String** (except the special value `'self'`)
17145 * - The string is matched against the full *normalized / absolute URL* of the resource
17146 * being tested (substring matches are not good enough.)
17147 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters
17148 * match themselves.
17149 * - `*`: matches zero or more occurrences of any character other than one of the following 6
17150 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use
17152 * - `**`: matches zero or more occurrences of *any* character. As such, it's not
17153 * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g.
17154 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might
17155 * not have been the intention.) Its usage at the very end of the path is ok. (e.g.
17156 * http://foo.example.com/templates/**).
17157 * - **RegExp** (*see caveat below*)
17158 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax
17159 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to
17160 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should
17161 * have good test coverage). For instance, the use of `.` in the regex is correct only in a
17162 * small number of cases. A `.` character in the regex used when matching the scheme or a
17163 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It
17164 * is highly recommended to use the string patterns and only fall back to regular expressions
17165 * as a last resort.
17166 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is
17167 * matched against the **entire** *normalized / absolute URL* of the resource being tested
17168 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags
17169 * present on the RegExp (such as multiline, global, ignoreCase) are ignored.
17170 * - If you are generating your JavaScript from some other templating engine (not
17171 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)),
17172 * remember to escape your regular expression (and be aware that you might need more than
17173 * one level of escaping depending on your templating engine and the way you interpolated
17174 * the value.) Do make use of your platform's escaping mechanism as it might be good
17175 * enough before coding your own. E.g. Ruby has
17176 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape)
17177 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape).
17178 * Javascript lacks a similar built in function for escaping. Take a look at Google
17179 * Closure library's [goog.string.regExpEscape(s)](
17180 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962).
17182 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example.
17184 * ## Show me an example using SCE.
17186 * <example module="mySceApp" deps="angular-sanitize.js">
17187 * <file name="index.html">
17188 * <div ng-controller="AppController as myCtrl">
17189 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br>
17190 * <b>User comments</b><br>
17191 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when
17192 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an
17194 * <div class="well">
17195 * <div ng-repeat="userComment in myCtrl.userComments">
17196 * <b>{{userComment.name}}</b>:
17197 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span>
17204 * <file name="script.js">
17205 * angular.module('mySceApp', ['ngSanitize'])
17206 * .controller('AppController', ['$http', '$templateCache', '$sce',
17207 * function($http, $templateCache, $sce) {
17209 * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) {
17210 * self.userComments = userComments;
17212 * self.explicitlyTrustedHtml = $sce.trustAsHtml(
17213 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' +
17214 * 'sanitization."">Hover over this text.</span>');
17218 * <file name="test_data.json">
17220 * { "name": "Alice",
17222 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>"
17225 * "htmlComment": "<i>Yes!</i> Am I the only other one?"
17230 * <file name="protractor.js" type="protractor">
17231 * describe('SCE doc demo', function() {
17232 * it('should sanitize untrusted values', function() {
17233 * expect(element.all(by.css('.htmlComment')).first().getInnerHtml())
17234 * .toBe('<span>Is <i>anyone</i> reading this?</span>');
17237 * it('should NOT sanitize explicitly trusted values', function() {
17238 * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe(
17239 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' +
17240 * 'sanitization."">Hover over this text.</span>');
17248 * ## Can I disable SCE completely?
17250 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits
17251 * for little coding overhead. It will be much harder to take an SCE disabled application and
17252 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE
17253 * for cases where you have a lot of existing code that was written before SCE was introduced and
17254 * you're migrating them a module at a time.
17256 * That said, here's how you can completely disable SCE:
17259 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
17260 * // Completely disable SCE. For demonstration purposes only!
17261 * // Do not use in new projects.
17262 * $sceProvider.enabled(false);
17267 /* jshint maxlen: 100 */
17269 function $SceProvider() {
17270 var enabled = true;
17274 * @name $sceProvider#enabled
17277 * @param {boolean=} value If provided, then enables/disables SCE.
17278 * @return {boolean} true if SCE is enabled, false otherwise.
17281 * Enables/disables SCE and returns the current value.
17283 this.enabled = function(value) {
17284 if (arguments.length) {
17291 /* Design notes on the default implementation for SCE.
17293 * The API contract for the SCE delegate
17294 * -------------------------------------
17295 * The SCE delegate object must provide the following 3 methods:
17297 * - trustAs(contextEnum, value)
17298 * This method is used to tell the SCE service that the provided value is OK to use in the
17299 * contexts specified by contextEnum. It must return an object that will be accepted by
17300 * getTrusted() for a compatible contextEnum and return this value.
17303 * For values that were not produced by trustAs(), return them as is. For values that were
17304 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if
17305 * trustAs is wrapping the given values into some type, this operation unwraps it when given
17308 * - getTrusted(contextEnum, value)
17309 * This function should return the a value that is safe to use in the context specified by
17310 * contextEnum or throw and exception otherwise.
17312 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be
17313 * opaque or wrapped in some holder object. That happens to be an implementation detail. For
17314 * instance, an implementation could maintain a registry of all trusted objects by context. In
17315 * such a case, trustAs() would return the same object that was passed in. getTrusted() would
17316 * return the same object passed in if it was found in the registry under a compatible context or
17317 * throw an exception otherwise. An implementation might only wrap values some of the time based
17318 * on some criteria. getTrusted() might return a value and not throw an exception for special
17319 * constants or objects even if not wrapped. All such implementations fulfill this contract.
17322 * A note on the inheritance model for SCE contexts
17323 * ------------------------------------------------
17324 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This
17325 * is purely an implementation details.
17327 * The contract is simply this:
17329 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value)
17330 * will also succeed.
17332 * Inheritance happens to capture this in a natural way. In some future, we
17333 * may not use inheritance anymore. That is OK because no code outside of
17334 * sce.js and sceSpecs.js would need to be aware of this detail.
17337 this.$get = ['$parse', '$sceDelegate', function(
17338 $parse, $sceDelegate) {
17339 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow
17340 // the "expression(javascript expression)" syntax which is insecure.
17341 if (enabled && msie < 8) {
17342 throw $sceMinErr('iequirks',
17343 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' +
17344 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
17345 'document. See http://docs.angularjs.org/api/ng.$sce for more information.');
17348 var sce = shallowCopy(SCE_CONTEXTS);
17352 * @name $sce#isEnabled
17355 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you
17356 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}.
17359 * Returns a boolean indicating if SCE is enabled.
17361 sce.isEnabled = function() {
17364 sce.trustAs = $sceDelegate.trustAs;
17365 sce.getTrusted = $sceDelegate.getTrusted;
17366 sce.valueOf = $sceDelegate.valueOf;
17369 sce.trustAs = sce.getTrusted = function(type, value) { return value; };
17370 sce.valueOf = identity;
17375 * @name $sce#parseAs
17378 * Converts Angular {@link guide/expression expression} into a function. This is like {@link
17379 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it
17380 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*,
17383 * @param {string} type The kind of SCE context in which this result will be used.
17384 * @param {string} expression String expression to compile.
17385 * @returns {function(context, locals)} a function which represents the compiled expression:
17387 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17388 * are evaluated against (typically a scope object).
17389 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17392 sce.parseAs = function sceParseAs(type, expr) {
17393 var parsed = $parse(expr);
17394 if (parsed.literal && parsed.constant) {
17397 return $parse(expr, function(value) {
17398 return sce.getTrusted(type, value);
17405 * @name $sce#trustAs
17408 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such,
17409 * returns an object that is trusted by angular for use in specified strict contextual
17410 * escaping contexts (such as ng-bind-html, ng-include, any src attribute
17411 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.)
17412 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual
17415 * @param {string} type The kind of context in which this value is safe for use. e.g. url,
17416 * resourceUrl, html, js and css.
17417 * @param {*} value The value that that should be considered trusted/safe.
17418 * @returns {*} A value that can be used to stand in for the provided `value` in places
17419 * where Angular expects a $sce.trustAs() return value.
17424 * @name $sce#trustAsHtml
17427 * Shorthand method. `$sce.trustAsHtml(value)` →
17428 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`}
17430 * @param {*} value The value to trustAs.
17431 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml
17432 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives
17433 * only accept expressions that are either literal constants or are the
17434 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17439 * @name $sce#trustAsUrl
17442 * Shorthand method. `$sce.trustAsUrl(value)` →
17443 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`}
17445 * @param {*} value The value to trustAs.
17446 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl
17447 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives
17448 * only accept expressions that are either literal constants or are the
17449 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17454 * @name $sce#trustAsResourceUrl
17457 * Shorthand method. `$sce.trustAsResourceUrl(value)` →
17458 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`}
17460 * @param {*} value The value to trustAs.
17461 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl
17462 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives
17463 * only accept expressions that are either literal constants or are the return
17464 * value of {@link ng.$sce#trustAs $sce.trustAs}.)
17469 * @name $sce#trustAsJs
17472 * Shorthand method. `$sce.trustAsJs(value)` →
17473 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`}
17475 * @param {*} value The value to trustAs.
17476 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs
17477 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives
17478 * only accept expressions that are either literal constants or are the
17479 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17484 * @name $sce#getTrusted
17487 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such,
17488 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the
17489 * originally supplied value if the queried context type is a supertype of the created type.
17490 * If this condition isn't satisfied, throws an exception.
17492 * @param {string} type The kind of context in which this value is to be used.
17493 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`}
17495 * @returns {*} The value the was originally provided to
17496 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context.
17497 * Otherwise, throws an exception.
17502 * @name $sce#getTrustedHtml
17505 * Shorthand method. `$sce.getTrustedHtml(value)` →
17506 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`}
17508 * @param {*} value The value to pass to `$sce.getTrusted`.
17509 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)`
17514 * @name $sce#getTrustedCss
17517 * Shorthand method. `$sce.getTrustedCss(value)` →
17518 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`}
17520 * @param {*} value The value to pass to `$sce.getTrusted`.
17521 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)`
17526 * @name $sce#getTrustedUrl
17529 * Shorthand method. `$sce.getTrustedUrl(value)` →
17530 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`}
17532 * @param {*} value The value to pass to `$sce.getTrusted`.
17533 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)`
17538 * @name $sce#getTrustedResourceUrl
17541 * Shorthand method. `$sce.getTrustedResourceUrl(value)` →
17542 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`}
17544 * @param {*} value The value to pass to `$sceDelegate.getTrusted`.
17545 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)`
17550 * @name $sce#getTrustedJs
17553 * Shorthand method. `$sce.getTrustedJs(value)` →
17554 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`}
17556 * @param {*} value The value to pass to `$sce.getTrusted`.
17557 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)`
17562 * @name $sce#parseAsHtml
17565 * Shorthand method. `$sce.parseAsHtml(expression string)` →
17566 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`}
17568 * @param {string} expression String expression to compile.
17569 * @returns {function(context, locals)} a function which represents the compiled expression:
17571 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17572 * are evaluated against (typically a scope object).
17573 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17579 * @name $sce#parseAsCss
17582 * Shorthand method. `$sce.parseAsCss(value)` →
17583 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`}
17585 * @param {string} expression String expression to compile.
17586 * @returns {function(context, locals)} a function which represents the compiled expression:
17588 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17589 * are evaluated against (typically a scope object).
17590 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17596 * @name $sce#parseAsUrl
17599 * Shorthand method. `$sce.parseAsUrl(value)` →
17600 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`}
17602 * @param {string} expression String expression to compile.
17603 * @returns {function(context, locals)} a function which represents the compiled expression:
17605 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17606 * are evaluated against (typically a scope object).
17607 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17613 * @name $sce#parseAsResourceUrl
17616 * Shorthand method. `$sce.parseAsResourceUrl(value)` →
17617 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`}
17619 * @param {string} expression String expression to compile.
17620 * @returns {function(context, locals)} a function which represents the compiled expression:
17622 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17623 * are evaluated against (typically a scope object).
17624 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17630 * @name $sce#parseAsJs
17633 * Shorthand method. `$sce.parseAsJs(value)` →
17634 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`}
17636 * @param {string} expression String expression to compile.
17637 * @returns {function(context, locals)} a function which represents the compiled expression:
17639 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17640 * are evaluated against (typically a scope object).
17641 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17645 // Shorthand delegations.
17646 var parse = sce.parseAs,
17647 getTrusted = sce.getTrusted,
17648 trustAs = sce.trustAs;
17650 forEach(SCE_CONTEXTS, function(enumValue, name) {
17651 var lName = lowercase(name);
17652 sce[camelCase("parse_as_" + lName)] = function(expr) {
17653 return parse(enumValue, expr);
17655 sce[camelCase("get_trusted_" + lName)] = function(value) {
17656 return getTrusted(enumValue, value);
17658 sce[camelCase("trust_as_" + lName)] = function(value) {
17659 return trustAs(enumValue, value);
17668 * !!! This is an undocumented "private" service !!!
17671 * @requires $window
17672 * @requires $document
17674 * @property {boolean} history Does the browser support html5 history api ?
17675 * @property {boolean} transitions Does the browser support CSS transition events ?
17676 * @property {boolean} animations Does the browser support CSS animation events ?
17679 * This is very simple implementation of testing browser's features.
17681 function $SnifferProvider() {
17682 this.$get = ['$window', '$document', function($window, $document) {
17683 var eventSupport = {},
17685 toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
17686 boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
17687 document = $document[0] || {},
17689 vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/,
17690 bodyStyle = document.body && document.body.style,
17691 transitions = false,
17692 animations = false,
17696 for (var prop in bodyStyle) {
17697 if (match = vendorRegex.exec(prop)) {
17698 vendorPrefix = match[0];
17699 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1);
17704 if (!vendorPrefix) {
17705 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit';
17708 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle));
17709 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle));
17711 if (android && (!transitions || !animations)) {
17712 transitions = isString(bodyStyle.webkitTransition);
17713 animations = isString(bodyStyle.webkitAnimation);
17719 // Android has history.pushState, but it does not update location correctly
17720 // so let's not use the history API at all.
17721 // http://code.google.com/p/android/issues/detail?id=17471
17722 // https://github.com/angular/angular.js/issues/904
17724 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has
17725 // so let's not use the history API also
17726 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined
17728 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee),
17730 hasEvent: function(event) {
17731 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
17732 // it. In particular the event is not fired when backspace or delete key are pressed or
17733 // when cut operation is performed.
17734 // IE10+ implements 'input' event but it erroneously fires under various situations,
17735 // e.g. when placeholder changes, or a form is focused.
17736 if (event === 'input' && msie <= 11) return false;
17738 if (isUndefined(eventSupport[event])) {
17739 var divElm = document.createElement('div');
17740 eventSupport[event] = 'on' + event in divElm;
17743 return eventSupport[event];
17746 vendorPrefix: vendorPrefix,
17747 transitions: transitions,
17748 animations: animations,
17754 var $compileMinErr = minErr('$compile');
17758 * @name $templateRequest
17761 * The `$templateRequest` service runs security checks then downloads the provided template using
17762 * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request
17763 * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the
17764 * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the
17765 * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted
17766 * when `tpl` is of type string and `$templateCache` has the matching entry.
17768 * @param {string|TrustedResourceUrl} tpl The HTTP request template URL
17769 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty
17771 * @return {Promise} a promise for the HTTP response data of the given URL.
17773 * @property {number} totalPendingRequests total amount of pending template requests being downloaded.
17775 function $TemplateRequestProvider() {
17776 this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) {
17777 function handleRequestFn(tpl, ignoreRequestError) {
17778 handleRequestFn.totalPendingRequests++;
17780 // We consider the template cache holds only trusted templates, so
17781 // there's no need to go through whitelisting again for keys that already
17782 // are included in there. This also makes Angular accept any script
17783 // directive, no matter its name. However, we still need to unwrap trusted
17785 if (!isString(tpl) || !$templateCache.get(tpl)) {
17786 tpl = $sce.getTrustedResourceUrl(tpl);
17789 var transformResponse = $http.defaults && $http.defaults.transformResponse;
17791 if (isArray(transformResponse)) {
17792 transformResponse = transformResponse.filter(function(transformer) {
17793 return transformer !== defaultHttpResponseTransform;
17795 } else if (transformResponse === defaultHttpResponseTransform) {
17796 transformResponse = null;
17799 var httpOptions = {
17800 cache: $templateCache,
17801 transformResponse: transformResponse
17804 return $http.get(tpl, httpOptions)
17805 ['finally'](function() {
17806 handleRequestFn.totalPendingRequests--;
17808 .then(function(response) {
17809 $templateCache.put(tpl, response.data);
17810 return response.data;
17813 function handleError(resp) {
17814 if (!ignoreRequestError) {
17815 throw $compileMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})',
17816 tpl, resp.status, resp.statusText);
17818 return $q.reject(resp);
17822 handleRequestFn.totalPendingRequests = 0;
17824 return handleRequestFn;
17828 function $$TestabilityProvider() {
17829 this.$get = ['$rootScope', '$browser', '$location',
17830 function($rootScope, $browser, $location) {
17833 * @name $testability
17836 * The private $$testability service provides a collection of methods for use when debugging
17837 * or by automated test and debugging tools.
17839 var testability = {};
17842 * @name $$testability#findBindings
17845 * Returns an array of elements that are bound (via ng-bind or {{}})
17846 * to expressions matching the input.
17848 * @param {Element} element The element root to search from.
17849 * @param {string} expression The binding expression to match.
17850 * @param {boolean} opt_exactMatch If true, only returns exact matches
17851 * for the expression. Filters and whitespace are ignored.
17853 testability.findBindings = function(element, expression, opt_exactMatch) {
17854 var bindings = element.getElementsByClassName('ng-binding');
17856 forEach(bindings, function(binding) {
17857 var dataBinding = angular.element(binding).data('$binding');
17859 forEach(dataBinding, function(bindingName) {
17860 if (opt_exactMatch) {
17861 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)');
17862 if (matcher.test(bindingName)) {
17863 matches.push(binding);
17866 if (bindingName.indexOf(expression) != -1) {
17867 matches.push(binding);
17877 * @name $$testability#findModels
17880 * Returns an array of elements that are two-way found via ng-model to
17881 * expressions matching the input.
17883 * @param {Element} element The element root to search from.
17884 * @param {string} expression The model expression to match.
17885 * @param {boolean} opt_exactMatch If true, only returns exact matches
17886 * for the expression.
17888 testability.findModels = function(element, expression, opt_exactMatch) {
17889 var prefixes = ['ng-', 'data-ng-', 'ng\\:'];
17890 for (var p = 0; p < prefixes.length; ++p) {
17891 var attributeEquals = opt_exactMatch ? '=' : '*=';
17892 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]';
17893 var elements = element.querySelectorAll(selector);
17894 if (elements.length) {
17901 * @name $$testability#getLocation
17904 * Shortcut for getting the location in a browser agnostic way. Returns
17905 * the path, search, and hash. (e.g. /path?a=b#hash)
17907 testability.getLocation = function() {
17908 return $location.url();
17912 * @name $$testability#setLocation
17915 * Shortcut for navigating to a location without doing a full page reload.
17917 * @param {string} url The location url (path, search and hash,
17918 * e.g. /path?a=b#hash) to go to.
17920 testability.setLocation = function(url) {
17921 if (url !== $location.url()) {
17922 $location.url(url);
17923 $rootScope.$digest();
17928 * @name $$testability#whenStable
17931 * Calls the callback when $timeout and $http requests are completed.
17933 * @param {function} callback
17935 testability.whenStable = function(callback) {
17936 $browser.notifyWhenNoOutstandingRequests(callback);
17939 return testability;
17943 function $TimeoutProvider() {
17944 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler',
17945 function($rootScope, $browser, $q, $$q, $exceptionHandler) {
17947 var deferreds = {};
17955 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch
17956 * block and delegates any exceptions to
17957 * {@link ng.$exceptionHandler $exceptionHandler} service.
17959 * The return value of calling `$timeout` is a promise, which will be resolved when
17960 * the delay has passed and the timeout function, if provided, is executed.
17962 * To cancel a timeout request, call `$timeout.cancel(promise)`.
17964 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
17965 * synchronously flush the queue of deferred functions.
17967 * If you only want a promise that will be resolved after some specified delay
17968 * then you can call `$timeout` without the `fn` function.
17970 * @param {function()=} fn A function, whose execution should be delayed.
17971 * @param {number=} [delay=0] Delay in milliseconds.
17972 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
17973 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
17974 * @param {...*=} Pass additional parameters to the executed function.
17975 * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this
17976 * promise will be resolved with is the return value of the `fn` function.
17979 function timeout(fn, delay, invokeApply) {
17980 if (!isFunction(fn)) {
17981 invokeApply = delay;
17986 var args = sliceArgs(arguments, 3),
17987 skipApply = (isDefined(invokeApply) && !invokeApply),
17988 deferred = (skipApply ? $$q : $q).defer(),
17989 promise = deferred.promise,
17992 timeoutId = $browser.defer(function() {
17994 deferred.resolve(fn.apply(null, args));
17996 deferred.reject(e);
17997 $exceptionHandler(e);
18000 delete deferreds[promise.$$timeoutId];
18003 if (!skipApply) $rootScope.$apply();
18006 promise.$$timeoutId = timeoutId;
18007 deferreds[timeoutId] = deferred;
18015 * @name $timeout#cancel
18018 * Cancels a task associated with the `promise`. As a result of this, the promise will be
18019 * resolved with a rejection.
18021 * @param {Promise=} promise Promise returned by the `$timeout` function.
18022 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
18025 timeout.cancel = function(promise) {
18026 if (promise && promise.$$timeoutId in deferreds) {
18027 deferreds[promise.$$timeoutId].reject('canceled');
18028 delete deferreds[promise.$$timeoutId];
18029 return $browser.defer.cancel(promise.$$timeoutId);
18038 // NOTE: The usage of window and document instead of $window and $document here is
18039 // deliberate. This service depends on the specific behavior of anchor nodes created by the
18040 // browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and
18041 // cause us to break tests. In addition, when the browser resolves a URL for XHR, it
18042 // doesn't know about mocked locations and resolves URLs to the real document - which is
18043 // exactly the behavior needed here. There is little value is mocking these out for this
18045 var urlParsingNode = document.createElement("a");
18046 var originUrl = urlResolve(window.location.href);
18051 * Implementation Notes for non-IE browsers
18052 * ----------------------------------------
18053 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM,
18054 * results both in the normalizing and parsing of the URL. Normalizing means that a relative
18055 * URL will be resolved into an absolute URL in the context of the application document.
18056 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related
18057 * properties are all populated to reflect the normalized URL. This approach has wide
18058 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See
18059 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
18061 * Implementation Notes for IE
18062 * ---------------------------
18063 * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other
18064 * browsers. However, the parsed components will not be set if the URL assigned did not specify
18065 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We
18066 * work around that by performing the parsing in a 2nd step by taking a previously normalized
18067 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the
18068 * properties such as protocol, hostname, port, etc.
18071 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement
18072 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
18073 * http://url.spec.whatwg.org/#urlutils
18074 * https://github.com/angular/angular.js/pull/2902
18075 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/
18078 * @param {string} url The URL to be parsed.
18079 * @description Normalizes and parses a URL.
18080 * @returns {object} Returns the normalized URL as a dictionary.
18082 * | member name | Description |
18083 * |---------------|----------------|
18084 * | href | A normalized version of the provided URL if it was not an absolute URL |
18085 * | protocol | The protocol including the trailing colon |
18086 * | host | The host and port (if the port is non-default) of the normalizedUrl |
18087 * | search | The search params, minus the question mark |
18088 * | hash | The hash string, minus the hash symbol
18089 * | hostname | The hostname
18090 * | port | The port, without ":"
18091 * | pathname | The pathname, beginning with "/"
18094 function urlResolve(url) {
18098 // Normalize before parse. Refer Implementation Notes on why this is
18099 // done in two steps on IE.
18100 urlParsingNode.setAttribute("href", href);
18101 href = urlParsingNode.href;
18104 urlParsingNode.setAttribute('href', href);
18106 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
18108 href: urlParsingNode.href,
18109 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
18110 host: urlParsingNode.host,
18111 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '',
18112 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
18113 hostname: urlParsingNode.hostname,
18114 port: urlParsingNode.port,
18115 pathname: (urlParsingNode.pathname.charAt(0) === '/')
18116 ? urlParsingNode.pathname
18117 : '/' + urlParsingNode.pathname
18122 * Parse a request URL and determine whether this is a same-origin request as the application document.
18124 * @param {string|object} requestUrl The url of the request as a string that will be resolved
18125 * or a parsed URL object.
18126 * @returns {boolean} Whether the request is for the same origin as the application document.
18128 function urlIsSameOrigin(requestUrl) {
18129 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl;
18130 return (parsed.protocol === originUrl.protocol &&
18131 parsed.host === originUrl.host);
18139 * A reference to the browser's `window` object. While `window`
18140 * is globally available in JavaScript, it causes testability problems, because
18141 * it is a global variable. In angular we always refer to it through the
18142 * `$window` service, so it may be overridden, removed or mocked for testing.
18144 * Expressions, like the one defined for the `ngClick` directive in the example
18145 * below, are evaluated with respect to the current scope. Therefore, there is
18146 * no risk of inadvertently coding in a dependency on a global value in such an
18150 <example module="windowExample">
18151 <file name="index.html">
18153 angular.module('windowExample', [])
18154 .controller('ExampleController', ['$scope', '$window', function($scope, $window) {
18155 $scope.greeting = 'Hello, World!';
18156 $scope.doGreeting = function(greeting) {
18157 $window.alert(greeting);
18161 <div ng-controller="ExampleController">
18162 <input type="text" ng-model="greeting" aria-label="greeting" />
18163 <button ng-click="doGreeting(greeting)">ALERT</button>
18166 <file name="protractor.js" type="protractor">
18167 it('should display the greeting in the input box', function() {
18168 element(by.model('greeting')).sendKeys('Hello, E2E Tests');
18169 // If we click the button it will block the test runner
18170 // element(':button').click();
18175 function $WindowProvider() {
18176 this.$get = valueFn(window);
18180 * @name $$cookieReader
18181 * @requires $document
18184 * This is a private service for reading cookies used by $http and ngCookies
18186 * @return {Object} a key/value map of the current cookies
18188 function $$CookieReader($document) {
18189 var rawDocument = $document[0] || {};
18190 var lastCookies = {};
18191 var lastCookieString = '';
18193 function safeDecodeURIComponent(str) {
18195 return decodeURIComponent(str);
18201 return function() {
18202 var cookieArray, cookie, i, index, name;
18203 var currentCookieString = rawDocument.cookie || '';
18205 if (currentCookieString !== lastCookieString) {
18206 lastCookieString = currentCookieString;
18207 cookieArray = lastCookieString.split('; ');
18210 for (i = 0; i < cookieArray.length; i++) {
18211 cookie = cookieArray[i];
18212 index = cookie.indexOf('=');
18213 if (index > 0) { //ignore nameless cookies
18214 name = safeDecodeURIComponent(cookie.substring(0, index));
18215 // the first value that is seen for a cookie is the most
18216 // specific one. values for the same cookie name that
18217 // follow are for less specific paths.
18218 if (isUndefined(lastCookies[name])) {
18219 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
18224 return lastCookies;
18228 $$CookieReader.$inject = ['$document'];
18230 function $$CookieReaderProvider() {
18231 this.$get = $$CookieReader;
18234 /* global currencyFilter: true,
18236 filterFilter: true,
18238 limitToFilter: true,
18239 lowercaseFilter: true,
18240 numberFilter: true,
18241 orderByFilter: true,
18242 uppercaseFilter: true,
18247 * @name $filterProvider
18250 * Filters are just functions which transform input to an output. However filters need to be
18251 * Dependency Injected. To achieve this a filter definition consists of a factory function which is
18252 * annotated with dependencies and is responsible for creating a filter function.
18254 * <div class="alert alert-warning">
18255 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
18256 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
18257 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
18258 * (`myapp_subsection_filterx`).
18262 * // Filter registration
18263 * function MyModule($provide, $filterProvider) {
18264 * // create a service to demonstrate injection (not always needed)
18265 * $provide.value('greet', function(name){
18266 * return 'Hello ' + name + '!';
18269 * // register a filter factory which uses the
18270 * // greet service to demonstrate DI.
18271 * $filterProvider.register('greet', function(greet){
18272 * // return the filter function which uses the greet service
18273 * // to generate salutation
18274 * return function(text) {
18275 * // filters need to be forgiving so check input validity
18276 * return text && greet(text) || text;
18282 * The filter function is registered with the `$injector` under the filter name suffix with
18286 * it('should be the same instance', inject(
18287 * function($filterProvider) {
18288 * $filterProvider.register('reverse', function(){
18292 * function($filter, reverseFilter) {
18293 * expect($filter('reverse')).toBe(reverseFilter);
18298 * For more information about how angular filters work, and how to create your own filters, see
18299 * {@link guide/filter Filters} in the Angular Developer Guide.
18307 * Filters are used for formatting data displayed to the user.
18309 * The general syntax in templates is as follows:
18311 * {{ expression [| filter_name[:parameter_value] ... ] }}
18313 * @param {String} name Name of the filter function to retrieve
18314 * @return {Function} the filter function
18316 <example name="$filter" module="filterExample">
18317 <file name="index.html">
18318 <div ng-controller="MainCtrl">
18319 <h3>{{ originalText }}</h3>
18320 <h3>{{ filteredText }}</h3>
18324 <file name="script.js">
18325 angular.module('filterExample', [])
18326 .controller('MainCtrl', function($scope, $filter) {
18327 $scope.originalText = 'hello';
18328 $scope.filteredText = $filter('uppercase')($scope.originalText);
18333 $FilterProvider.$inject = ['$provide'];
18334 function $FilterProvider($provide) {
18335 var suffix = 'Filter';
18339 * @name $filterProvider#register
18340 * @param {string|Object} name Name of the filter function, or an object map of filters where
18341 * the keys are the filter names and the values are the filter factories.
18343 * <div class="alert alert-warning">
18344 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
18345 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
18346 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
18347 * (`myapp_subsection_filterx`).
18349 * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered.
18350 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map
18351 * of the registered filter instances.
18353 function register(name, factory) {
18354 if (isObject(name)) {
18356 forEach(name, function(filter, key) {
18357 filters[key] = register(key, filter);
18361 return $provide.factory(name + suffix, factory);
18364 this.register = register;
18366 this.$get = ['$injector', function($injector) {
18367 return function(name) {
18368 return $injector.get(name + suffix);
18372 ////////////////////////////////////////
18375 currencyFilter: false,
18377 filterFilter: false,
18379 limitToFilter: false,
18380 lowercaseFilter: false,
18381 numberFilter: false,
18382 orderByFilter: false,
18383 uppercaseFilter: false,
18386 register('currency', currencyFilter);
18387 register('date', dateFilter);
18388 register('filter', filterFilter);
18389 register('json', jsonFilter);
18390 register('limitTo', limitToFilter);
18391 register('lowercase', lowercaseFilter);
18392 register('number', numberFilter);
18393 register('orderBy', orderByFilter);
18394 register('uppercase', uppercaseFilter);
18403 * Selects a subset of items from `array` and returns it as a new array.
18405 * @param {Array} array The source array.
18406 * @param {string|Object|function()} expression The predicate to be used for selecting items from
18411 * - `string`: The string is used for matching against the contents of the `array`. All strings or
18412 * objects with string properties in `array` that match this string will be returned. This also
18413 * applies to nested object properties.
18414 * The predicate can be negated by prefixing the string with `!`.
18416 * - `Object`: A pattern object can be used to filter specific properties on objects contained
18417 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
18418 * which have property `name` containing "M" and property `phone` containing "1". A special
18419 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any
18420 * property of the object or its nested object properties. That's equivalent to the simple
18421 * substring match with a `string` as described above. The predicate can be negated by prefixing
18422 * the string with `!`.
18423 * For example `{name: "!M"}` predicate will return an array of items which have property `name`
18424 * not containing "M".
18426 * Note that a named property will match properties on the same level only, while the special
18427 * `$` property will match properties on the same level or deeper. E.g. an array item like
18428 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but
18429 * **will** be matched by `{$: 'John'}`.
18431 * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters.
18432 * The function is called for each element of the array, with the element, its index, and
18433 * the entire array itself as arguments.
18435 * The final result is an array of those elements that the predicate returned true for.
18437 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in
18438 * determining if the expected value (from the filter expression) and actual value (from
18439 * the object in the array) should be considered a match.
18443 * - `function(actual, expected)`:
18444 * The function will be given the object value and the predicate value to compare and
18445 * should return true if both values should be considered equal.
18447 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`.
18448 * This is essentially strict comparison of expected and actual.
18450 * - `false|undefined`: A short hand for a function which will look for a substring match in case
18453 * Primitive values are converted to strings. Objects are not compared against primitives,
18454 * unless they have a custom `toString` method (e.g. `Date` objects).
18458 <file name="index.html">
18459 <div ng-init="friends = [{name:'John', phone:'555-1276'},
18460 {name:'Mary', phone:'800-BIG-MARY'},
18461 {name:'Mike', phone:'555-4321'},
18462 {name:'Adam', phone:'555-5678'},
18463 {name:'Julie', phone:'555-8765'},
18464 {name:'Juliette', phone:'555-5678'}]"></div>
18466 <label>Search: <input ng-model="searchText"></label>
18467 <table id="searchTextResults">
18468 <tr><th>Name</th><th>Phone</th></tr>
18469 <tr ng-repeat="friend in friends | filter:searchText">
18470 <td>{{friend.name}}</td>
18471 <td>{{friend.phone}}</td>
18475 <label>Any: <input ng-model="search.$"></label> <br>
18476 <label>Name only <input ng-model="search.name"></label><br>
18477 <label>Phone only <input ng-model="search.phone"></label><br>
18478 <label>Equality <input type="checkbox" ng-model="strict"></label><br>
18479 <table id="searchObjResults">
18480 <tr><th>Name</th><th>Phone</th></tr>
18481 <tr ng-repeat="friendObj in friends | filter:search:strict">
18482 <td>{{friendObj.name}}</td>
18483 <td>{{friendObj.phone}}</td>
18487 <file name="protractor.js" type="protractor">
18488 var expectFriendNames = function(expectedNames, key) {
18489 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) {
18490 arr.forEach(function(wd, i) {
18491 expect(wd.getText()).toMatch(expectedNames[i]);
18496 it('should search across all fields when filtering with a string', function() {
18497 var searchText = element(by.model('searchText'));
18498 searchText.clear();
18499 searchText.sendKeys('m');
18500 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend');
18502 searchText.clear();
18503 searchText.sendKeys('76');
18504 expectFriendNames(['John', 'Julie'], 'friend');
18507 it('should search in specific fields when filtering with a predicate object', function() {
18508 var searchAny = element(by.model('search.$'));
18510 searchAny.sendKeys('i');
18511 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj');
18513 it('should use a equal comparison when comparator is true', function() {
18514 var searchName = element(by.model('search.name'));
18515 var strict = element(by.model('strict'));
18516 searchName.clear();
18517 searchName.sendKeys('Julie');
18519 expectFriendNames(['Julie'], 'friendObj');
18524 function filterFilter() {
18525 return function(array, expression, comparator) {
18526 if (!isArrayLike(array)) {
18527 if (array == null) {
18530 throw minErr('filter')('notarray', 'Expected array but received: {0}', array);
18534 var expressionType = getTypeForFilter(expression);
18536 var matchAgainstAnyProp;
18538 switch (expressionType) {
18540 predicateFn = expression;
18546 matchAgainstAnyProp = true;
18550 predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp);
18556 return Array.prototype.filter.call(array, predicateFn);
18560 // Helper functions for `filterFilter`
18561 function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
18562 var shouldMatchPrimitives = isObject(expression) && ('$' in expression);
18565 if (comparator === true) {
18566 comparator = equals;
18567 } else if (!isFunction(comparator)) {
18568 comparator = function(actual, expected) {
18569 if (isUndefined(actual)) {
18570 // No substring matching against `undefined`
18573 if ((actual === null) || (expected === null)) {
18574 // No substring matching against `null`; only match against `null`
18575 return actual === expected;
18577 if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) {
18578 // Should not compare primitives against objects, unless they have custom `toString` method
18582 actual = lowercase('' + actual);
18583 expected = lowercase('' + expected);
18584 return actual.indexOf(expected) !== -1;
18588 predicateFn = function(item) {
18589 if (shouldMatchPrimitives && !isObject(item)) {
18590 return deepCompare(item, expression.$, comparator, false);
18592 return deepCompare(item, expression, comparator, matchAgainstAnyProp);
18595 return predicateFn;
18598 function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) {
18599 var actualType = getTypeForFilter(actual);
18600 var expectedType = getTypeForFilter(expected);
18602 if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
18603 return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
18604 } else if (isArray(actual)) {
18605 // In case `actual` is an array, consider it a match
18606 // if ANY of it's items matches `expected`
18607 return actual.some(function(item) {
18608 return deepCompare(item, expected, comparator, matchAgainstAnyProp);
18612 switch (actualType) {
18615 if (matchAgainstAnyProp) {
18616 for (key in actual) {
18617 if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) {
18621 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false);
18622 } else if (expectedType === 'object') {
18623 for (key in expected) {
18624 var expectedVal = expected[key];
18625 if (isFunction(expectedVal) || isUndefined(expectedVal)) {
18629 var matchAnyProperty = key === '$';
18630 var actualVal = matchAnyProperty ? actual : actual[key];
18631 if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) {
18637 return comparator(actual, expected);
18643 return comparator(actual, expected);
18647 // Used for easily differentiating between `null` and actual `object`
18648 function getTypeForFilter(val) {
18649 return (val === null) ? 'null' : typeof val;
18658 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
18659 * symbol for current locale is used.
18661 * @param {number} amount Input to filter.
18662 * @param {string=} symbol Currency symbol or identifier to be displayed.
18663 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale
18664 * @returns {string} Formatted number.
18668 <example module="currencyExample">
18669 <file name="index.html">
18671 angular.module('currencyExample', [])
18672 .controller('ExampleController', ['$scope', function($scope) {
18673 $scope.amount = 1234.56;
18676 <div ng-controller="ExampleController">
18677 <input type="number" ng-model="amount" aria-label="amount"> <br>
18678 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br>
18679 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span>
18680 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span>
18683 <file name="protractor.js" type="protractor">
18684 it('should init with 1234.56', function() {
18685 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56');
18686 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56');
18687 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235');
18689 it('should update', function() {
18690 if (browser.params.browser == 'safari') {
18691 // Safari does not understand the minus key. See
18692 // https://github.com/angular/protractor/issues/481
18695 element(by.model('amount')).clear();
18696 element(by.model('amount')).sendKeys('-1234');
18697 expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00');
18698 expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00');
18699 expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234');
18704 currencyFilter.$inject = ['$locale'];
18705 function currencyFilter($locale) {
18706 var formats = $locale.NUMBER_FORMATS;
18707 return function(amount, currencySymbol, fractionSize) {
18708 if (isUndefined(currencySymbol)) {
18709 currencySymbol = formats.CURRENCY_SYM;
18712 if (isUndefined(fractionSize)) {
18713 fractionSize = formats.PATTERNS[1].maxFrac;
18716 // if null or undefined pass it through
18717 return (amount == null)
18719 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize).
18720 replace(/\u00A4/g, currencySymbol);
18730 * Formats a number as text.
18732 * If the input is null or undefined, it will just be returned.
18733 * If the input is infinite (Infinity/-Infinity) the Infinity symbol '∞' is returned.
18734 * If the input is not a number an empty string is returned.
18737 * @param {number|string} number Number to format.
18738 * @param {(number|string)=} fractionSize Number of decimal places to round the number to.
18739 * If this is not provided then the fraction size is computed from the current locale's number
18740 * formatting pattern. In the case of the default locale, it will be 3.
18741 * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
18744 <example module="numberFilterExample">
18745 <file name="index.html">
18747 angular.module('numberFilterExample', [])
18748 .controller('ExampleController', ['$scope', function($scope) {
18749 $scope.val = 1234.56789;
18752 <div ng-controller="ExampleController">
18753 <label>Enter number: <input ng-model='val'></label><br>
18754 Default formatting: <span id='number-default'>{{val | number}}</span><br>
18755 No fractions: <span>{{val | number:0}}</span><br>
18756 Negative number: <span>{{-val | number:4}}</span>
18759 <file name="protractor.js" type="protractor">
18760 it('should format numbers', function() {
18761 expect(element(by.id('number-default')).getText()).toBe('1,234.568');
18762 expect(element(by.binding('val | number:0')).getText()).toBe('1,235');
18763 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679');
18766 it('should update', function() {
18767 element(by.model('val')).clear();
18768 element(by.model('val')).sendKeys('3374.333');
18769 expect(element(by.id('number-default')).getText()).toBe('3,374.333');
18770 expect(element(by.binding('val | number:0')).getText()).toBe('3,374');
18771 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330');
18778 numberFilter.$inject = ['$locale'];
18779 function numberFilter($locale) {
18780 var formats = $locale.NUMBER_FORMATS;
18781 return function(number, fractionSize) {
18783 // if null or undefined pass it through
18784 return (number == null)
18786 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP,
18791 var DECIMAL_SEP = '.';
18792 function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
18793 if (isObject(number)) return '';
18795 var isNegative = number < 0;
18796 number = Math.abs(number);
18798 var isInfinity = number === Infinity;
18799 if (!isInfinity && !isFinite(number)) return '';
18801 var numStr = number + '',
18803 hasExponent = false,
18806 if (isInfinity) formatedText = '\u221e';
18808 if (!isInfinity && numStr.indexOf('e') !== -1) {
18809 var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
18810 if (match && match[2] == '-' && match[3] > fractionSize + 1) {
18813 formatedText = numStr;
18814 hasExponent = true;
18818 if (!isInfinity && !hasExponent) {
18819 var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
18821 // determine fractionSize if it is not specified
18822 if (isUndefined(fractionSize)) {
18823 fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac);
18826 // safely round numbers in JS without hitting imprecisions of floating-point arithmetics
18828 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
18829 number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
18831 var fraction = ('' + number).split(DECIMAL_SEP);
18832 var whole = fraction[0];
18833 fraction = fraction[1] || '';
18836 lgroup = pattern.lgSize,
18837 group = pattern.gSize;
18839 if (whole.length >= (lgroup + group)) {
18840 pos = whole.length - lgroup;
18841 for (i = 0; i < pos; i++) {
18842 if ((pos - i) % group === 0 && i !== 0) {
18843 formatedText += groupSep;
18845 formatedText += whole.charAt(i);
18849 for (i = pos; i < whole.length; i++) {
18850 if ((whole.length - i) % lgroup === 0 && i !== 0) {
18851 formatedText += groupSep;
18853 formatedText += whole.charAt(i);
18856 // format fraction part.
18857 while (fraction.length < fractionSize) {
18861 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize);
18863 if (fractionSize > 0 && number < 1) {
18864 formatedText = number.toFixed(fractionSize);
18865 number = parseFloat(formatedText);
18866 formatedText = formatedText.replace(DECIMAL_SEP, decimalSep);
18870 if (number === 0) {
18871 isNegative = false;
18874 parts.push(isNegative ? pattern.negPre : pattern.posPre,
18876 isNegative ? pattern.negSuf : pattern.posSuf);
18877 return parts.join('');
18880 function padNumber(num, digits, trim) {
18887 while (num.length < digits) num = '0' + num;
18889 num = num.substr(num.length - digits);
18895 function dateGetter(name, size, offset, trim) {
18896 offset = offset || 0;
18897 return function(date) {
18898 var value = date['get' + name]();
18899 if (offset > 0 || value > -offset) {
18902 if (value === 0 && offset == -12) value = 12;
18903 return padNumber(value, size, trim);
18907 function dateStrGetter(name, shortForm) {
18908 return function(date, formats) {
18909 var value = date['get' + name]();
18910 var get = uppercase(shortForm ? ('SHORT' + name) : name);
18912 return formats[get][value];
18916 function timeZoneGetter(date, formats, offset) {
18917 var zone = -1 * offset;
18918 var paddedZone = (zone >= 0) ? "+" : "";
18920 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) +
18921 padNumber(Math.abs(zone % 60), 2);
18926 function getFirstThursdayOfYear(year) {
18927 // 0 = index of January
18928 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay();
18929 // 4 = index of Thursday (+1 to account for 1st = 5)
18930 // 11 = index of *next* Thursday (+1 account for 1st = 12)
18931 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst);
18934 function getThursdayThisWeek(datetime) {
18935 return new Date(datetime.getFullYear(), datetime.getMonth(),
18936 // 4 = index of Thursday
18937 datetime.getDate() + (4 - datetime.getDay()));
18940 function weekGetter(size) {
18941 return function(date) {
18942 var firstThurs = getFirstThursdayOfYear(date.getFullYear()),
18943 thisThurs = getThursdayThisWeek(date);
18945 var diff = +thisThurs - +firstThurs,
18946 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week
18948 return padNumber(result, size);
18952 function ampmGetter(date, formats) {
18953 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
18956 function eraGetter(date, formats) {
18957 return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1];
18960 function longEraGetter(date, formats) {
18961 return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1];
18964 var DATE_FORMATS = {
18965 yyyy: dateGetter('FullYear', 4),
18966 yy: dateGetter('FullYear', 2, 0, true),
18967 y: dateGetter('FullYear', 1),
18968 MMMM: dateStrGetter('Month'),
18969 MMM: dateStrGetter('Month', true),
18970 MM: dateGetter('Month', 2, 1),
18971 M: dateGetter('Month', 1, 1),
18972 dd: dateGetter('Date', 2),
18973 d: dateGetter('Date', 1),
18974 HH: dateGetter('Hours', 2),
18975 H: dateGetter('Hours', 1),
18976 hh: dateGetter('Hours', 2, -12),
18977 h: dateGetter('Hours', 1, -12),
18978 mm: dateGetter('Minutes', 2),
18979 m: dateGetter('Minutes', 1),
18980 ss: dateGetter('Seconds', 2),
18981 s: dateGetter('Seconds', 1),
18982 // while ISO 8601 requires fractions to be prefixed with `.` or `,`
18983 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions
18984 sss: dateGetter('Milliseconds', 3),
18985 EEEE: dateStrGetter('Day'),
18986 EEE: dateStrGetter('Day', true),
18994 GGGG: longEraGetter
18997 var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,
18998 NUMBER_STRING = /^\-?\d+$/;
19006 * Formats `date` to a string based on the requested `format`.
19008 * `format` string can be composed of the following elements:
19010 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
19011 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
19012 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
19013 * * `'MMMM'`: Month in year (January-December)
19014 * * `'MMM'`: Month in year (Jan-Dec)
19015 * * `'MM'`: Month in year, padded (01-12)
19016 * * `'M'`: Month in year (1-12)
19017 * * `'dd'`: Day in month, padded (01-31)
19018 * * `'d'`: Day in month (1-31)
19019 * * `'EEEE'`: Day in Week,(Sunday-Saturday)
19020 * * `'EEE'`: Day in Week, (Sun-Sat)
19021 * * `'HH'`: Hour in day, padded (00-23)
19022 * * `'H'`: Hour in day (0-23)
19023 * * `'hh'`: Hour in AM/PM, padded (01-12)
19024 * * `'h'`: Hour in AM/PM, (1-12)
19025 * * `'mm'`: Minute in hour, padded (00-59)
19026 * * `'m'`: Minute in hour (0-59)
19027 * * `'ss'`: Second in minute, padded (00-59)
19028 * * `'s'`: Second in minute (0-59)
19029 * * `'sss'`: Millisecond in second, padded (000-999)
19030 * * `'a'`: AM/PM marker
19031 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
19032 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year
19033 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year
19034 * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD')
19035 * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini')
19037 * `format` string can also be one of the following predefined
19038 * {@link guide/i18n localizable formats}:
19040 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
19041 * (e.g. Sep 3, 2010 12:05:08 PM)
19042 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM)
19043 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale
19044 * (e.g. Friday, September 3, 2010)
19045 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010)
19046 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010)
19047 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
19048 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM)
19049 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM)
19051 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g.
19052 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence
19053 * (e.g. `"h 'o''clock'"`).
19055 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
19056 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its
19057 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is
19058 * specified in the string input, the time is considered to be in the local timezone.
19059 * @param {string=} format Formatting rules (see Description). If not specified,
19060 * `mediumDate` is used.
19061 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the
19062 * continental US time zone abbreviations, but for general use, use a time zone offset, for
19063 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
19064 * If not specified, the timezone of the browser will be used.
19065 * @returns {string} Formatted string or the input if input is not recognized as date/millis.
19069 <file name="index.html">
19070 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>:
19071 <span>{{1288323623006 | date:'medium'}}</span><br>
19072 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
19073 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br>
19074 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
19075 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br>
19076 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>:
19077 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br>
19079 <file name="protractor.js" type="protractor">
19080 it('should format date', function() {
19081 expect(element(by.binding("1288323623006 | date:'medium'")).getText()).
19082 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
19083 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()).
19084 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/);
19085 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()).
19086 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
19087 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()).
19088 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/);
19093 dateFilter.$inject = ['$locale'];
19094 function dateFilter($locale) {
19097 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
19098 // 1 2 3 4 5 6 7 8 9 10 11
19099 function jsonStringToDate(string) {
19101 if (match = string.match(R_ISO8601_STR)) {
19102 var date = new Date(0),
19105 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear,
19106 timeSetter = match[8] ? date.setUTCHours : date.setHours;
19109 tzHour = toInt(match[9] + match[10]);
19110 tzMin = toInt(match[9] + match[11]);
19112 dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
19113 var h = toInt(match[4] || 0) - tzHour;
19114 var m = toInt(match[5] || 0) - tzMin;
19115 var s = toInt(match[6] || 0);
19116 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
19117 timeSetter.call(date, h, m, s, ms);
19124 return function(date, format, timezone) {
19129 format = format || 'mediumDate';
19130 format = $locale.DATETIME_FORMATS[format] || format;
19131 if (isString(date)) {
19132 date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date);
19135 if (isNumber(date)) {
19136 date = new Date(date);
19139 if (!isDate(date) || !isFinite(date.getTime())) {
19144 match = DATE_FORMATS_SPLIT.exec(format);
19146 parts = concat(parts, match, 1);
19147 format = parts.pop();
19149 parts.push(format);
19154 var dateTimezoneOffset = date.getTimezoneOffset();
19156 dateTimezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
19157 date = convertTimezoneToLocal(date, timezone, true);
19159 forEach(parts, function(value) {
19160 fn = DATE_FORMATS[value];
19161 text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset)
19162 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
19176 * Allows you to convert a JavaScript object into JSON string.
19178 * This filter is mostly useful for debugging. When using the double curly {{value}} notation
19179 * the binding is automatically converted to JSON.
19181 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
19182 * @param {number=} spacing The number of spaces to use per indentation, defaults to 2.
19183 * @returns {string} JSON string.
19188 <file name="index.html">
19189 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre>
19190 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre>
19192 <file name="protractor.js" type="protractor">
19193 it('should jsonify filtered objects', function() {
19194 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
19195 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
19201 function jsonFilter() {
19202 return function(object, spacing) {
19203 if (isUndefined(spacing)) {
19206 return toJson(object, spacing);
19216 * Converts string to lowercase.
19217 * @see angular.lowercase
19219 var lowercaseFilter = valueFn(lowercase);
19227 * Converts string to uppercase.
19228 * @see angular.uppercase
19230 var uppercaseFilter = valueFn(uppercase);
19238 * Creates a new array or string containing only a specified number of elements. The elements
19239 * are taken from either the beginning or the end of the source array, string or number, as specified by
19240 * the value and sign (positive or negative) of `limit`. If a number is used as input, it is
19241 * converted to a string.
19243 * @param {Array|string|number} input Source array, string or number to be limited.
19244 * @param {string|number} limit The length of the returned array or string. If the `limit` number
19245 * is positive, `limit` number of items from the beginning of the source array/string are copied.
19246 * If the number is negative, `limit` number of items from the end of the source array/string
19247 * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined,
19248 * the input will be returned unchanged.
19249 * @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin`
19250 * indicates an offset from the end of `input`. Defaults to `0`.
19251 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
19252 * had less than `limit` elements.
19255 <example module="limitToExample">
19256 <file name="index.html">
19258 angular.module('limitToExample', [])
19259 .controller('ExampleController', ['$scope', function($scope) {
19260 $scope.numbers = [1,2,3,4,5,6,7,8,9];
19261 $scope.letters = "abcdefghi";
19262 $scope.longNumber = 2345432342;
19263 $scope.numLimit = 3;
19264 $scope.letterLimit = 3;
19265 $scope.longNumberLimit = 3;
19268 <div ng-controller="ExampleController">
19270 Limit {{numbers}} to:
19271 <input type="number" step="1" ng-model="numLimit">
19273 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p>
19275 Limit {{letters}} to:
19276 <input type="number" step="1" ng-model="letterLimit">
19278 <p>Output letters: {{ letters | limitTo:letterLimit }}</p>
19280 Limit {{longNumber}} to:
19281 <input type="number" step="1" ng-model="longNumberLimit">
19283 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p>
19286 <file name="protractor.js" type="protractor">
19287 var numLimitInput = element(by.model('numLimit'));
19288 var letterLimitInput = element(by.model('letterLimit'));
19289 var longNumberLimitInput = element(by.model('longNumberLimit'));
19290 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit'));
19291 var limitedLetters = element(by.binding('letters | limitTo:letterLimit'));
19292 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit'));
19294 it('should limit the number array to first three items', function() {
19295 expect(numLimitInput.getAttribute('value')).toBe('3');
19296 expect(letterLimitInput.getAttribute('value')).toBe('3');
19297 expect(longNumberLimitInput.getAttribute('value')).toBe('3');
19298 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]');
19299 expect(limitedLetters.getText()).toEqual('Output letters: abc');
19300 expect(limitedLongNumber.getText()).toEqual('Output long number: 234');
19303 // There is a bug in safari and protractor that doesn't like the minus key
19304 // it('should update the output when -3 is entered', function() {
19305 // numLimitInput.clear();
19306 // numLimitInput.sendKeys('-3');
19307 // letterLimitInput.clear();
19308 // letterLimitInput.sendKeys('-3');
19309 // longNumberLimitInput.clear();
19310 // longNumberLimitInput.sendKeys('-3');
19311 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
19312 // expect(limitedLetters.getText()).toEqual('Output letters: ghi');
19313 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342');
19316 it('should not exceed the maximum size of input array', function() {
19317 numLimitInput.clear();
19318 numLimitInput.sendKeys('100');
19319 letterLimitInput.clear();
19320 letterLimitInput.sendKeys('100');
19321 longNumberLimitInput.clear();
19322 longNumberLimitInput.sendKeys('100');
19323 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]');
19324 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi');
19325 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342');
19330 function limitToFilter() {
19331 return function(input, limit, begin) {
19332 if (Math.abs(Number(limit)) === Infinity) {
19333 limit = Number(limit);
19335 limit = toInt(limit);
19337 if (isNaN(limit)) return input;
19339 if (isNumber(input)) input = input.toString();
19340 if (!isArray(input) && !isString(input)) return input;
19342 begin = (!begin || isNaN(begin)) ? 0 : toInt(begin);
19343 begin = (begin < 0) ? Math.max(0, input.length + begin) : begin;
19346 return input.slice(begin, begin + limit);
19349 return input.slice(limit, input.length);
19351 return input.slice(Math.max(0, begin + limit), begin);
19363 * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically
19364 * for strings and numerically for numbers. Note: if you notice numbers are not being sorted
19365 * as expected, make sure they are actually being saved as numbers and not strings.
19367 * @param {Array} array The array to sort.
19368 * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be
19369 * used by the comparator to determine the order of elements.
19373 * - `function`: Getter function. The result of this function will be sorted using the
19374 * `<`, `===`, `>` operator.
19375 * - `string`: An Angular expression. The result of this expression is used to compare elements
19376 * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by
19377 * 3 first characters of a property called `name`). The result of a constant expression
19378 * is interpreted as a property name to be used in comparisons (for example `"special name"`
19379 * to sort object by the value of their `special name` property). An expression can be
19380 * optionally prefixed with `+` or `-` to control ascending or descending sort order
19381 * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array
19382 * element itself is used to compare where sorting.
19383 * - `Array`: An array of function or string predicates. The first predicate in the array
19384 * is used for sorting, but when two items are equivalent, the next predicate is used.
19386 * If the predicate is missing or empty then it defaults to `'+'`.
19388 * @param {boolean=} reverse Reverse the order of the array.
19389 * @returns {Array} Sorted copy of the source array.
19393 * The example below demonstrates a simple ngRepeat, where the data is sorted
19394 * by age in descending order (predicate is set to `'-age'`).
19395 * `reverse` is not set, which means it defaults to `false`.
19396 <example module="orderByExample">
19397 <file name="index.html">
19399 angular.module('orderByExample', [])
19400 .controller('ExampleController', ['$scope', function($scope) {
19402 [{name:'John', phone:'555-1212', age:10},
19403 {name:'Mary', phone:'555-9876', age:19},
19404 {name:'Mike', phone:'555-4321', age:21},
19405 {name:'Adam', phone:'555-5678', age:35},
19406 {name:'Julie', phone:'555-8765', age:29}];
19409 <div ng-controller="ExampleController">
19410 <table class="friend">
19413 <th>Phone Number</th>
19416 <tr ng-repeat="friend in friends | orderBy:'-age'">
19417 <td>{{friend.name}}</td>
19418 <td>{{friend.phone}}</td>
19419 <td>{{friend.age}}</td>
19426 * The predicate and reverse parameters can be controlled dynamically through scope properties,
19427 * as shown in the next example.
19429 <example module="orderByExample">
19430 <file name="index.html">
19432 angular.module('orderByExample', [])
19433 .controller('ExampleController', ['$scope', function($scope) {
19435 [{name:'John', phone:'555-1212', age:10},
19436 {name:'Mary', phone:'555-9876', age:19},
19437 {name:'Mike', phone:'555-4321', age:21},
19438 {name:'Adam', phone:'555-5678', age:35},
19439 {name:'Julie', phone:'555-8765', age:29}];
19440 $scope.predicate = 'age';
19441 $scope.reverse = true;
19442 $scope.order = function(predicate) {
19443 $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
19444 $scope.predicate = predicate;
19448 <style type="text/css">
19452 .sortorder.reverse:after {
19456 <div ng-controller="ExampleController">
19457 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
19459 [ <a href="" ng-click="predicate=''">unsorted</a> ]
19460 <table class="friend">
19463 <a href="" ng-click="order('name')">Name</a>
19464 <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
19467 <a href="" ng-click="order('phone')">Phone Number</a>
19468 <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span>
19471 <a href="" ng-click="order('age')">Age</a>
19472 <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span>
19475 <tr ng-repeat="friend in friends | orderBy:predicate:reverse">
19476 <td>{{friend.name}}</td>
19477 <td>{{friend.phone}}</td>
19478 <td>{{friend.age}}</td>
19485 * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the
19486 * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the
19487 * desired parameters.
19492 <example module="orderByExample">
19493 <file name="index.html">
19494 <div ng-controller="ExampleController">
19495 <table class="friend">
19497 <th><a href="" ng-click="reverse=false;order('name', false)">Name</a>
19498 (<a href="" ng-click="order('-name',false)">^</a>)</th>
19499 <th><a href="" ng-click="reverse=!reverse;order('phone', reverse)">Phone Number</a></th>
19500 <th><a href="" ng-click="reverse=!reverse;order('age',reverse)">Age</a></th>
19502 <tr ng-repeat="friend in friends">
19503 <td>{{friend.name}}</td>
19504 <td>{{friend.phone}}</td>
19505 <td>{{friend.age}}</td>
19511 <file name="script.js">
19512 angular.module('orderByExample', [])
19513 .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) {
19514 var orderBy = $filter('orderBy');
19516 { name: 'John', phone: '555-1212', age: 10 },
19517 { name: 'Mary', phone: '555-9876', age: 19 },
19518 { name: 'Mike', phone: '555-4321', age: 21 },
19519 { name: 'Adam', phone: '555-5678', age: 35 },
19520 { name: 'Julie', phone: '555-8765', age: 29 }
19522 $scope.order = function(predicate, reverse) {
19523 $scope.friends = orderBy($scope.friends, predicate, reverse);
19525 $scope.order('-age',false);
19530 orderByFilter.$inject = ['$parse'];
19531 function orderByFilter($parse) {
19532 return function(array, sortPredicate, reverseOrder) {
19534 if (!(isArrayLike(array))) return array;
19536 if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; }
19537 if (sortPredicate.length === 0) { sortPredicate = ['+']; }
19539 var predicates = processPredicates(sortPredicate, reverseOrder);
19540 // Add a predicate at the end that evaluates to the element index. This makes the
19541 // sort stable as it works as a tie-breaker when all the input predicates cannot
19542 // distinguish between two elements.
19543 predicates.push({ get: function() { return {}; }, descending: reverseOrder ? -1 : 1});
19545 // The next three lines are a version of a Swartzian Transform idiom from Perl
19546 // (sometimes called the Decorate-Sort-Undecorate idiom)
19547 // See https://en.wikipedia.org/wiki/Schwartzian_transform
19548 var compareValues = Array.prototype.map.call(array, getComparisonObject);
19549 compareValues.sort(doComparison);
19550 array = compareValues.map(function(item) { return item.value; });
19554 function getComparisonObject(value, index) {
19557 predicateValues: predicates.map(function(predicate) {
19558 return getPredicateValue(predicate.get(value), index);
19563 function doComparison(v1, v2) {
19565 for (var index=0, length = predicates.length; index < length; ++index) {
19566 result = compare(v1.predicateValues[index], v2.predicateValues[index]) * predicates[index].descending;
19573 function processPredicates(sortPredicate, reverseOrder) {
19574 reverseOrder = reverseOrder ? -1 : 1;
19575 return sortPredicate.map(function(predicate) {
19576 var descending = 1, get = identity;
19578 if (isFunction(predicate)) {
19580 } else if (isString(predicate)) {
19581 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) {
19582 descending = predicate.charAt(0) == '-' ? -1 : 1;
19583 predicate = predicate.substring(1);
19585 if (predicate !== '') {
19586 get = $parse(predicate);
19587 if (get.constant) {
19589 get = function(value) { return value[key]; };
19593 return { get: get, descending: descending * reverseOrder };
19597 function isPrimitive(value) {
19598 switch (typeof value) {
19599 case 'number': /* falls through */
19600 case 'boolean': /* falls through */
19608 function objectValue(value, index) {
19609 // If `valueOf` is a valid function use that
19610 if (typeof value.valueOf === 'function') {
19611 value = value.valueOf();
19612 if (isPrimitive(value)) return value;
19614 // If `toString` is a valid function and not the one from `Object.prototype` use that
19615 if (hasCustomToString(value)) {
19616 value = value.toString();
19617 if (isPrimitive(value)) return value;
19619 // We have a basic object so we use the position of the object in the collection
19623 function getPredicateValue(value, index) {
19624 var type = typeof value;
19625 if (value === null) {
19628 } else if (type === 'string') {
19629 value = value.toLowerCase();
19630 } else if (type === 'object') {
19631 value = objectValue(value, index);
19633 return { value: value, type: type };
19636 function compare(v1, v2) {
19638 if (v1.type === v2.type) {
19639 if (v1.value !== v2.value) {
19640 result = v1.value < v2.value ? -1 : 1;
19643 result = v1.type < v2.type ? -1 : 1;
19649 function ngDirective(directive) {
19650 if (isFunction(directive)) {
19655 directive.restrict = directive.restrict || 'AC';
19656 return valueFn(directive);
19665 * Modifies the default behavior of the html A tag so that the default action is prevented when
19666 * the href attribute is empty.
19668 * This change permits the easy creation of action links with the `ngClick` directive
19669 * without changing the location or causing page reloads, e.g.:
19670 * `<a href="" ng-click="list.addItem()">Add Item</a>`
19672 var htmlAnchorDirective = valueFn({
19674 compile: function(element, attr) {
19675 if (!attr.href && !attr.xlinkHref) {
19676 return function(scope, element) {
19677 // If the linked element is not an anchor tag anymore, do nothing
19678 if (element[0].nodeName.toLowerCase() !== 'a') return;
19680 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
19681 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
19682 'xlink:href' : 'href';
19683 element.on('click', function(event) {
19684 // if we have no href url, then don't navigate anywhere.
19685 if (!element.attr(href)) {
19686 event.preventDefault();
19701 * Using Angular markup like `{{hash}}` in an href attribute will
19702 * make the link go to the wrong URL if the user clicks it before
19703 * Angular has a chance to replace the `{{hash}}` markup with its
19704 * value. Until Angular replaces the markup the link will be broken
19705 * and will most likely return a 404 error. The `ngHref` directive
19706 * solves this problem.
19708 * The wrong way to write it:
19710 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
19713 * The correct way to write it:
19715 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
19719 * @param {template} ngHref any string which can contain `{{}}` markup.
19722 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes
19723 * in links and their different behaviors:
19725 <file name="index.html">
19726 <input ng-model="value" /><br />
19727 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br />
19728 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br />
19729 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br />
19730 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br />
19731 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br />
19732 <a id="link-6" ng-href="{{value}}">link</a> (link, change location)
19734 <file name="protractor.js" type="protractor">
19735 it('should execute ng-click but not reload when href without value', function() {
19736 element(by.id('link-1')).click();
19737 expect(element(by.model('value')).getAttribute('value')).toEqual('1');
19738 expect(element(by.id('link-1')).getAttribute('href')).toBe('');
19741 it('should execute ng-click but not reload when href empty string', function() {
19742 element(by.id('link-2')).click();
19743 expect(element(by.model('value')).getAttribute('value')).toEqual('2');
19744 expect(element(by.id('link-2')).getAttribute('href')).toBe('');
19747 it('should execute ng-click and change url when ng-href specified', function() {
19748 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/);
19750 element(by.id('link-3')).click();
19752 // At this point, we navigate away from an Angular page, so we need
19753 // to use browser.driver to get the base webdriver.
19755 browser.wait(function() {
19756 return browser.driver.getCurrentUrl().then(function(url) {
19757 return url.match(/\/123$/);
19759 }, 5000, 'page should navigate to /123');
19762 it('should execute ng-click but not reload when href empty string and name specified', function() {
19763 element(by.id('link-4')).click();
19764 expect(element(by.model('value')).getAttribute('value')).toEqual('4');
19765 expect(element(by.id('link-4')).getAttribute('href')).toBe('');
19768 it('should execute ng-click but not reload when no href but name specified', function() {
19769 element(by.id('link-5')).click();
19770 expect(element(by.model('value')).getAttribute('value')).toEqual('5');
19771 expect(element(by.id('link-5')).getAttribute('href')).toBe(null);
19774 it('should only change url when only ng-href', function() {
19775 element(by.model('value')).clear();
19776 element(by.model('value')).sendKeys('6');
19777 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/);
19779 element(by.id('link-6')).click();
19781 // At this point, we navigate away from an Angular page, so we need
19782 // to use browser.driver to get the base webdriver.
19783 browser.wait(function() {
19784 return browser.driver.getCurrentUrl().then(function(url) {
19785 return url.match(/\/6$/);
19787 }, 5000, 'page should navigate to /6');
19800 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't
19801 * work right: The browser will fetch from the URL with the literal
19802 * text `{{hash}}` until Angular replaces the expression inside
19803 * `{{hash}}`. The `ngSrc` directive solves this problem.
19805 * The buggy way to write it:
19807 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/>
19810 * The correct way to write it:
19812 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" />
19816 * @param {template} ngSrc any string which can contain `{{}}` markup.
19826 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't
19827 * work right: The browser will fetch from the URL with the literal
19828 * text `{{hash}}` until Angular replaces the expression inside
19829 * `{{hash}}`. The `ngSrcset` directive solves this problem.
19831 * The buggy way to write it:
19833 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/>
19836 * The correct way to write it:
19838 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" />
19842 * @param {template} ngSrcset any string which can contain `{{}}` markup.
19853 * This directive sets the `disabled` attribute on the element if the
19854 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy.
19856 * A special directive is necessary because we cannot use interpolation inside the `disabled`
19857 * attribute. The following example would make the button enabled on Chrome/Firefox
19858 * but not on older IEs:
19861 * <!-- See below for an example of ng-disabled being used correctly -->
19862 * <div ng-init="isDisabled = false">
19863 * <button disabled="{{isDisabled}}">Disabled</button>
19867 * This is because the HTML specification does not require browsers to preserve the values of
19868 * boolean attributes such as `disabled` (Their presence means true and their absence means false.)
19869 * If we put an Angular interpolation expression into such an attribute then the
19870 * binding information would be lost when the browser removes the attribute.
19874 <file name="index.html">
19875 <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/>
19876 <button ng-model="button" ng-disabled="checked">Button</button>
19878 <file name="protractor.js" type="protractor">
19879 it('should toggle button', function() {
19880 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
19881 element(by.model('checked')).click();
19882 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy();
19888 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
19889 * then the `disabled` attribute will be set on the element
19900 * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy.
19902 * Note that this directive should not be used together with {@link ngModel `ngModel`},
19903 * as this can lead to unexpected behavior.
19905 * ### Why do we need `ngChecked`?
19907 * The HTML specification does not require browsers to preserve the values of boolean attributes
19908 * such as checked. (Their presence means true and their absence means false.)
19909 * If we put an Angular interpolation expression into such an attribute then the
19910 * binding information would be lost when the browser removes the attribute.
19911 * The `ngChecked` directive solves this problem for the `checked` attribute.
19912 * This complementary directive is not removed by the browser and so provides
19913 * a permanent reliable place to store the binding information.
19916 <file name="index.html">
19917 <label>Check me to check both: <input type="checkbox" ng-model="master"></label><br/>
19918 <input id="checkSlave" type="checkbox" ng-checked="master" aria-label="Slave input">
19920 <file name="protractor.js" type="protractor">
19921 it('should check both checkBoxes', function() {
19922 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy();
19923 element(by.model('master')).click();
19924 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy();
19930 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy,
19931 * then the `checked` attribute will be set on the element
19942 * The HTML specification does not require browsers to preserve the values of boolean attributes
19943 * such as readonly. (Their presence means true and their absence means false.)
19944 * If we put an Angular interpolation expression into such an attribute then the
19945 * binding information would be lost when the browser removes the attribute.
19946 * The `ngReadonly` directive solves this problem for the `readonly` attribute.
19947 * This complementary directive is not removed by the browser and so provides
19948 * a permanent reliable place to store the binding information.
19951 <file name="index.html">
19952 <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/>
19953 <input type="text" ng-readonly="checked" value="I'm Angular" aria-label="Readonly field" />
19955 <file name="protractor.js" type="protractor">
19956 it('should toggle readonly attr', function() {
19957 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy();
19958 element(by.model('checked')).click();
19959 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy();
19965 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy,
19966 * then special attribute "readonly" will be set on the element
19977 * The HTML specification does not require browsers to preserve the values of boolean attributes
19978 * such as selected. (Their presence means true and their absence means false.)
19979 * If we put an Angular interpolation expression into such an attribute then the
19980 * binding information would be lost when the browser removes the attribute.
19981 * The `ngSelected` directive solves this problem for the `selected` attribute.
19982 * This complementary directive is not removed by the browser and so provides
19983 * a permanent reliable place to store the binding information.
19987 <file name="index.html">
19988 <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/>
19989 <select aria-label="ngSelected demo">
19990 <option>Hello!</option>
19991 <option id="greet" ng-selected="selected">Greetings!</option>
19994 <file name="protractor.js" type="protractor">
19995 it('should select Greetings!', function() {
19996 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
19997 element(by.model('selected')).click();
19998 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy();
20004 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy,
20005 * then special attribute "selected" will be set on the element
20015 * The HTML specification does not require browsers to preserve the values of boolean attributes
20016 * such as open. (Their presence means true and their absence means false.)
20017 * If we put an Angular interpolation expression into such an attribute then the
20018 * binding information would be lost when the browser removes the attribute.
20019 * The `ngOpen` directive solves this problem for the `open` attribute.
20020 * This complementary directive is not removed by the browser and so provides
20021 * a permanent reliable place to store the binding information.
20024 <file name="index.html">
20025 <label>Check me check multiple: <input type="checkbox" ng-model="open"></label><br/>
20026 <details id="details" ng-open="open">
20027 <summary>Show/Hide me</summary>
20030 <file name="protractor.js" type="protractor">
20031 it('should toggle open', function() {
20032 expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
20033 element(by.model('open')).click();
20034 expect(element(by.id('details')).getAttribute('open')).toBeTruthy();
20040 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy,
20041 * then special attribute "open" will be set on the element
20044 var ngAttributeAliasDirectives = {};
20046 // boolean attrs are evaluated
20047 forEach(BOOLEAN_ATTR, function(propName, attrName) {
20048 // binding to multiple is not supported
20049 if (propName == "multiple") return;
20051 function defaultLinkFn(scope, element, attr) {
20052 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
20053 attr.$set(attrName, !!value);
20057 var normalized = directiveNormalize('ng-' + attrName);
20058 var linkFn = defaultLinkFn;
20060 if (propName === 'checked') {
20061 linkFn = function(scope, element, attr) {
20062 // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input
20063 if (attr.ngModel !== attr[normalized]) {
20064 defaultLinkFn(scope, element, attr);
20069 ngAttributeAliasDirectives[normalized] = function() {
20078 // aliased input attrs are evaluated
20079 forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) {
20080 ngAttributeAliasDirectives[ngAttr] = function() {
20083 link: function(scope, element, attr) {
20084 //special case ngPattern when a literal regular expression value
20085 //is used as the expression (this way we don't have to watch anything).
20086 if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") {
20087 var match = attr.ngPattern.match(REGEX_STRING_REGEXP);
20089 attr.$set("ngPattern", new RegExp(match[1], match[2]));
20094 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) {
20095 attr.$set(ngAttr, value);
20102 // ng-src, ng-srcset, ng-href are interpolated
20103 forEach(['src', 'srcset', 'href'], function(attrName) {
20104 var normalized = directiveNormalize('ng-' + attrName);
20105 ngAttributeAliasDirectives[normalized] = function() {
20107 priority: 99, // it needs to run after the attributes are interpolated
20108 link: function(scope, element, attr) {
20109 var propName = attrName,
20112 if (attrName === 'href' &&
20113 toString.call(element.prop('href')) === '[object SVGAnimatedString]') {
20114 name = 'xlinkHref';
20115 attr.$attr[name] = 'xlink:href';
20119 attr.$observe(normalized, function(value) {
20121 if (attrName === 'href') {
20122 attr.$set(name, null);
20127 attr.$set(name, value);
20129 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
20130 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
20131 // to set the property as well to achieve the desired effect.
20132 // we use attr[attrName] value since $set can sanitize the url.
20133 if (msie && propName) element.prop(propName, attr[name]);
20140 /* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true
20142 var nullFormCtrl = {
20144 $$renameControl: nullFormRenameControl,
20145 $removeControl: noop,
20146 $setValidity: noop,
20148 $setPristine: noop,
20149 $setSubmitted: noop
20151 SUBMITTED_CLASS = 'ng-submitted';
20153 function nullFormRenameControl(control, name) {
20154 control.$name = name;
20159 * @name form.FormController
20161 * @property {boolean} $pristine True if user has not interacted with the form yet.
20162 * @property {boolean} $dirty True if user has already interacted with the form.
20163 * @property {boolean} $valid True if all of the containing forms and controls are valid.
20164 * @property {boolean} $invalid True if at least one containing control or form is invalid.
20165 * @property {boolean} $pending True if at least one containing control or form is pending.
20166 * @property {boolean} $submitted True if user has submitted the form even if its invalid.
20168 * @property {Object} $error Is an object hash, containing references to controls or
20169 * forms with failing validators, where:
20171 * - keys are validation tokens (error names),
20172 * - values are arrays of controls or forms that have a failing validator for given error name.
20174 * Built-in validation tokens:
20186 * - `datetimelocal`
20192 * `FormController` keeps track of all its controls and nested forms as well as the state of them,
20193 * such as being valid/invalid or dirty/pristine.
20195 * Each {@link ng.directive:form form} directive creates an instance
20196 * of `FormController`.
20199 //asks for $scope to fool the BC controller module
20200 FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate'];
20201 function FormController(element, attrs, $scope, $animate, $interpolate) {
20207 form.$$success = {};
20208 form.$pending = undefined;
20209 form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope);
20210 form.$dirty = false;
20211 form.$pristine = true;
20212 form.$valid = true;
20213 form.$invalid = false;
20214 form.$submitted = false;
20215 form.$$parentForm = nullFormCtrl;
20219 * @name form.FormController#$rollbackViewValue
20222 * Rollback all form controls pending updates to the `$modelValue`.
20224 * Updates may be pending by a debounced event or because the input is waiting for a some future
20225 * event defined in `ng-model-options`. This method is typically needed by the reset button of
20226 * a form that uses `ng-model-options` to pend updates.
20228 form.$rollbackViewValue = function() {
20229 forEach(controls, function(control) {
20230 control.$rollbackViewValue();
20236 * @name form.FormController#$commitViewValue
20239 * Commit all form controls pending updates to the `$modelValue`.
20241 * Updates may be pending by a debounced event or because the input is waiting for a some future
20242 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController`
20243 * usually handles calling this in response to input events.
20245 form.$commitViewValue = function() {
20246 forEach(controls, function(control) {
20247 control.$commitViewValue();
20253 * @name form.FormController#$addControl
20254 * @param {object} control control object, either a {@link form.FormController} or an
20255 * {@link ngModel.NgModelController}
20258 * Register a control with the form. Input elements using ngModelController do this automatically
20259 * when they are linked.
20261 * Note that the current state of the control will not be reflected on the new parent form. This
20262 * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine`
20265 * However, if the method is used programmatically, for example by adding dynamically created controls,
20266 * or controls that have been previously removed without destroying their corresponding DOM element,
20267 * it's the developers responsiblity to make sure the current state propagates to the parent form.
20269 * For example, if an input control is added that is already `$dirty` and has `$error` properties,
20270 * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form.
20272 form.$addControl = function(control) {
20273 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored
20274 // and not added to the scope. Now we throw an error.
20275 assertNotHasOwnProperty(control.$name, 'input');
20276 controls.push(control);
20278 if (control.$name) {
20279 form[control.$name] = control;
20282 control.$$parentForm = form;
20285 // Private API: rename a form control
20286 form.$$renameControl = function(control, newName) {
20287 var oldName = control.$name;
20289 if (form[oldName] === control) {
20290 delete form[oldName];
20292 form[newName] = control;
20293 control.$name = newName;
20298 * @name form.FormController#$removeControl
20299 * @param {object} control control object, either a {@link form.FormController} or an
20300 * {@link ngModel.NgModelController}
20303 * Deregister a control from the form.
20305 * Input elements using ngModelController do this automatically when they are destroyed.
20307 * Note that only the removed control's validation state (`$errors`etc.) will be removed from the
20308 * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be
20309 * different from case to case. For example, removing the only `$dirty` control from a form may or
20310 * may not mean that the form is still `$dirty`.
20312 form.$removeControl = function(control) {
20313 if (control.$name && form[control.$name] === control) {
20314 delete form[control.$name];
20316 forEach(form.$pending, function(value, name) {
20317 form.$setValidity(name, null, control);
20319 forEach(form.$error, function(value, name) {
20320 form.$setValidity(name, null, control);
20322 forEach(form.$$success, function(value, name) {
20323 form.$setValidity(name, null, control);
20326 arrayRemove(controls, control);
20327 control.$$parentForm = nullFormCtrl;
20333 * @name form.FormController#$setValidity
20336 * Sets the validity of a form control.
20338 * This method will also propagate to parent forms.
20340 addSetValidityMethod({
20343 set: function(object, property, controller) {
20344 var list = object[property];
20346 object[property] = [controller];
20348 var index = list.indexOf(controller);
20349 if (index === -1) {
20350 list.push(controller);
20354 unset: function(object, property, controller) {
20355 var list = object[property];
20359 arrayRemove(list, controller);
20360 if (list.length === 0) {
20361 delete object[property];
20369 * @name form.FormController#$setDirty
20372 * Sets the form to a dirty state.
20374 * This method can be called to add the 'ng-dirty' class and set the form to a dirty
20375 * state (ng-dirty class). This method will also propagate to parent forms.
20377 form.$setDirty = function() {
20378 $animate.removeClass(element, PRISTINE_CLASS);
20379 $animate.addClass(element, DIRTY_CLASS);
20380 form.$dirty = true;
20381 form.$pristine = false;
20382 form.$$parentForm.$setDirty();
20387 * @name form.FormController#$setPristine
20390 * Sets the form to its pristine state.
20392 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine
20393 * state (ng-pristine class). This method will also propagate to all the controls contained
20396 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
20397 * saving or resetting it.
20399 form.$setPristine = function() {
20400 $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS);
20401 form.$dirty = false;
20402 form.$pristine = true;
20403 form.$submitted = false;
20404 forEach(controls, function(control) {
20405 control.$setPristine();
20411 * @name form.FormController#$setUntouched
20414 * Sets the form to its untouched state.
20416 * This method can be called to remove the 'ng-touched' class and set the form controls to their
20417 * untouched state (ng-untouched class).
20419 * Setting a form controls back to their untouched state is often useful when setting the form
20420 * back to its pristine state.
20422 form.$setUntouched = function() {
20423 forEach(controls, function(control) {
20424 control.$setUntouched();
20430 * @name form.FormController#$setSubmitted
20433 * Sets the form to its submitted state.
20435 form.$setSubmitted = function() {
20436 $animate.addClass(element, SUBMITTED_CLASS);
20437 form.$submitted = true;
20438 form.$$parentForm.$setSubmitted();
20448 * Nestable alias of {@link ng.directive:form `form`} directive. HTML
20449 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
20450 * sub-group of controls needs to be determined.
20452 * Note: the purpose of `ngForm` is to group controls,
20453 * but not to be a replacement for the `<form>` tag with all of its capabilities
20454 * (e.g. posting to the server, ...).
20456 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into
20457 * related scope, under this name.
20467 * Directive that instantiates
20468 * {@link form.FormController FormController}.
20470 * If the `name` attribute is specified, the form controller is published onto the current scope under
20473 * # Alias: {@link ng.directive:ngForm `ngForm`}
20475 * In Angular, forms can be nested. This means that the outer form is valid when all of the child
20476 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so
20477 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to
20478 * `<form>` but can be nested. This allows you to have nested forms, which is very useful when
20479 * using Angular validation directives in forms that are dynamically generated using the
20480 * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name`
20481 * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an
20482 * `ngForm` directive and nest these in an outer `form` element.
20486 * - `ng-valid` is set if the form is valid.
20487 * - `ng-invalid` is set if the form is invalid.
20488 * - `ng-pending` is set if the form is pending.
20489 * - `ng-pristine` is set if the form is pristine.
20490 * - `ng-dirty` is set if the form is dirty.
20491 * - `ng-submitted` is set if the form was submitted.
20493 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
20496 * # Submitting a form and preventing the default action
20498 * Since the role of forms in client-side Angular applications is different than in classical
20499 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full
20500 * page reload that sends the data to the server. Instead some javascript logic should be triggered
20501 * to handle the form submission in an application-specific way.
20503 * For this reason, Angular prevents the default action (form submission to the server) unless the
20504 * `<form>` element has an `action` attribute specified.
20506 * You can use one of the following two ways to specify what javascript method should be called when
20507 * a form is submitted:
20509 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element
20510 * - {@link ng.directive:ngClick ngClick} directive on the first
20511 * button or input field of type submit (input[type=submit])
20513 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit}
20514 * or {@link ng.directive:ngClick ngClick} directives.
20515 * This is because of the following form submission rules in the HTML specification:
20517 * - If a form has only one input field then hitting enter in this field triggers form submit
20519 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter
20520 * doesn't trigger submit
20521 * - if a form has one or more input fields and one or more buttons or input[type=submit] then
20522 * hitting enter in any of the input fields will trigger the click handler on the *first* button or
20523 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
20525 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is
20526 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
20527 * to have access to the updated model.
20529 * ## Animation Hooks
20531 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed.
20532 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any
20533 * other validations that are performed within the form. Animations in ngForm are similar to how
20534 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well
20535 * as JS animations.
20537 * The following example shows a simple way to utilize CSS transitions to style a form element
20538 * that has been rendered as invalid after it has been validated:
20541 * //be sure to include ngAnimate as a module to hook into more
20542 * //advanced animations
20544 * transition:0.5s linear all;
20545 * background: white;
20547 * .my-form.ng-invalid {
20554 <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample">
20555 <file name="index.html">
20557 angular.module('formExample', [])
20558 .controller('FormController', ['$scope', function($scope) {
20559 $scope.userType = 'guest';
20564 transition:all linear 0.5s;
20565 background: transparent;
20567 .my-form.ng-invalid {
20571 <form name="myForm" ng-controller="FormController" class="my-form">
20572 userType: <input name="input" ng-model="userType" required>
20573 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br>
20574 <code>userType = {{userType}}</code><br>
20575 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br>
20576 <code>myForm.input.$error = {{myForm.input.$error}}</code><br>
20577 <code>myForm.$valid = {{myForm.$valid}}</code><br>
20578 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br>
20581 <file name="protractor.js" type="protractor">
20582 it('should initialize to model', function() {
20583 var userType = element(by.binding('userType'));
20584 var valid = element(by.binding('myForm.input.$valid'));
20586 expect(userType.getText()).toContain('guest');
20587 expect(valid.getText()).toContain('true');
20590 it('should be invalid if empty', function() {
20591 var userType = element(by.binding('userType'));
20592 var valid = element(by.binding('myForm.input.$valid'));
20593 var userInput = element(by.model('userType'));
20596 userInput.sendKeys('');
20598 expect(userType.getText()).toEqual('userType =');
20599 expect(valid.getText()).toContain('false');
20604 * @param {string=} name Name of the form. If specified, the form controller will be published into
20605 * related scope, under this name.
20607 var formDirectiveFactory = function(isNgForm) {
20608 return ['$timeout', '$parse', function($timeout, $parse) {
20609 var formDirective = {
20611 restrict: isNgForm ? 'EAC' : 'E',
20612 require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form
20613 controller: FormController,
20614 compile: function ngFormCompile(formElement, attr) {
20615 // Setup initial state of the control
20616 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);
20618 var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false);
20621 pre: function ngFormPreLink(scope, formElement, attr, ctrls) {
20622 var controller = ctrls[0];
20624 // if `action` attr is not present on the form, prevent the default action (submission)
20625 if (!('action' in attr)) {
20626 // we can't use jq events because if a form is destroyed during submission the default
20627 // action is not prevented. see #1238
20629 // IE 9 is not affected because it doesn't fire a submit event and try to do a full
20630 // page reload if the form was destroyed by submission of the form via a click handler
20631 // on a button in the form. Looks like an IE9 specific bug.
20632 var handleFormSubmission = function(event) {
20633 scope.$apply(function() {
20634 controller.$commitViewValue();
20635 controller.$setSubmitted();
20638 event.preventDefault();
20641 addEventListenerFn(formElement[0], 'submit', handleFormSubmission);
20643 // unregister the preventDefault listener so that we don't not leak memory but in a
20644 // way that will achieve the prevention of the default action.
20645 formElement.on('$destroy', function() {
20646 $timeout(function() {
20647 removeEventListenerFn(formElement[0], 'submit', handleFormSubmission);
20652 var parentFormCtrl = ctrls[1] || controller.$$parentForm;
20653 parentFormCtrl.$addControl(controller);
20655 var setter = nameAttr ? getSetter(controller.$name) : noop;
20658 setter(scope, controller);
20659 attr.$observe(nameAttr, function(newValue) {
20660 if (controller.$name === newValue) return;
20661 setter(scope, undefined);
20662 controller.$$parentForm.$$renameControl(controller, newValue);
20663 setter = getSetter(controller.$name);
20664 setter(scope, controller);
20667 formElement.on('$destroy', function() {
20668 controller.$$parentForm.$removeControl(controller);
20669 setter(scope, undefined);
20670 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
20677 return formDirective;
20679 function getSetter(expression) {
20680 if (expression === '') {
20681 //create an assignable expression, so forms with an empty name can be renamed later
20682 return $parse('this[""]').assign;
20684 return $parse(expression).assign || noop;
20689 var formDirective = formDirectiveFactory();
20690 var ngFormDirective = formDirectiveFactory(true);
20692 /* global VALID_CLASS: false,
20693 INVALID_CLASS: false,
20694 PRISTINE_CLASS: false,
20695 DIRTY_CLASS: false,
20696 UNTOUCHED_CLASS: false,
20697 TOUCHED_CLASS: false,
20698 ngModelMinErr: false,
20701 // Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231
20702 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)/;
20703 // See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987)
20704 var URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.?+=&%@\-/]*)?$/;
20705 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;
20706 var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/;
20707 var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/;
20708 var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
20709 var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/;
20710 var MONTH_REGEXP = /^(\d{4})-(\d\d)$/;
20711 var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
20717 * @name input[text]
20720 * Standard HTML text input with angular data binding, inherited by most of the `input` elements.
20723 * @param {string} ngModel Assignable angular expression to data-bind to.
20724 * @param {string=} name Property name of the form under which the control is published.
20725 * @param {string=} required Adds `required` validation error key if the value is not entered.
20726 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20727 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20728 * `required` when you want to data-bind to the `required` attribute.
20729 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
20731 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
20732 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
20734 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
20735 * that contains the regular expression body that will be converted to a regular expression
20736 * as in the ngPattern directive.
20737 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
20738 * a RegExp found by evaluating the Angular expression given in the attribute value.
20739 * If the expression evaluates to a RegExp object, then this is used directly.
20740 * If the expression evaluates to a string, then it will be converted to a RegExp
20741 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
20742 * `new RegExp('^abc$')`.<br />
20743 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
20744 * start at the index of the last search's match, thus not taking the whole input value into
20746 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20747 * interaction with the input element.
20748 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
20749 * This parameter is ignored for input[type=password] controls, which will never trim the
20753 <example name="text-input-directive" module="textInputExample">
20754 <file name="index.html">
20756 angular.module('textInputExample', [])
20757 .controller('ExampleController', ['$scope', function($scope) {
20760 word: /^\s*\w*\s*$/
20764 <form name="myForm" ng-controller="ExampleController">
20765 <label>Single word:
20766 <input type="text" name="input" ng-model="example.text"
20767 ng-pattern="example.word" required ng-trim="false">
20770 <span class="error" ng-show="myForm.input.$error.required">
20772 <span class="error" ng-show="myForm.input.$error.pattern">
20773 Single word only!</span>
20775 <tt>text = {{example.text}}</tt><br/>
20776 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20777 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20778 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20779 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20782 <file name="protractor.js" type="protractor">
20783 var text = element(by.binding('example.text'));
20784 var valid = element(by.binding('myForm.input.$valid'));
20785 var input = element(by.model('example.text'));
20787 it('should initialize to model', function() {
20788 expect(text.getText()).toContain('guest');
20789 expect(valid.getText()).toContain('true');
20792 it('should be invalid if empty', function() {
20794 input.sendKeys('');
20796 expect(text.getText()).toEqual('text =');
20797 expect(valid.getText()).toContain('false');
20800 it('should be invalid if multi word', function() {
20802 input.sendKeys('hello world');
20804 expect(valid.getText()).toContain('false');
20809 'text': textInputType,
20813 * @name input[date]
20816 * Input with date validation and transformation. In browsers that do not yet support
20817 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601
20818 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many
20819 * modern browsers do not yet support this input type, it is important to provide cues to users on the
20820 * expected input format via a placeholder or label.
20822 * The model must always be a Date object, otherwise Angular will throw an error.
20823 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
20825 * The timezone to be used to read/write the `Date` instance in the model can be defined using
20826 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
20828 * @param {string} ngModel Assignable angular expression to data-bind to.
20829 * @param {string=} name Property name of the form under which the control is published.
20830 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
20831 * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
20832 * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5
20833 * constraint validation.
20834 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be
20835 * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
20836 * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5
20837 * constraint validation.
20838 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string
20839 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
20840 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string
20841 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
20842 * @param {string=} required Sets `required` validation error key if the value is not entered.
20843 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20844 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20845 * `required` when you want to data-bind to the `required` attribute.
20846 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20847 * interaction with the input element.
20850 <example name="date-input-directive" module="dateInputExample">
20851 <file name="index.html">
20853 angular.module('dateInputExample', [])
20854 .controller('DateController', ['$scope', function($scope) {
20856 value: new Date(2013, 9, 22)
20860 <form name="myForm" ng-controller="DateController as dateCtrl">
20861 <label for="exampleInput">Pick a date in 2013:</label>
20862 <input type="date" id="exampleInput" name="input" ng-model="example.value"
20863 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required />
20865 <span class="error" ng-show="myForm.input.$error.required">
20867 <span class="error" ng-show="myForm.input.$error.date">
20868 Not a valid date!</span>
20870 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/>
20871 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20872 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20873 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20874 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20877 <file name="protractor.js" type="protractor">
20878 var value = element(by.binding('example.value | date: "yyyy-MM-dd"'));
20879 var valid = element(by.binding('myForm.input.$valid'));
20880 var input = element(by.model('example.value'));
20882 // currently protractor/webdriver does not support
20883 // sending keys to all known HTML5 input controls
20884 // for various browsers (see https://github.com/angular/protractor/issues/562).
20885 function setInput(val) {
20886 // set the value of the element and force validation.
20887 var scr = "var ipt = document.getElementById('exampleInput'); " +
20888 "ipt.value = '" + val + "';" +
20889 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
20890 browser.executeScript(scr);
20893 it('should initialize to model', function() {
20894 expect(value.getText()).toContain('2013-10-22');
20895 expect(valid.getText()).toContain('myForm.input.$valid = true');
20898 it('should be invalid if empty', function() {
20900 expect(value.getText()).toEqual('value =');
20901 expect(valid.getText()).toContain('myForm.input.$valid = false');
20904 it('should be invalid if over max', function() {
20905 setInput('2015-01-01');
20906 expect(value.getText()).toContain('');
20907 expect(valid.getText()).toContain('myForm.input.$valid = false');
20912 'date': createDateInputType('date', DATE_REGEXP,
20913 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']),
20918 * @name input[datetime-local]
20921 * Input with datetime validation and transformation. In browsers that do not yet support
20922 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
20923 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`.
20925 * The model must always be a Date object, otherwise Angular will throw an error.
20926 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
20928 * The timezone to be used to read/write the `Date` instance in the model can be defined using
20929 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
20931 * @param {string} ngModel Assignable angular expression to data-bind to.
20932 * @param {string=} name Property name of the form under which the control is published.
20933 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
20934 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
20935 * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
20936 * Note that `min` will also add native HTML5 constraint validation.
20937 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
20938 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
20939 * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
20940 * Note that `max` will also add native HTML5 constraint validation.
20941 * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string
20942 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
20943 * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string
20944 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
20945 * @param {string=} required Sets `required` validation error key if the value is not entered.
20946 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20947 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20948 * `required` when you want to data-bind to the `required` attribute.
20949 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20950 * interaction with the input element.
20953 <example name="datetimelocal-input-directive" module="dateExample">
20954 <file name="index.html">
20956 angular.module('dateExample', [])
20957 .controller('DateController', ['$scope', function($scope) {
20959 value: new Date(2010, 11, 28, 14, 57)
20963 <form name="myForm" ng-controller="DateController as dateCtrl">
20964 <label for="exampleInput">Pick a date between in 2013:</label>
20965 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value"
20966 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required />
20968 <span class="error" ng-show="myForm.input.$error.required">
20970 <span class="error" ng-show="myForm.input.$error.datetimelocal">
20971 Not a valid date!</span>
20973 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/>
20974 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20975 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20976 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20977 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20980 <file name="protractor.js" type="protractor">
20981 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"'));
20982 var valid = element(by.binding('myForm.input.$valid'));
20983 var input = element(by.model('example.value'));
20985 // currently protractor/webdriver does not support
20986 // sending keys to all known HTML5 input controls
20987 // for various browsers (https://github.com/angular/protractor/issues/562).
20988 function setInput(val) {
20989 // set the value of the element and force validation.
20990 var scr = "var ipt = document.getElementById('exampleInput'); " +
20991 "ipt.value = '" + val + "';" +
20992 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
20993 browser.executeScript(scr);
20996 it('should initialize to model', function() {
20997 expect(value.getText()).toContain('2010-12-28T14:57:00');
20998 expect(valid.getText()).toContain('myForm.input.$valid = true');
21001 it('should be invalid if empty', function() {
21003 expect(value.getText()).toEqual('value =');
21004 expect(valid.getText()).toContain('myForm.input.$valid = false');
21007 it('should be invalid if over max', function() {
21008 setInput('2015-01-01T23:59:00');
21009 expect(value.getText()).toContain('');
21010 expect(valid.getText()).toContain('myForm.input.$valid = false');
21015 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP,
21016 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']),
21017 'yyyy-MM-ddTHH:mm:ss.sss'),
21021 * @name input[time]
21024 * Input with time validation and transformation. In browsers that do not yet support
21025 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
21026 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a
21027 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`.
21029 * The model must always be a Date object, otherwise Angular will throw an error.
21030 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
21032 * The timezone to be used to read/write the `Date` instance in the model can be defined using
21033 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
21035 * @param {string} ngModel Assignable angular expression to data-bind to.
21036 * @param {string=} name Property name of the form under which the control is published.
21037 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21038 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
21039 * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add
21040 * native HTML5 constraint validation.
21041 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21042 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
21043 * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add
21044 * native HTML5 constraint validation.
21045 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the
21046 * `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
21047 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the
21048 * `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
21049 * @param {string=} required Sets `required` validation error key if the value is not entered.
21050 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21051 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21052 * `required` when you want to data-bind to the `required` attribute.
21053 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21054 * interaction with the input element.
21057 <example name="time-input-directive" module="timeExample">
21058 <file name="index.html">
21060 angular.module('timeExample', [])
21061 .controller('DateController', ['$scope', function($scope) {
21063 value: new Date(1970, 0, 1, 14, 57, 0)
21067 <form name="myForm" ng-controller="DateController as dateCtrl">
21068 <label for="exampleInput">Pick a between 8am and 5pm:</label>
21069 <input type="time" id="exampleInput" name="input" ng-model="example.value"
21070 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required />
21072 <span class="error" ng-show="myForm.input.$error.required">
21074 <span class="error" ng-show="myForm.input.$error.time">
21075 Not a valid date!</span>
21077 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/>
21078 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21079 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21080 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21081 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21084 <file name="protractor.js" type="protractor">
21085 var value = element(by.binding('example.value | date: "HH:mm:ss"'));
21086 var valid = element(by.binding('myForm.input.$valid'));
21087 var input = element(by.model('example.value'));
21089 // currently protractor/webdriver does not support
21090 // sending keys to all known HTML5 input controls
21091 // for various browsers (https://github.com/angular/protractor/issues/562).
21092 function setInput(val) {
21093 // set the value of the element and force validation.
21094 var scr = "var ipt = document.getElementById('exampleInput'); " +
21095 "ipt.value = '" + val + "';" +
21096 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21097 browser.executeScript(scr);
21100 it('should initialize to model', function() {
21101 expect(value.getText()).toContain('14:57:00');
21102 expect(valid.getText()).toContain('myForm.input.$valid = true');
21105 it('should be invalid if empty', function() {
21107 expect(value.getText()).toEqual('value =');
21108 expect(valid.getText()).toContain('myForm.input.$valid = false');
21111 it('should be invalid if over max', function() {
21112 setInput('23:59:00');
21113 expect(value.getText()).toContain('');
21114 expect(valid.getText()).toContain('myForm.input.$valid = false');
21119 'time': createDateInputType('time', TIME_REGEXP,
21120 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']),
21125 * @name input[week]
21128 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support
21129 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
21130 * week format (yyyy-W##), for example: `2013-W02`.
21132 * The model must always be a Date object, otherwise Angular will throw an error.
21133 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
21135 * The timezone to be used to read/write the `Date` instance in the model can be defined using
21136 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
21138 * @param {string} ngModel Assignable angular expression to data-bind to.
21139 * @param {string=} name Property name of the form under which the control is published.
21140 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21141 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
21142 * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add
21143 * native HTML5 constraint validation.
21144 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21145 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
21146 * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add
21147 * native HTML5 constraint validation.
21148 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
21149 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
21150 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
21151 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
21152 * @param {string=} required Sets `required` validation error key if the value is not entered.
21153 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21154 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21155 * `required` when you want to data-bind to the `required` attribute.
21156 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21157 * interaction with the input element.
21160 <example name="week-input-directive" module="weekExample">
21161 <file name="index.html">
21163 angular.module('weekExample', [])
21164 .controller('DateController', ['$scope', function($scope) {
21166 value: new Date(2013, 0, 3)
21170 <form name="myForm" ng-controller="DateController as dateCtrl">
21171 <label>Pick a date between in 2013:
21172 <input id="exampleInput" type="week" name="input" ng-model="example.value"
21173 placeholder="YYYY-W##" min="2012-W32"
21174 max="2013-W52" required />
21177 <span class="error" ng-show="myForm.input.$error.required">
21179 <span class="error" ng-show="myForm.input.$error.week">
21180 Not a valid date!</span>
21182 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/>
21183 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21184 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21185 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21186 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21189 <file name="protractor.js" type="protractor">
21190 var value = element(by.binding('example.value | date: "yyyy-Www"'));
21191 var valid = element(by.binding('myForm.input.$valid'));
21192 var input = element(by.model('example.value'));
21194 // currently protractor/webdriver does not support
21195 // sending keys to all known HTML5 input controls
21196 // for various browsers (https://github.com/angular/protractor/issues/562).
21197 function setInput(val) {
21198 // set the value of the element and force validation.
21199 var scr = "var ipt = document.getElementById('exampleInput'); " +
21200 "ipt.value = '" + val + "';" +
21201 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21202 browser.executeScript(scr);
21205 it('should initialize to model', function() {
21206 expect(value.getText()).toContain('2013-W01');
21207 expect(valid.getText()).toContain('myForm.input.$valid = true');
21210 it('should be invalid if empty', function() {
21212 expect(value.getText()).toEqual('value =');
21213 expect(valid.getText()).toContain('myForm.input.$valid = false');
21216 it('should be invalid if over max', function() {
21217 setInput('2015-W01');
21218 expect(value.getText()).toContain('');
21219 expect(valid.getText()).toContain('myForm.input.$valid = false');
21224 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'),
21228 * @name input[month]
21231 * Input with month validation and transformation. In browsers that do not yet support
21232 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
21233 * month format (yyyy-MM), for example: `2009-01`.
21235 * The model must always be a Date object, otherwise Angular will throw an error.
21236 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
21237 * If the model is not set to the first of the month, the next view to model update will set it
21238 * to the first of the month.
21240 * The timezone to be used to read/write the `Date` instance in the model can be defined using
21241 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
21243 * @param {string} ngModel Assignable angular expression to data-bind to.
21244 * @param {string=} name Property name of the form under which the control is published.
21245 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21246 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
21247 * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add
21248 * native HTML5 constraint validation.
21249 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21250 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
21251 * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add
21252 * native HTML5 constraint validation.
21253 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
21254 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
21255 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
21256 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
21258 * @param {string=} required Sets `required` validation error key if the value is not entered.
21259 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21260 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21261 * `required` when you want to data-bind to the `required` attribute.
21262 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21263 * interaction with the input element.
21266 <example name="month-input-directive" module="monthExample">
21267 <file name="index.html">
21269 angular.module('monthExample', [])
21270 .controller('DateController', ['$scope', function($scope) {
21272 value: new Date(2013, 9, 1)
21276 <form name="myForm" ng-controller="DateController as dateCtrl">
21277 <label for="exampleInput">Pick a month in 2013:</label>
21278 <input id="exampleInput" type="month" name="input" ng-model="example.value"
21279 placeholder="yyyy-MM" min="2013-01" max="2013-12" required />
21281 <span class="error" ng-show="myForm.input.$error.required">
21283 <span class="error" ng-show="myForm.input.$error.month">
21284 Not a valid month!</span>
21286 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/>
21287 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21288 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21289 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21290 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21293 <file name="protractor.js" type="protractor">
21294 var value = element(by.binding('example.value | date: "yyyy-MM"'));
21295 var valid = element(by.binding('myForm.input.$valid'));
21296 var input = element(by.model('example.value'));
21298 // currently protractor/webdriver does not support
21299 // sending keys to all known HTML5 input controls
21300 // for various browsers (https://github.com/angular/protractor/issues/562).
21301 function setInput(val) {
21302 // set the value of the element and force validation.
21303 var scr = "var ipt = document.getElementById('exampleInput'); " +
21304 "ipt.value = '" + val + "';" +
21305 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21306 browser.executeScript(scr);
21309 it('should initialize to model', function() {
21310 expect(value.getText()).toContain('2013-10');
21311 expect(valid.getText()).toContain('myForm.input.$valid = true');
21314 it('should be invalid if empty', function() {
21316 expect(value.getText()).toEqual('value =');
21317 expect(valid.getText()).toContain('myForm.input.$valid = false');
21320 it('should be invalid if over max', function() {
21321 setInput('2015-01');
21322 expect(value.getText()).toContain('');
21323 expect(valid.getText()).toContain('myForm.input.$valid = false');
21328 'month': createDateInputType('month', MONTH_REGEXP,
21329 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']),
21334 * @name input[number]
21337 * Text input with number validation and transformation. Sets the `number` validation
21338 * error if not a valid number.
21340 * <div class="alert alert-warning">
21341 * The model must always be of type `number` otherwise Angular will throw an error.
21342 * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt}
21343 * error docs for more information and an example of how to convert your model if necessary.
21346 * ## Issues with HTML5 constraint validation
21348 * In browsers that follow the
21349 * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29),
21350 * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}.
21351 * If a non-number is entered in the input, the browser will report the value as an empty string,
21352 * which means the view / model values in `ngModel` and subsequently the scope value
21353 * will also be an empty string.
21356 * @param {string} ngModel Assignable angular expression to data-bind to.
21357 * @param {string=} name Property name of the form under which the control is published.
21358 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21359 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21360 * @param {string=} required Sets `required` validation error key if the value is not entered.
21361 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21362 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21363 * `required` when you want to data-bind to the `required` attribute.
21364 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21366 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21367 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21369 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21370 * that contains the regular expression body that will be converted to a regular expression
21371 * as in the ngPattern directive.
21372 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21373 * a RegExp found by evaluating the Angular expression given in the attribute value.
21374 * If the expression evaluates to a RegExp object, then this is used directly.
21375 * If the expression evaluates to a string, then it will be converted to a RegExp
21376 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21377 * `new RegExp('^abc$')`.<br />
21378 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21379 * start at the index of the last search's match, thus not taking the whole input value into
21381 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21382 * interaction with the input element.
21385 <example name="number-input-directive" module="numberExample">
21386 <file name="index.html">
21388 angular.module('numberExample', [])
21389 .controller('ExampleController', ['$scope', function($scope) {
21395 <form name="myForm" ng-controller="ExampleController">
21397 <input type="number" name="input" ng-model="example.value"
21398 min="0" max="99" required>
21401 <span class="error" ng-show="myForm.input.$error.required">
21403 <span class="error" ng-show="myForm.input.$error.number">
21404 Not valid number!</span>
21406 <tt>value = {{example.value}}</tt><br/>
21407 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21408 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21409 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21410 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21413 <file name="protractor.js" type="protractor">
21414 var value = element(by.binding('example.value'));
21415 var valid = element(by.binding('myForm.input.$valid'));
21416 var input = element(by.model('example.value'));
21418 it('should initialize to model', function() {
21419 expect(value.getText()).toContain('12');
21420 expect(valid.getText()).toContain('true');
21423 it('should be invalid if empty', function() {
21425 input.sendKeys('');
21426 expect(value.getText()).toEqual('value =');
21427 expect(valid.getText()).toContain('false');
21430 it('should be invalid if over max', function() {
21432 input.sendKeys('123');
21433 expect(value.getText()).toEqual('value =');
21434 expect(valid.getText()).toContain('false');
21439 'number': numberInputType,
21447 * Text input with URL validation. Sets the `url` validation error key if the content is not a
21450 * <div class="alert alert-warning">
21451 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex
21452 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify
21453 * the built-in validators (see the {@link guide/forms Forms guide})
21456 * @param {string} ngModel Assignable angular expression to data-bind to.
21457 * @param {string=} name Property name of the form under which the control is published.
21458 * @param {string=} required Sets `required` validation error key if the value is not entered.
21459 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21460 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21461 * `required` when you want to data-bind to the `required` attribute.
21462 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21464 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21465 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21467 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21468 * that contains the regular expression body that will be converted to a regular expression
21469 * as in the ngPattern directive.
21470 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21471 * a RegExp found by evaluating the Angular expression given in the attribute value.
21472 * If the expression evaluates to a RegExp object, then this is used directly.
21473 * If the expression evaluates to a string, then it will be converted to a RegExp
21474 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21475 * `new RegExp('^abc$')`.<br />
21476 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21477 * start at the index of the last search's match, thus not taking the whole input value into
21479 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21480 * interaction with the input element.
21483 <example name="url-input-directive" module="urlExample">
21484 <file name="index.html">
21486 angular.module('urlExample', [])
21487 .controller('ExampleController', ['$scope', function($scope) {
21489 text: 'http://google.com'
21493 <form name="myForm" ng-controller="ExampleController">
21495 <input type="url" name="input" ng-model="url.text" required>
21498 <span class="error" ng-show="myForm.input.$error.required">
21500 <span class="error" ng-show="myForm.input.$error.url">
21501 Not valid url!</span>
21503 <tt>text = {{url.text}}</tt><br/>
21504 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21505 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21506 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21507 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21508 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/>
21511 <file name="protractor.js" type="protractor">
21512 var text = element(by.binding('url.text'));
21513 var valid = element(by.binding('myForm.input.$valid'));
21514 var input = element(by.model('url.text'));
21516 it('should initialize to model', function() {
21517 expect(text.getText()).toContain('http://google.com');
21518 expect(valid.getText()).toContain('true');
21521 it('should be invalid if empty', function() {
21523 input.sendKeys('');
21525 expect(text.getText()).toEqual('text =');
21526 expect(valid.getText()).toContain('false');
21529 it('should be invalid if not url', function() {
21531 input.sendKeys('box');
21533 expect(valid.getText()).toContain('false');
21538 'url': urlInputType,
21543 * @name input[email]
21546 * Text input with email validation. Sets the `email` validation error key if not a valid email
21549 * <div class="alert alert-warning">
21550 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex
21551 * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can
21552 * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide})
21555 * @param {string} ngModel Assignable angular expression to data-bind to.
21556 * @param {string=} name Property name of the form under which the control is published.
21557 * @param {string=} required Sets `required` validation error key if the value is not entered.
21558 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21559 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21560 * `required` when you want to data-bind to the `required` attribute.
21561 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21563 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21564 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21566 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21567 * that contains the regular expression body that will be converted to a regular expression
21568 * as in the ngPattern directive.
21569 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21570 * a RegExp found by evaluating the Angular expression given in the attribute value.
21571 * If the expression evaluates to a RegExp object, then this is used directly.
21572 * If the expression evaluates to a string, then it will be converted to a RegExp
21573 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21574 * `new RegExp('^abc$')`.<br />
21575 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21576 * start at the index of the last search's match, thus not taking the whole input value into
21578 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21579 * interaction with the input element.
21582 <example name="email-input-directive" module="emailExample">
21583 <file name="index.html">
21585 angular.module('emailExample', [])
21586 .controller('ExampleController', ['$scope', function($scope) {
21588 text: 'me@example.com'
21592 <form name="myForm" ng-controller="ExampleController">
21594 <input type="email" name="input" ng-model="email.text" required>
21597 <span class="error" ng-show="myForm.input.$error.required">
21599 <span class="error" ng-show="myForm.input.$error.email">
21600 Not valid email!</span>
21602 <tt>text = {{email.text}}</tt><br/>
21603 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21604 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21605 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21606 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21607 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/>
21610 <file name="protractor.js" type="protractor">
21611 var text = element(by.binding('email.text'));
21612 var valid = element(by.binding('myForm.input.$valid'));
21613 var input = element(by.model('email.text'));
21615 it('should initialize to model', function() {
21616 expect(text.getText()).toContain('me@example.com');
21617 expect(valid.getText()).toContain('true');
21620 it('should be invalid if empty', function() {
21622 input.sendKeys('');
21623 expect(text.getText()).toEqual('text =');
21624 expect(valid.getText()).toContain('false');
21627 it('should be invalid if not email', function() {
21629 input.sendKeys('xxx');
21631 expect(valid.getText()).toContain('false');
21636 'email': emailInputType,
21641 * @name input[radio]
21644 * HTML radio button.
21646 * @param {string} ngModel Assignable angular expression to data-bind to.
21647 * @param {string} value The value to which the `ngModel` expression should be set when selected.
21648 * Note that `value` only supports `string` values, i.e. the scope model needs to be a string,
21649 * too. Use `ngValue` if you need complex models (`number`, `object`, ...).
21650 * @param {string=} name Property name of the form under which the control is published.
21651 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21652 * interaction with the input element.
21653 * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio
21654 * is selected. Should be used instead of the `value` attribute if you need
21655 * a non-string `ngModel` (`boolean`, `array`, ...).
21658 <example name="radio-input-directive" module="radioExample">
21659 <file name="index.html">
21661 angular.module('radioExample', [])
21662 .controller('ExampleController', ['$scope', function($scope) {
21666 $scope.specialValue = {
21672 <form name="myForm" ng-controller="ExampleController">
21674 <input type="radio" ng-model="color.name" value="red">
21678 <input type="radio" ng-model="color.name" ng-value="specialValue">
21682 <input type="radio" ng-model="color.name" value="blue">
21685 <tt>color = {{color.name | json}}</tt><br/>
21687 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
21689 <file name="protractor.js" type="protractor">
21690 it('should change state', function() {
21691 var color = element(by.binding('color.name'));
21693 expect(color.getText()).toContain('blue');
21695 element.all(by.model('color.name')).get(0).click();
21697 expect(color.getText()).toContain('red');
21702 'radio': radioInputType,
21707 * @name input[checkbox]
21712 * @param {string} ngModel Assignable angular expression to data-bind to.
21713 * @param {string=} name Property name of the form under which the control is published.
21714 * @param {expression=} ngTrueValue The value to which the expression should be set when selected.
21715 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected.
21716 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21717 * interaction with the input element.
21720 <example name="checkbox-input-directive" module="checkboxExample">
21721 <file name="index.html">
21723 angular.module('checkboxExample', [])
21724 .controller('ExampleController', ['$scope', function($scope) {
21725 $scope.checkboxModel = {
21731 <form name="myForm" ng-controller="ExampleController">
21733 <input type="checkbox" ng-model="checkboxModel.value1">
21736 <input type="checkbox" ng-model="checkboxModel.value2"
21737 ng-true-value="'YES'" ng-false-value="'NO'">
21739 <tt>value1 = {{checkboxModel.value1}}</tt><br/>
21740 <tt>value2 = {{checkboxModel.value2}}</tt><br/>
21743 <file name="protractor.js" type="protractor">
21744 it('should change state', function() {
21745 var value1 = element(by.binding('checkboxModel.value1'));
21746 var value2 = element(by.binding('checkboxModel.value2'));
21748 expect(value1.getText()).toContain('true');
21749 expect(value2.getText()).toContain('YES');
21751 element(by.model('checkboxModel.value1')).click();
21752 element(by.model('checkboxModel.value2')).click();
21754 expect(value1.getText()).toContain('false');
21755 expect(value2.getText()).toContain('NO');
21760 'checkbox': checkboxInputType,
21769 function stringBasedInputType(ctrl) {
21770 ctrl.$formatters.push(function(value) {
21771 return ctrl.$isEmpty(value) ? value : value.toString();
21775 function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
21776 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
21777 stringBasedInputType(ctrl);
21780 function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
21781 var type = lowercase(element[0].type);
21783 // In composition mode, users are still inputing intermediate text buffer,
21784 // hold the listener until composition is done.
21785 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
21786 if (!$sniffer.android) {
21787 var composing = false;
21789 element.on('compositionstart', function(data) {
21793 element.on('compositionend', function() {
21799 var listener = function(ev) {
21801 $browser.defer.cancel(timeout);
21804 if (composing) return;
21805 var value = element.val(),
21806 event = ev && ev.type;
21808 // By default we will trim the value
21809 // If the attribute ng-trim exists we will avoid trimming
21810 // If input type is 'password', the value is never trimmed
21811 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) {
21812 value = trim(value);
21815 // If a control is suffering from bad input (due to native validators), browsers discard its
21816 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the
21817 // control's value is the same empty value twice in a row.
21818 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) {
21819 ctrl.$setViewValue(value, event);
21823 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the
21824 // input event on backspace, delete or cut
21825 if ($sniffer.hasEvent('input')) {
21826 element.on('input', listener);
21830 var deferListener = function(ev, input, origValue) {
21832 timeout = $browser.defer(function() {
21834 if (!input || input.value !== origValue) {
21841 element.on('keydown', function(event) {
21842 var key = event.keyCode;
21845 // command modifiers arrows
21846 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
21848 deferListener(event, this, this.value);
21851 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
21852 if ($sniffer.hasEvent('paste')) {
21853 element.on('paste cut', deferListener);
21857 // if user paste into input using mouse on older browser
21858 // or form autocomplete on newer browser, we need "change" event to catch it
21859 element.on('change', listener);
21861 ctrl.$render = function() {
21862 // Workaround for Firefox validation #12102.
21863 var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue;
21864 if (element.val() !== value) {
21865 element.val(value);
21870 function weekParser(isoWeek, existingDate) {
21871 if (isDate(isoWeek)) {
21875 if (isString(isoWeek)) {
21876 WEEK_REGEXP.lastIndex = 0;
21877 var parts = WEEK_REGEXP.exec(isoWeek);
21879 var year = +parts[1],
21885 firstThurs = getFirstThursdayOfYear(year),
21886 addDays = (week - 1) * 7;
21888 if (existingDate) {
21889 hours = existingDate.getHours();
21890 minutes = existingDate.getMinutes();
21891 seconds = existingDate.getSeconds();
21892 milliseconds = existingDate.getMilliseconds();
21895 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds);
21902 function createDateParser(regexp, mapping) {
21903 return function(iso, date) {
21910 if (isString(iso)) {
21911 // When a date is JSON'ified to wraps itself inside of an extra
21912 // set of double quotes. This makes the date parsing code unable
21913 // to match the date string and parse it as a date.
21914 if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') {
21915 iso = iso.substring(1, iso.length - 1);
21917 if (ISO_DATE_REGEXP.test(iso)) {
21918 return new Date(iso);
21920 regexp.lastIndex = 0;
21921 parts = regexp.exec(iso);
21927 yyyy: date.getFullYear(),
21928 MM: date.getMonth() + 1,
21929 dd: date.getDate(),
21930 HH: date.getHours(),
21931 mm: date.getMinutes(),
21932 ss: date.getSeconds(),
21933 sss: date.getMilliseconds() / 1000
21936 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 };
21939 forEach(parts, function(part, index) {
21940 if (index < mapping.length) {
21941 map[mapping[index]] = +part;
21944 return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0);
21952 function createDateInputType(type, regexp, parseDate, format) {
21953 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
21954 badInputChecker(scope, element, attr, ctrl);
21955 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
21956 var timezone = ctrl && ctrl.$options && ctrl.$options.timezone;
21959 ctrl.$$parserName = type;
21960 ctrl.$parsers.push(function(value) {
21961 if (ctrl.$isEmpty(value)) return null;
21962 if (regexp.test(value)) {
21963 // Note: We cannot read ctrl.$modelValue, as there might be a different
21964 // parser/formatter in the processing chain so that the model
21965 // contains some different data format!
21966 var parsedDate = parseDate(value, previousDate);
21968 parsedDate = convertTimezoneToLocal(parsedDate, timezone);
21975 ctrl.$formatters.push(function(value) {
21976 if (value && !isDate(value)) {
21977 throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
21979 if (isValidDate(value)) {
21980 previousDate = value;
21981 if (previousDate && timezone) {
21982 previousDate = convertTimezoneToLocal(previousDate, timezone, true);
21984 return $filter('date')(value, format, timezone);
21986 previousDate = null;
21991 if (isDefined(attr.min) || attr.ngMin) {
21993 ctrl.$validators.min = function(value) {
21994 return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal;
21996 attr.$observe('min', function(val) {
21997 minVal = parseObservedDateValue(val);
22002 if (isDefined(attr.max) || attr.ngMax) {
22004 ctrl.$validators.max = function(value) {
22005 return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal;
22007 attr.$observe('max', function(val) {
22008 maxVal = parseObservedDateValue(val);
22013 function isValidDate(value) {
22014 // Invalid Date: getTime() returns NaN
22015 return value && !(value.getTime && value.getTime() !== value.getTime());
22018 function parseObservedDateValue(val) {
22019 return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val;
22024 function badInputChecker(scope, element, attr, ctrl) {
22025 var node = element[0];
22026 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity);
22027 if (nativeValidation) {
22028 ctrl.$parsers.push(function(value) {
22029 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {};
22030 // Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430):
22031 // - also sets validity.badInput (should only be validity.typeMismatch).
22032 // - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email)
22033 // - can ignore this case as we can still read out the erroneous email...
22034 return validity.badInput && !validity.typeMismatch ? undefined : value;
22039 function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
22040 badInputChecker(scope, element, attr, ctrl);
22041 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
22043 ctrl.$$parserName = 'number';
22044 ctrl.$parsers.push(function(value) {
22045 if (ctrl.$isEmpty(value)) return null;
22046 if (NUMBER_REGEXP.test(value)) return parseFloat(value);
22050 ctrl.$formatters.push(function(value) {
22051 if (!ctrl.$isEmpty(value)) {
22052 if (!isNumber(value)) {
22053 throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value);
22055 value = value.toString();
22060 if (isDefined(attr.min) || attr.ngMin) {
22062 ctrl.$validators.min = function(value) {
22063 return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal;
22066 attr.$observe('min', function(val) {
22067 if (isDefined(val) && !isNumber(val)) {
22068 val = parseFloat(val, 10);
22070 minVal = isNumber(val) && !isNaN(val) ? val : undefined;
22071 // TODO(matsko): implement validateLater to reduce number of validations
22076 if (isDefined(attr.max) || attr.ngMax) {
22078 ctrl.$validators.max = function(value) {
22079 return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal;
22082 attr.$observe('max', function(val) {
22083 if (isDefined(val) && !isNumber(val)) {
22084 val = parseFloat(val, 10);
22086 maxVal = isNumber(val) && !isNaN(val) ? val : undefined;
22087 // TODO(matsko): implement validateLater to reduce number of validations
22093 function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
22094 // Note: no badInputChecker here by purpose as `url` is only a validation
22095 // in browsers, i.e. we can always read out input.value even if it is not valid!
22096 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
22097 stringBasedInputType(ctrl);
22099 ctrl.$$parserName = 'url';
22100 ctrl.$validators.url = function(modelValue, viewValue) {
22101 var value = modelValue || viewValue;
22102 return ctrl.$isEmpty(value) || URL_REGEXP.test(value);
22106 function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
22107 // Note: no badInputChecker here by purpose as `url` is only a validation
22108 // in browsers, i.e. we can always read out input.value even if it is not valid!
22109 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
22110 stringBasedInputType(ctrl);
22112 ctrl.$$parserName = 'email';
22113 ctrl.$validators.email = function(modelValue, viewValue) {
22114 var value = modelValue || viewValue;
22115 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value);
22119 function radioInputType(scope, element, attr, ctrl) {
22120 // make the name unique, if not defined
22121 if (isUndefined(attr.name)) {
22122 element.attr('name', nextUid());
22125 var listener = function(ev) {
22126 if (element[0].checked) {
22127 ctrl.$setViewValue(attr.value, ev && ev.type);
22131 element.on('click', listener);
22133 ctrl.$render = function() {
22134 var value = attr.value;
22135 element[0].checked = (value == ctrl.$viewValue);
22138 attr.$observe('value', ctrl.$render);
22141 function parseConstantExpr($parse, context, name, expression, fallback) {
22143 if (isDefined(expression)) {
22144 parseFn = $parse(expression);
22145 if (!parseFn.constant) {
22146 throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' +
22147 '`{1}`.', name, expression);
22149 return parseFn(context);
22154 function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
22155 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true);
22156 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false);
22158 var listener = function(ev) {
22159 ctrl.$setViewValue(element[0].checked, ev && ev.type);
22162 element.on('click', listener);
22164 ctrl.$render = function() {
22165 element[0].checked = ctrl.$viewValue;
22168 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false`
22169 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert
22170 // it to a boolean.
22171 ctrl.$isEmpty = function(value) {
22172 return value === false;
22175 ctrl.$formatters.push(function(value) {
22176 return equals(value, trueValue);
22179 ctrl.$parsers.push(function(value) {
22180 return value ? trueValue : falseValue;
22191 * HTML textarea element control with angular data-binding. The data-binding and validation
22192 * properties of this element are exactly the same as those of the
22193 * {@link ng.directive:input input element}.
22195 * @param {string} ngModel Assignable angular expression to data-bind to.
22196 * @param {string=} name Property name of the form under which the control is published.
22197 * @param {string=} required Sets `required` validation error key if the value is not entered.
22198 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
22199 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
22200 * `required` when you want to data-bind to the `required` attribute.
22201 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
22203 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
22204 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
22206 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
22207 * a RegExp found by evaluating the Angular expression given in the attribute value.
22208 * If the expression evaluates to a RegExp object, then this is used directly.
22209 * If the expression evaluates to a string, then it will be converted to a RegExp
22210 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
22211 * `new RegExp('^abc$')`.<br />
22212 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
22213 * start at the index of the last search's match, thus not taking the whole input value into
22215 * @param {string=} ngChange Angular expression to be executed when input changes due to user
22216 * interaction with the input element.
22217 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
22227 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding,
22228 * input state control, and validation.
22229 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers.
22231 * <div class="alert alert-warning">
22232 * **Note:** Not every feature offered is available for all input types.
22233 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`.
22236 * @param {string} ngModel Assignable angular expression to data-bind to.
22237 * @param {string=} name Property name of the form under which the control is published.
22238 * @param {string=} required Sets `required` validation error key if the value is not entered.
22239 * @param {boolean=} ngRequired Sets `required` attribute if set to true
22240 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
22242 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
22243 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
22245 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
22246 * a RegExp found by evaluating the Angular expression given in the attribute value.
22247 * If the expression evaluates to a RegExp object, then this is used directly.
22248 * If the expression evaluates to a string, then it will be converted to a RegExp
22249 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
22250 * `new RegExp('^abc$')`.<br />
22251 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
22252 * start at the index of the last search's match, thus not taking the whole input value into
22254 * @param {string=} ngChange Angular expression to be executed when input changes due to user
22255 * interaction with the input element.
22256 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
22257 * This parameter is ignored for input[type=password] controls, which will never trim the
22261 <example name="input-directive" module="inputExample">
22262 <file name="index.html">
22264 angular.module('inputExample', [])
22265 .controller('ExampleController', ['$scope', function($scope) {
22266 $scope.user = {name: 'guest', last: 'visitor'};
22269 <div ng-controller="ExampleController">
22270 <form name="myForm">
22273 <input type="text" name="userName" ng-model="user.name" required>
22276 <span class="error" ng-show="myForm.userName.$error.required">
22281 <input type="text" name="lastName" ng-model="user.last"
22282 ng-minlength="3" ng-maxlength="10">
22285 <span class="error" ng-show="myForm.lastName.$error.minlength">
22287 <span class="error" ng-show="myForm.lastName.$error.maxlength">
22292 <tt>user = {{user}}</tt><br/>
22293 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/>
22294 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/>
22295 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/>
22296 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/>
22297 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
22298 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
22299 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/>
22300 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/>
22303 <file name="protractor.js" type="protractor">
22304 var user = element(by.exactBinding('user'));
22305 var userNameValid = element(by.binding('myForm.userName.$valid'));
22306 var lastNameValid = element(by.binding('myForm.lastName.$valid'));
22307 var lastNameError = element(by.binding('myForm.lastName.$error'));
22308 var formValid = element(by.binding('myForm.$valid'));
22309 var userNameInput = element(by.model('user.name'));
22310 var userLastInput = element(by.model('user.last'));
22312 it('should initialize to model', function() {
22313 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}');
22314 expect(userNameValid.getText()).toContain('true');
22315 expect(formValid.getText()).toContain('true');
22318 it('should be invalid if empty when required', function() {
22319 userNameInput.clear();
22320 userNameInput.sendKeys('');
22322 expect(user.getText()).toContain('{"last":"visitor"}');
22323 expect(userNameValid.getText()).toContain('false');
22324 expect(formValid.getText()).toContain('false');
22327 it('should be valid if empty when min length is set', function() {
22328 userLastInput.clear();
22329 userLastInput.sendKeys('');
22331 expect(user.getText()).toContain('{"name":"guest","last":""}');
22332 expect(lastNameValid.getText()).toContain('true');
22333 expect(formValid.getText()).toContain('true');
22336 it('should be invalid if less than required min length', function() {
22337 userLastInput.clear();
22338 userLastInput.sendKeys('xx');
22340 expect(user.getText()).toContain('{"name":"guest"}');
22341 expect(lastNameValid.getText()).toContain('false');
22342 expect(lastNameError.getText()).toContain('minlength');
22343 expect(formValid.getText()).toContain('false');
22346 it('should be invalid if longer than max length', function() {
22347 userLastInput.clear();
22348 userLastInput.sendKeys('some ridiculously long name');
22350 expect(user.getText()).toContain('{"name":"guest"}');
22351 expect(lastNameValid.getText()).toContain('false');
22352 expect(lastNameError.getText()).toContain('maxlength');
22353 expect(formValid.getText()).toContain('false');
22358 var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
22359 function($browser, $sniffer, $filter, $parse) {
22362 require: ['?ngModel'],
22364 pre: function(scope, element, attr, ctrls) {
22366 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
22367 $browser, $filter, $parse);
22376 var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
22382 * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`},
22383 * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to
22386 * `ngValue` is useful when dynamically generating lists of radio buttons using
22387 * {@link ngRepeat `ngRepeat`}, as shown below.
22389 * Likewise, `ngValue` can be used to generate `<option>` elements for
22390 * the {@link select `select`} element. In that case however, only strings are supported
22391 * for the `value `attribute, so the resulting `ngModel` will always be a string.
22392 * Support for `select` models with non-string values is available via `ngOptions`.
22395 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute
22396 * of the `input` element
22399 <example name="ngValue-directive" module="valueExample">
22400 <file name="index.html">
22402 angular.module('valueExample', [])
22403 .controller('ExampleController', ['$scope', function($scope) {
22404 $scope.names = ['pizza', 'unicorns', 'robots'];
22405 $scope.my = { favorite: 'unicorns' };
22408 <form ng-controller="ExampleController">
22409 <h2>Which is your favorite?</h2>
22410 <label ng-repeat="name in names" for="{{name}}">
22412 <input type="radio"
22413 ng-model="my.favorite"
22418 <div>You chose {{my.favorite}}</div>
22421 <file name="protractor.js" type="protractor">
22422 var favorite = element(by.binding('my.favorite'));
22424 it('should initialize to model', function() {
22425 expect(favorite.getText()).toContain('unicorns');
22427 it('should bind the values to the inputs', function() {
22428 element.all(by.model('my.favorite')).get(0).click();
22429 expect(favorite.getText()).toContain('pizza');
22434 var ngValueDirective = function() {
22438 compile: function(tpl, tplAttr) {
22439 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) {
22440 return function ngValueConstantLink(scope, elm, attr) {
22441 attr.$set('value', scope.$eval(attr.ngValue));
22444 return function ngValueLink(scope, elm, attr) {
22445 scope.$watch(attr.ngValue, function valueWatchAction(value) {
22446 attr.$set('value', value);
22460 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element
22461 * with the value of a given expression, and to update the text content when the value of that
22462 * expression changes.
22464 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like
22465 * `{{ expression }}` which is similar but less verbose.
22467 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily
22468 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an
22469 * element attribute, it makes the bindings invisible to the user while the page is loading.
22471 * An alternative solution to this problem would be using the
22472 * {@link ng.directive:ngCloak ngCloak} directive.
22476 * @param {expression} ngBind {@link guide/expression Expression} to evaluate.
22479 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly.
22480 <example module="bindExample">
22481 <file name="index.html">
22483 angular.module('bindExample', [])
22484 .controller('ExampleController', ['$scope', function($scope) {
22485 $scope.name = 'Whirled';
22488 <div ng-controller="ExampleController">
22489 <label>Enter name: <input type="text" ng-model="name"></label><br>
22490 Hello <span ng-bind="name"></span>!
22493 <file name="protractor.js" type="protractor">
22494 it('should check ng-bind', function() {
22495 var nameInput = element(by.model('name'));
22497 expect(element(by.binding('name')).getText()).toBe('Whirled');
22499 nameInput.sendKeys('world');
22500 expect(element(by.binding('name')).getText()).toBe('world');
22505 var ngBindDirective = ['$compile', function($compile) {
22508 compile: function ngBindCompile(templateElement) {
22509 $compile.$$addBindingClass(templateElement);
22510 return function ngBindLink(scope, element, attr) {
22511 $compile.$$addBindingInfo(element, attr.ngBind);
22512 element = element[0];
22513 scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
22514 element.textContent = isUndefined(value) ? '' : value;
22524 * @name ngBindTemplate
22527 * The `ngBindTemplate` directive specifies that the element
22528 * text content should be replaced with the interpolation of the template
22529 * in the `ngBindTemplate` attribute.
22530 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}`
22531 * expressions. This directive is needed since some HTML elements
22532 * (such as TITLE and OPTION) cannot contain SPAN elements.
22535 * @param {string} ngBindTemplate template of form
22536 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval.
22539 * Try it here: enter text in text box and watch the greeting change.
22540 <example module="bindExample">
22541 <file name="index.html">
22543 angular.module('bindExample', [])
22544 .controller('ExampleController', ['$scope', function($scope) {
22545 $scope.salutation = 'Hello';
22546 $scope.name = 'World';
22549 <div ng-controller="ExampleController">
22550 <label>Salutation: <input type="text" ng-model="salutation"></label><br>
22551 <label>Name: <input type="text" ng-model="name"></label><br>
22552 <pre ng-bind-template="{{salutation}} {{name}}!"></pre>
22555 <file name="protractor.js" type="protractor">
22556 it('should check ng-bind', function() {
22557 var salutationElem = element(by.binding('salutation'));
22558 var salutationInput = element(by.model('salutation'));
22559 var nameInput = element(by.model('name'));
22561 expect(salutationElem.getText()).toBe('Hello World!');
22563 salutationInput.clear();
22564 salutationInput.sendKeys('Greetings');
22566 nameInput.sendKeys('user');
22568 expect(salutationElem.getText()).toBe('Greetings user!');
22573 var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) {
22575 compile: function ngBindTemplateCompile(templateElement) {
22576 $compile.$$addBindingClass(templateElement);
22577 return function ngBindTemplateLink(scope, element, attr) {
22578 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
22579 $compile.$$addBindingInfo(element, interpolateFn.expressions);
22580 element = element[0];
22581 attr.$observe('ngBindTemplate', function(value) {
22582 element.textContent = isUndefined(value) ? '' : value;
22595 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default,
22596 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service.
22597 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link
22598 * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize}
22599 * in your module's dependencies, you need to include "angular-sanitize.js" in your application.
22601 * You may also bypass sanitization for values you know are safe. To do so, bind to
22602 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
22603 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}.
22605 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you
22606 * will have an exception (instead of an exploit.)
22609 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
22613 <example module="bindHtmlExample" deps="angular-sanitize.js">
22614 <file name="index.html">
22615 <div ng-controller="ExampleController">
22616 <p ng-bind-html="myHTML"></p>
22620 <file name="script.js">
22621 angular.module('bindHtmlExample', ['ngSanitize'])
22622 .controller('ExampleController', ['$scope', function($scope) {
22624 'I am an <code>HTML</code>string with ' +
22625 '<a href="#">links!</a> and other <em>stuff</em>';
22629 <file name="protractor.js" type="protractor">
22630 it('should check ng-bind-html', function() {
22631 expect(element(by.binding('myHTML')).getText()).toBe(
22632 'I am an HTMLstring with links! and other stuff');
22637 var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) {
22640 compile: function ngBindHtmlCompile(tElement, tAttrs) {
22641 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml);
22642 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) {
22643 return (value || '').toString();
22645 $compile.$$addBindingClass(tElement);
22647 return function ngBindHtmlLink(scope, element, attr) {
22648 $compile.$$addBindingInfo(element, attr.ngBindHtml);
22650 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() {
22651 // we re-evaluate the expr because we want a TrustedValueHolderType
22652 // for $sce, not a string
22653 element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || '');
22665 * Evaluate the given expression when the user changes the input.
22666 * The expression is evaluated immediately, unlike the JavaScript onchange event
22667 * which only triggers at the end of a change (usually, when the user leaves the
22668 * form element or presses the return key).
22670 * The `ngChange` expression is only evaluated when a change in the input value causes
22671 * a new value to be committed to the model.
22673 * It will not be evaluated:
22674 * * if the value returned from the `$parsers` transformation pipeline has not changed
22675 * * if the input has continued to be invalid since the model will stay `null`
22676 * * if the model is changed programmatically and not by a change to the input value
22679 * Note, this directive requires `ngModel` to be present.
22682 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change
22686 * <example name="ngChange-directive" module="changeExample">
22687 * <file name="index.html">
22689 * angular.module('changeExample', [])
22690 * .controller('ExampleController', ['$scope', function($scope) {
22691 * $scope.counter = 0;
22692 * $scope.change = function() {
22693 * $scope.counter++;
22697 * <div ng-controller="ExampleController">
22698 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
22699 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
22700 * <label for="ng-change-example2">Confirmed</label><br />
22701 * <tt>debug = {{confirmed}}</tt><br/>
22702 * <tt>counter = {{counter}}</tt><br/>
22705 * <file name="protractor.js" type="protractor">
22706 * var counter = element(by.binding('counter'));
22707 * var debug = element(by.binding('confirmed'));
22709 * it('should evaluate the expression if changing from view', function() {
22710 * expect(counter.getText()).toContain('0');
22712 * element(by.id('ng-change-example1')).click();
22714 * expect(counter.getText()).toContain('1');
22715 * expect(debug.getText()).toContain('true');
22718 * it('should not evaluate the expression if changing from model', function() {
22719 * element(by.id('ng-change-example2')).click();
22721 * expect(counter.getText()).toContain('0');
22722 * expect(debug.getText()).toContain('true');
22727 var ngChangeDirective = valueFn({
22729 require: 'ngModel',
22730 link: function(scope, element, attr, ctrl) {
22731 ctrl.$viewChangeListeners.push(function() {
22732 scope.$eval(attr.ngChange);
22737 function classDirective(name, selector) {
22738 name = 'ngClass' + name;
22739 return ['$animate', function($animate) {
22742 link: function(scope, element, attr) {
22745 scope.$watch(attr[name], ngClassWatchAction, true);
22747 attr.$observe('class', function(value) {
22748 ngClassWatchAction(scope.$eval(attr[name]));
22752 if (name !== 'ngClass') {
22753 scope.$watch('$index', function($index, old$index) {
22754 // jshint bitwise: false
22755 var mod = $index & 1;
22756 if (mod !== (old$index & 1)) {
22757 var classes = arrayClasses(scope.$eval(attr[name]));
22759 addClasses(classes) :
22760 removeClasses(classes);
22765 function addClasses(classes) {
22766 var newClasses = digestClassCounts(classes, 1);
22767 attr.$addClass(newClasses);
22770 function removeClasses(classes) {
22771 var newClasses = digestClassCounts(classes, -1);
22772 attr.$removeClass(newClasses);
22775 function digestClassCounts(classes, count) {
22776 // Use createMap() to prevent class assumptions involving property
22777 // names in Object.prototype
22778 var classCounts = element.data('$classCounts') || createMap();
22779 var classesToUpdate = [];
22780 forEach(classes, function(className) {
22781 if (count > 0 || classCounts[className]) {
22782 classCounts[className] = (classCounts[className] || 0) + count;
22783 if (classCounts[className] === +(count > 0)) {
22784 classesToUpdate.push(className);
22788 element.data('$classCounts', classCounts);
22789 return classesToUpdate.join(' ');
22792 function updateClasses(oldClasses, newClasses) {
22793 var toAdd = arrayDifference(newClasses, oldClasses);
22794 var toRemove = arrayDifference(oldClasses, newClasses);
22795 toAdd = digestClassCounts(toAdd, 1);
22796 toRemove = digestClassCounts(toRemove, -1);
22797 if (toAdd && toAdd.length) {
22798 $animate.addClass(element, toAdd);
22800 if (toRemove && toRemove.length) {
22801 $animate.removeClass(element, toRemove);
22805 function ngClassWatchAction(newVal) {
22806 if (selector === true || scope.$index % 2 === selector) {
22807 var newClasses = arrayClasses(newVal || []);
22809 addClasses(newClasses);
22810 } else if (!equals(newVal,oldVal)) {
22811 var oldClasses = arrayClasses(oldVal);
22812 updateClasses(oldClasses, newClasses);
22815 oldVal = shallowCopy(newVal);
22820 function arrayDifference(tokens1, tokens2) {
22824 for (var i = 0; i < tokens1.length; i++) {
22825 var token = tokens1[i];
22826 for (var j = 0; j < tokens2.length; j++) {
22827 if (token == tokens2[j]) continue outer;
22829 values.push(token);
22834 function arrayClasses(classVal) {
22836 if (isArray(classVal)) {
22837 forEach(classVal, function(v) {
22838 classes = classes.concat(arrayClasses(v));
22841 } else if (isString(classVal)) {
22842 return classVal.split(' ');
22843 } else if (isObject(classVal)) {
22844 forEach(classVal, function(v, k) {
22846 classes = classes.concat(k.split(' '));
22862 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding
22863 * an expression that represents all classes to be added.
22865 * The directive operates in three different ways, depending on which of three types the expression
22868 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class
22871 * 2. If the expression evaluates to an object, then for each key-value pair of the
22872 * object with a truthy value the corresponding key is used as a class name.
22874 * 3. If the expression evaluates to an array, each element of the array should either be a string as in
22875 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array
22876 * to give you more control over what CSS classes appear. See the code below for an example of this.
22879 * The directive won't add duplicate classes if a particular class was already set.
22881 * When the expression changes, the previously added classes are removed and only then are the
22882 * new classes added.
22885 * **add** - happens just before the class is applied to the elements
22887 * **remove** - happens just before the class is removed from the element
22890 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result
22891 * of the evaluation can be a string representing space delimited class
22892 * names, an array, or a map of class names to boolean values. In the case of a map, the
22893 * names of the properties whose values are truthy will be added as css classes to the
22896 * @example Example that demonstrates basic bindings via ngClass directive.
22898 <file name="index.html">
22899 <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p>
22901 <input type="checkbox" ng-model="deleted">
22902 deleted (apply "strike" class)
22905 <input type="checkbox" ng-model="important">
22906 important (apply "bold" class)
22909 <input type="checkbox" ng-model="error">
22910 error (apply "has-error" class)
22913 <p ng-class="style">Using String Syntax</p>
22914 <input type="text" ng-model="style"
22915 placeholder="Type: bold strike red" aria-label="Type: bold strike red">
22917 <p ng-class="[style1, style2, style3]">Using Array Syntax</p>
22918 <input ng-model="style1"
22919 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br>
22920 <input ng-model="style2"
22921 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br>
22922 <input ng-model="style3"
22923 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br>
22925 <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p>
22926 <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br>
22927 <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label>
22929 <file name="style.css">
22931 text-decoration: line-through;
22941 background-color: yellow;
22947 <file name="protractor.js" type="protractor">
22948 var ps = element.all(by.css('p'));
22950 it('should let you toggle the class', function() {
22952 expect(ps.first().getAttribute('class')).not.toMatch(/bold/);
22953 expect(ps.first().getAttribute('class')).not.toMatch(/has-error/);
22955 element(by.model('important')).click();
22956 expect(ps.first().getAttribute('class')).toMatch(/bold/);
22958 element(by.model('error')).click();
22959 expect(ps.first().getAttribute('class')).toMatch(/has-error/);
22962 it('should let you toggle string example', function() {
22963 expect(ps.get(1).getAttribute('class')).toBe('');
22964 element(by.model('style')).clear();
22965 element(by.model('style')).sendKeys('red');
22966 expect(ps.get(1).getAttribute('class')).toBe('red');
22969 it('array example should have 3 classes', function() {
22970 expect(ps.get(2).getAttribute('class')).toBe('');
22971 element(by.model('style1')).sendKeys('bold');
22972 element(by.model('style2')).sendKeys('strike');
22973 element(by.model('style3')).sendKeys('red');
22974 expect(ps.get(2).getAttribute('class')).toBe('bold strike red');
22977 it('array with map example should have 2 classes', function() {
22978 expect(ps.last().getAttribute('class')).toBe('');
22979 element(by.model('style4')).sendKeys('bold');
22980 element(by.model('warning')).click();
22981 expect(ps.last().getAttribute('class')).toBe('bold orange');
22988 The example below demonstrates how to perform animations using ngClass.
22990 <example module="ngAnimate" deps="angular-animate.js" animations="true">
22991 <file name="index.html">
22992 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'">
22993 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''">
22995 <span class="base-class" ng-class="myVar">Sample Text</span>
22997 <file name="style.css">
22999 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
23002 .base-class.my-class {
23007 <file name="protractor.js" type="protractor">
23008 it('should check ng-class', function() {
23009 expect(element(by.css('.base-class')).getAttribute('class')).not.
23010 toMatch(/my-class/);
23012 element(by.id('setbtn')).click();
23014 expect(element(by.css('.base-class')).getAttribute('class')).
23015 toMatch(/my-class/);
23017 element(by.id('clearbtn')).click();
23019 expect(element(by.css('.base-class')).getAttribute('class')).not.
23020 toMatch(/my-class/);
23026 ## ngClass and pre-existing CSS3 Transitions/Animations
23027 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure.
23028 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder
23029 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure
23030 to view the step by step details of {@link $animate#addClass $animate.addClass} and
23031 {@link $animate#removeClass $animate.removeClass}.
23033 var ngClassDirective = classDirective('', true);
23041 * The `ngClassOdd` and `ngClassEven` directives work exactly as
23042 * {@link ng.directive:ngClass ngClass}, except they work in
23043 * conjunction with `ngRepeat` and take effect only on odd (even) rows.
23045 * This directive can be applied only within the scope of an
23046 * {@link ng.directive:ngRepeat ngRepeat}.
23049 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result
23050 * of the evaluation can be a string representing space delimited class names or an array.
23054 <file name="index.html">
23055 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
23056 <li ng-repeat="name in names">
23057 <span ng-class-odd="'odd'" ng-class-even="'even'">
23063 <file name="style.css">
23071 <file name="protractor.js" type="protractor">
23072 it('should check ng-class-odd and ng-class-even', function() {
23073 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
23075 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
23081 var ngClassOddDirective = classDirective('Odd', 0);
23085 * @name ngClassEven
23089 * The `ngClassOdd` and `ngClassEven` directives work exactly as
23090 * {@link ng.directive:ngClass ngClass}, except they work in
23091 * conjunction with `ngRepeat` and take effect only on odd (even) rows.
23093 * This directive can be applied only within the scope of an
23094 * {@link ng.directive:ngRepeat ngRepeat}.
23097 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The
23098 * result of the evaluation can be a string representing space delimited class names or an array.
23102 <file name="index.html">
23103 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
23104 <li ng-repeat="name in names">
23105 <span ng-class-odd="'odd'" ng-class-even="'even'">
23106 {{name}}
23111 <file name="style.css">
23119 <file name="protractor.js" type="protractor">
23120 it('should check ng-class-odd and ng-class-even', function() {
23121 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
23123 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
23129 var ngClassEvenDirective = classDirective('Even', 1);
23137 * The `ngCloak` directive is used to prevent the Angular html template from being briefly
23138 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this
23139 * directive to avoid the undesirable flicker effect caused by the html template display.
23141 * The directive can be applied to the `<body>` element, but the preferred usage is to apply
23142 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering
23143 * of the browser view.
23145 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and
23146 * `angular.min.js`.
23147 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
23150 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
23151 * display: none !important;
23155 * When this css rule is loaded by the browser, all html elements (including their children) that
23156 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive
23157 * during the compilation of the template it deletes the `ngCloak` element attribute, making
23158 * the compiled element visible.
23160 * For the best result, the `angular.js` script must be loaded in the head section of the html
23161 * document; alternatively, the css rule above must be included in the external stylesheet of the
23168 <file name="index.html">
23169 <div id="template1" ng-cloak>{{ 'hello' }}</div>
23170 <div id="template2" class="ng-cloak">{{ 'world' }}</div>
23172 <file name="protractor.js" type="protractor">
23173 it('should remove the template directive and css class', function() {
23174 expect($('#template1').getAttribute('ng-cloak')).
23176 expect($('#template2').getAttribute('ng-cloak')).
23183 var ngCloakDirective = ngDirective({
23184 compile: function(element, attr) {
23185 attr.$set('ngCloak', undefined);
23186 element.removeClass('ng-cloak');
23192 * @name ngController
23195 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular
23196 * supports the principles behind the Model-View-Controller design pattern.
23198 * MVC components in angular:
23200 * * Model — Models are the properties of a scope; scopes are attached to the DOM where scope properties
23201 * are accessed through bindings.
23202 * * View — The template (HTML with data bindings) that is rendered into the View.
23203 * * Controller — The `ngController` directive specifies a Controller class; the class contains business
23204 * logic behind the application to decorate the scope with functions and values
23206 * Note that you can also attach controllers to the DOM by declaring it in a route definition
23207 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller
23208 * again using `ng-controller` in the template itself. This will cause the controller to be attached
23209 * and executed twice.
23214 * @param {expression} ngController Name of a constructor function registered with the current
23215 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression}
23216 * that on the current scope evaluates to a constructor function.
23218 * The controller instance can be published into a scope property by specifying
23219 * `ng-controller="as propertyName"`.
23221 * If the current `$controllerProvider` is configured to use globals (via
23222 * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may
23223 * also be the name of a globally accessible constructor function (not recommended).
23226 * Here is a simple form for editing user contact information. Adding, removing, clearing, and
23227 * greeting are methods declared on the controller (see source tab). These methods can
23228 * easily be called from the angular markup. Any changes to the data are automatically reflected
23229 * in the View without the need for a manual update.
23231 * Two different declaration styles are included below:
23233 * * one binds methods and properties directly onto the controller using `this`:
23234 * `ng-controller="SettingsController1 as settings"`
23235 * * one injects `$scope` into the controller:
23236 * `ng-controller="SettingsController2"`
23238 * The second option is more common in the Angular community, and is generally used in boilerplates
23239 * and in this guide. However, there are advantages to binding properties directly to the controller
23240 * and avoiding scope.
23242 * * Using `controller as` makes it obvious which controller you are accessing in the template when
23243 * multiple controllers apply to an element.
23244 * * If you are writing your controllers as classes you have easier access to the properties and
23245 * methods, which will appear on the scope, from inside the controller code.
23246 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal
23247 * inheritance masking primitives.
23249 * This example demonstrates the `controller as` syntax.
23251 * <example name="ngControllerAs" module="controllerAsExample">
23252 * <file name="index.html">
23253 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings">
23254 * <label>Name: <input type="text" ng-model="settings.name"/></label>
23255 * <button ng-click="settings.greet()">greet</button><br/>
23258 * <li ng-repeat="contact in settings.contacts">
23259 * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}">
23260 * <option>phone</option>
23261 * <option>email</option>
23263 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
23264 * <button ng-click="settings.clearContact(contact)">clear</button>
23265 * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button>
23267 * <li><button ng-click="settings.addContact()">add</button></li>
23271 * <file name="app.js">
23272 * angular.module('controllerAsExample', [])
23273 * .controller('SettingsController1', SettingsController1);
23275 * function SettingsController1() {
23276 * this.name = "John Smith";
23277 * this.contacts = [
23278 * {type: 'phone', value: '408 555 1212'},
23279 * {type: 'email', value: 'john.smith@example.org'} ];
23282 * SettingsController1.prototype.greet = function() {
23283 * alert(this.name);
23286 * SettingsController1.prototype.addContact = function() {
23287 * this.contacts.push({type: 'email', value: 'yourname@example.org'});
23290 * SettingsController1.prototype.removeContact = function(contactToRemove) {
23291 * var index = this.contacts.indexOf(contactToRemove);
23292 * this.contacts.splice(index, 1);
23295 * SettingsController1.prototype.clearContact = function(contact) {
23296 * contact.type = 'phone';
23297 * contact.value = '';
23300 * <file name="protractor.js" type="protractor">
23301 * it('should check controller as', function() {
23302 * var container = element(by.id('ctrl-as-exmpl'));
23303 * expect(container.element(by.model('settings.name'))
23304 * .getAttribute('value')).toBe('John Smith');
23306 * var firstRepeat =
23307 * container.element(by.repeater('contact in settings.contacts').row(0));
23308 * var secondRepeat =
23309 * container.element(by.repeater('contact in settings.contacts').row(1));
23311 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23312 * .toBe('408 555 1212');
23314 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
23315 * .toBe('john.smith@example.org');
23317 * firstRepeat.element(by.buttonText('clear')).click();
23319 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23322 * container.element(by.buttonText('add')).click();
23324 * expect(container.element(by.repeater('contact in settings.contacts').row(2))
23325 * .element(by.model('contact.value'))
23326 * .getAttribute('value'))
23327 * .toBe('yourname@example.org');
23332 * This example demonstrates the "attach to `$scope`" style of controller.
23334 * <example name="ngController" module="controllerExample">
23335 * <file name="index.html">
23336 * <div id="ctrl-exmpl" ng-controller="SettingsController2">
23337 * <label>Name: <input type="text" ng-model="name"/></label>
23338 * <button ng-click="greet()">greet</button><br/>
23341 * <li ng-repeat="contact in contacts">
23342 * <select ng-model="contact.type" id="select_{{$index}}">
23343 * <option>phone</option>
23344 * <option>email</option>
23346 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
23347 * <button ng-click="clearContact(contact)">clear</button>
23348 * <button ng-click="removeContact(contact)">X</button>
23350 * <li>[ <button ng-click="addContact()">add</button> ]</li>
23354 * <file name="app.js">
23355 * angular.module('controllerExample', [])
23356 * .controller('SettingsController2', ['$scope', SettingsController2]);
23358 * function SettingsController2($scope) {
23359 * $scope.name = "John Smith";
23360 * $scope.contacts = [
23361 * {type:'phone', value:'408 555 1212'},
23362 * {type:'email', value:'john.smith@example.org'} ];
23364 * $scope.greet = function() {
23365 * alert($scope.name);
23368 * $scope.addContact = function() {
23369 * $scope.contacts.push({type:'email', value:'yourname@example.org'});
23372 * $scope.removeContact = function(contactToRemove) {
23373 * var index = $scope.contacts.indexOf(contactToRemove);
23374 * $scope.contacts.splice(index, 1);
23377 * $scope.clearContact = function(contact) {
23378 * contact.type = 'phone';
23379 * contact.value = '';
23383 * <file name="protractor.js" type="protractor">
23384 * it('should check controller', function() {
23385 * var container = element(by.id('ctrl-exmpl'));
23387 * expect(container.element(by.model('name'))
23388 * .getAttribute('value')).toBe('John Smith');
23390 * var firstRepeat =
23391 * container.element(by.repeater('contact in contacts').row(0));
23392 * var secondRepeat =
23393 * container.element(by.repeater('contact in contacts').row(1));
23395 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23396 * .toBe('408 555 1212');
23397 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
23398 * .toBe('john.smith@example.org');
23400 * firstRepeat.element(by.buttonText('clear')).click();
23402 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23405 * container.element(by.buttonText('add')).click();
23407 * expect(container.element(by.repeater('contact in contacts').row(2))
23408 * .element(by.model('contact.value'))
23409 * .getAttribute('value'))
23410 * .toBe('yourname@example.org');
23416 var ngControllerDirective = [function() {
23432 * Angular has some features that can break certain
23433 * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules.
23435 * If you intend to implement these rules then you must tell Angular not to use these features.
23437 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps.
23440 * The following rules affect Angular:
23442 * * `unsafe-eval`: this rule forbids apps to use `eval` or `Function(string)` generated functions
23443 * (among other things). Angular makes use of this in the {@link $parse} service to provide a 30%
23444 * increase in the speed of evaluating Angular expressions.
23446 * * `unsafe-inline`: this rule forbids apps from inject custom styles into the document. Angular
23447 * makes use of this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}).
23448 * To make these directives work when a CSP rule is blocking inline styles, you must link to the
23449 * `angular-csp.css` in your HTML manually.
23451 * If you do not provide `ngCsp` then Angular tries to autodetect if CSP is blocking unsafe-eval
23452 * and automatically deactivates this feature in the {@link $parse} service. This autodetection,
23453 * however, triggers a CSP error to be logged in the console:
23456 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of
23457 * script in the following Content Security Policy directive: "default-src 'self'". Note that
23458 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
23461 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp`
23462 * directive on an element of the HTML document that appears before the `<script>` tag that loads
23463 * the `angular.js` file.
23465 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.*
23467 * You can specify which of the CSP related Angular features should be deactivated by providing
23468 * a value for the `ng-csp` attribute. The options are as follows:
23470 * * no-inline-style: this stops Angular from injecting CSS styles into the DOM
23472 * * no-unsafe-eval: this stops Angular from optimising $parse with unsafe eval of strings
23474 * You can use these values in the following combinations:
23477 * * No declaration means that Angular will assume that you can do inline styles, but it will do
23478 * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous versions
23481 * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell Angular to deactivate both inline
23482 * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous versions
23485 * * Specifying only `no-unsafe-eval` tells Angular that we must not use eval, but that we can inject
23486 * inline styles. E.g. `<body ng-csp="no-unsafe-eval">`.
23488 * * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can
23489 * run eval - no automcatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">`
23491 * * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject
23492 * styles nor use eval, which is the same as an empty: ng-csp.
23493 * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">`
23496 * This example shows how to apply the `ngCsp` directive to the `html` tag.
23499 <html ng-app ng-csp>
23505 // Note: the suffix `.csp` in the example name triggers
23506 // csp mode in our http server!
23507 <example name="example.csp" module="cspExample" ng-csp="true">
23508 <file name="index.html">
23509 <div ng-controller="MainController as ctrl">
23511 <button ng-click="ctrl.inc()" id="inc">Increment</button>
23512 <span id="counter">
23518 <button ng-click="ctrl.evil()" id="evil">Evil</button>
23519 <span id="evilError">
23525 <file name="script.js">
23526 angular.module('cspExample', [])
23527 .controller('MainController', function() {
23529 this.inc = function() {
23532 this.evil = function() {
23533 // jshint evil:true
23537 this.evilError = e.message;
23542 <file name="protractor.js" type="protractor">
23543 var util, webdriver;
23545 var incBtn = element(by.id('inc'));
23546 var counter = element(by.id('counter'));
23547 var evilBtn = element(by.id('evil'));
23548 var evilError = element(by.id('evilError'));
23550 function getAndClearSevereErrors() {
23551 return browser.manage().logs().get('browser').then(function(browserLog) {
23552 return browserLog.filter(function(logEntry) {
23553 return logEntry.level.value > webdriver.logging.Level.WARNING.value;
23558 function clearErrors() {
23559 getAndClearSevereErrors();
23562 function expectNoErrors() {
23563 getAndClearSevereErrors().then(function(filteredLog) {
23564 expect(filteredLog.length).toEqual(0);
23565 if (filteredLog.length) {
23566 console.log('browser console errors: ' + util.inspect(filteredLog));
23571 function expectError(regex) {
23572 getAndClearSevereErrors().then(function(filteredLog) {
23574 filteredLog.forEach(function(log) {
23575 if (log.message.match(regex)) {
23580 throw new Error('expected an error that matches ' + regex);
23585 beforeEach(function() {
23586 util = require('util');
23587 webdriver = require('protractor/node_modules/selenium-webdriver');
23590 // For now, we only test on Chrome,
23591 // as Safari does not load the page with Protractor's injected scripts,
23592 // and Firefox webdriver always disables content security policy (#6358)
23593 if (browser.params.browser !== 'chrome') {
23597 it('should not report errors when the page is loaded', function() {
23598 // clear errors so we are not dependent on previous tests
23600 // Need to reload the page as the page is already loaded when
23602 browser.driver.getCurrentUrl().then(function(url) {
23608 it('should evaluate expressions', function() {
23609 expect(counter.getText()).toEqual('0');
23611 expect(counter.getText()).toEqual('1');
23615 it('should throw and report an error when using "eval"', function() {
23617 expect(evilError.getText()).toMatch(/Content Security Policy/);
23618 expectError(/Content Security Policy/);
23624 // ngCsp is not implemented as a proper directive any more, because we need it be processed while we
23625 // bootstrap the system (before $parse is instantiated), for this reason we just have
23626 // the csp() fn that looks for the `ng-csp` attribute anywhere in the current doc
23633 * The ngClick directive allows you to specify custom behavior when
23634 * an element is clicked.
23638 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
23639 * click. ({@link guide/expression#-event- Event object is available as `$event`})
23643 <file name="index.html">
23644 <button ng-click="count = count + 1" ng-init="count=0">
23651 <file name="protractor.js" type="protractor">
23652 it('should check ng-click', function() {
23653 expect(element(by.binding('count')).getText()).toMatch('0');
23654 element(by.css('button')).click();
23655 expect(element(by.binding('count')).getText()).toMatch('1');
23661 * A collection of directives that allows creation of custom event handlers that are defined as
23662 * angular expressions and are compiled and executed within the current scope.
23664 var ngEventDirectives = {};
23666 // For events that might fire synchronously during DOM manipulation
23667 // we need to execute their event handlers asynchronously using $evalAsync,
23668 // so that they are not executed in an inconsistent state.
23669 var forceAsyncEvents = {
23674 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
23675 function(eventName) {
23676 var directiveName = directiveNormalize('ng-' + eventName);
23677 ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) {
23680 compile: function($element, attr) {
23681 // We expose the powerful $event object on the scope that provides access to the Window,
23682 // etc. that isn't protected by the fast paths in $parse. We explicitly request better
23683 // checks at the cost of speed since event handler expressions are not executed as
23684 // frequently as regular change detection.
23685 var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true);
23686 return function ngEventHandler(scope, element) {
23687 element.on(eventName, function(event) {
23688 var callback = function() {
23689 fn(scope, {$event:event});
23691 if (forceAsyncEvents[eventName] && $rootScope.$$phase) {
23692 scope.$evalAsync(callback);
23694 scope.$apply(callback);
23709 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event.
23713 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon
23714 * a dblclick. (The Event object is available as `$event`)
23718 <file name="index.html">
23719 <button ng-dblclick="count = count + 1" ng-init="count=0">
23720 Increment (on double click)
23730 * @name ngMousedown
23733 * The ngMousedown directive allows you to specify custom behavior on mousedown event.
23737 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon
23738 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`})
23742 <file name="index.html">
23743 <button ng-mousedown="count = count + 1" ng-init="count=0">
23744 Increment (on mouse down)
23757 * Specify custom behavior on mouseup event.
23761 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon
23762 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`})
23766 <file name="index.html">
23767 <button ng-mouseup="count = count + 1" ng-init="count=0">
23768 Increment (on mouse up)
23777 * @name ngMouseover
23780 * Specify custom behavior on mouseover event.
23784 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon
23785 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`})
23789 <file name="index.html">
23790 <button ng-mouseover="count = count + 1" ng-init="count=0">
23791 Increment (when mouse is over)
23801 * @name ngMouseenter
23804 * Specify custom behavior on mouseenter event.
23808 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon
23809 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`})
23813 <file name="index.html">
23814 <button ng-mouseenter="count = count + 1" ng-init="count=0">
23815 Increment (when mouse enters)
23825 * @name ngMouseleave
23828 * Specify custom behavior on mouseleave event.
23832 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon
23833 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`})
23837 <file name="index.html">
23838 <button ng-mouseleave="count = count + 1" ng-init="count=0">
23839 Increment (when mouse leaves)
23849 * @name ngMousemove
23852 * Specify custom behavior on mousemove event.
23856 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon
23857 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`})
23861 <file name="index.html">
23862 <button ng-mousemove="count = count + 1" ng-init="count=0">
23863 Increment (when mouse moves)
23876 * Specify custom behavior on keydown event.
23880 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon
23881 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
23885 <file name="index.html">
23886 <input ng-keydown="count = count + 1" ng-init="count=0">
23887 key down count: {{count}}
23898 * Specify custom behavior on keyup event.
23902 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon
23903 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
23907 <file name="index.html">
23908 <p>Typing in the input box below updates the key count</p>
23909 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}}
23911 <p>Typing in the input box below updates the keycode</p>
23912 <input ng-keyup="event=$event">
23913 <p>event keyCode: {{ event.keyCode }}</p>
23914 <p>event altKey: {{ event.altKey }}</p>
23925 * Specify custom behavior on keypress event.
23928 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon
23929 * keypress. ({@link guide/expression#-event- Event object is available as `$event`}
23930 * and can be interrogated for keyCode, altKey, etc.)
23934 <file name="index.html">
23935 <input ng-keypress="count = count + 1" ng-init="count=0">
23936 key press count: {{count}}
23947 * Enables binding angular expressions to onsubmit events.
23949 * Additionally it prevents the default action (which for form means sending the request to the
23950 * server and reloading the current page), but only if the form does not contain `action`,
23951 * `data-action`, or `x-action` attributes.
23953 * <div class="alert alert-warning">
23954 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and
23955 * `ngSubmit` handlers together. See the
23956 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation}
23957 * for a detailed discussion of when `ngSubmit` may be triggered.
23962 * @param {expression} ngSubmit {@link guide/expression Expression} to eval.
23963 * ({@link guide/expression#-event- Event object is available as `$event`})
23966 <example module="submitExample">
23967 <file name="index.html">
23969 angular.module('submitExample', [])
23970 .controller('ExampleController', ['$scope', function($scope) {
23972 $scope.text = 'hello';
23973 $scope.submit = function() {
23975 $scope.list.push(this.text);
23981 <form ng-submit="submit()" ng-controller="ExampleController">
23982 Enter text and hit enter:
23983 <input type="text" ng-model="text" name="text" />
23984 <input type="submit" id="submit" value="Submit" />
23985 <pre>list={{list}}</pre>
23988 <file name="protractor.js" type="protractor">
23989 it('should check ng-submit', function() {
23990 expect(element(by.binding('list')).getText()).toBe('list=[]');
23991 element(by.css('#submit')).click();
23992 expect(element(by.binding('list')).getText()).toContain('hello');
23993 expect(element(by.model('text')).getAttribute('value')).toBe('');
23995 it('should ignore empty strings', function() {
23996 expect(element(by.binding('list')).getText()).toBe('list=[]');
23997 element(by.css('#submit')).click();
23998 element(by.css('#submit')).click();
23999 expect(element(by.binding('list')).getText()).toContain('hello');
24010 * Specify custom behavior on focus event.
24012 * Note: As the `focus` event is executed synchronously when calling `input.focus()`
24013 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired
24014 * during an `$apply` to ensure a consistent state.
24016 * @element window, input, select, textarea, a
24018 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon
24019 * focus. ({@link guide/expression#-event- Event object is available as `$event`})
24022 * See {@link ng.directive:ngClick ngClick}
24030 * Specify custom behavior on blur event.
24032 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when
24033 * an element has lost focus.
24035 * Note: As the `blur` event is executed synchronously also during DOM manipulations
24036 * (e.g. removing a focussed input),
24037 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired
24038 * during an `$apply` to ensure a consistent state.
24040 * @element window, input, select, textarea, a
24042 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon
24043 * blur. ({@link guide/expression#-event- Event object is available as `$event`})
24046 * See {@link ng.directive:ngClick ngClick}
24054 * Specify custom behavior on copy event.
24056 * @element window, input, select, textarea, a
24058 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon
24059 * copy. ({@link guide/expression#-event- Event object is available as `$event`})
24063 <file name="index.html">
24064 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value">
24075 * Specify custom behavior on cut event.
24077 * @element window, input, select, textarea, a
24079 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon
24080 * cut. ({@link guide/expression#-event- Event object is available as `$event`})
24084 <file name="index.html">
24085 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value">
24096 * Specify custom behavior on paste event.
24098 * @element window, input, select, textarea, a
24100 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon
24101 * paste. ({@link guide/expression#-event- Event object is available as `$event`})
24105 <file name="index.html">
24106 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'>
24119 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an
24120 * {expression}. If the expression assigned to `ngIf` evaluates to a false
24121 * value then the element is removed from the DOM, otherwise a clone of the
24122 * element is reinserted into the DOM.
24124 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the
24125 * element in the DOM rather than changing its visibility via the `display` css property. A common
24126 * case when this difference is significant is when using css selectors that rely on an element's
24127 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classes.
24129 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope
24130 * is created when the element is restored. The scope created within `ngIf` inherits from
24131 * its parent scope using
24132 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance).
24133 * An important implication of this is if `ngModel` is used within `ngIf` to bind to
24134 * a javascript primitive defined in the parent scope. In this case any modifications made to the
24135 * variable within the child scope will override (hide) the value in the parent scope.
24137 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior
24138 * is if an element's class attribute is directly modified after it's compiled, using something like
24139 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element
24140 * the added class will be lost because the original compiled state is used to regenerate the element.
24142 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter`
24143 * and `leave` effects.
24146 * enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container
24147 * leave - happens just before the `ngIf` contents are removed from the DOM
24152 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then
24153 * the element is removed from the DOM tree. If it is truthy a copy of the compiled
24154 * element is added to the DOM tree.
24157 <example module="ngAnimate" deps="angular-animate.js" animations="true">
24158 <file name="index.html">
24159 <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/>
24161 <span ng-if="checked" class="animate-if">
24162 This is removed when the checkbox is unchecked.
24165 <file name="animations.css">
24168 border:1px solid black;
24172 .animate-if.ng-enter, .animate-if.ng-leave {
24173 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
24176 .animate-if.ng-enter,
24177 .animate-if.ng-leave.ng-leave-active {
24181 .animate-if.ng-leave,
24182 .animate-if.ng-enter.ng-enter-active {
24188 var ngIfDirective = ['$animate', function($animate) {
24190 multiElement: true,
24191 transclude: 'element',
24196 link: function($scope, $element, $attr, ctrl, $transclude) {
24197 var block, childScope, previousElements;
24198 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) {
24202 $transclude(function(clone, newScope) {
24203 childScope = newScope;
24204 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ');
24205 // Note: We only need the first/last node of the cloned nodes.
24206 // However, we need to keep the reference to the jqlite wrapper as it might be changed later
24207 // by a directive with templateUrl when its template arrives.
24211 $animate.enter(clone, $element.parent(), $element);
24215 if (previousElements) {
24216 previousElements.remove();
24217 previousElements = null;
24220 childScope.$destroy();
24224 previousElements = getBlockNodes(block.clone);
24225 $animate.leave(previousElements).then(function() {
24226 previousElements = null;
24242 * Fetches, compiles and includes an external HTML fragment.
24244 * By default, the template URL is restricted to the same domain and protocol as the
24245 * application document. This is done by calling {@link $sce#getTrustedResourceUrl
24246 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols
24247 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or
24248 * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link
24249 * ng.$sce Strict Contextual Escaping}.
24251 * In addition, the browser's
24252 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
24253 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
24254 * policy may further restrict whether the template is successfully loaded.
24255 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://`
24256 * access on some browsers.
24259 * enter - animation is used to bring new content into the browser.
24260 * leave - animation is used to animate existing content away.
24262 * The enter and leave animation occur concurrently.
24267 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
24268 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`.
24269 * @param {string=} onload Expression to evaluate when a new partial is loaded.
24270 * <div class="alert alert-warning">
24271 * **Note:** When using onload on SVG elements in IE11, the browser will try to call
24272 * a function with the name on the window element, which will usually throw a
24273 * "function is undefined" error. To fix this, you can instead use `data-onload` or a
24274 * different form that {@link guide/directive#normalization matches} `onload`.
24277 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll
24278 * $anchorScroll} to scroll the viewport after the content is loaded.
24280 * - If the attribute is not set, disable scrolling.
24281 * - If the attribute is set without value, enable scrolling.
24282 * - Otherwise enable scrolling only if the expression evaluates to truthy value.
24285 <example module="includeExample" deps="angular-animate.js" animations="true">
24286 <file name="index.html">
24287 <div ng-controller="ExampleController">
24288 <select ng-model="template" ng-options="t.name for t in templates">
24289 <option value="">(blank)</option>
24291 url of the template: <code>{{template.url}}</code>
24293 <div class="slide-animate-container">
24294 <div class="slide-animate" ng-include="template.url"></div>
24298 <file name="script.js">
24299 angular.module('includeExample', ['ngAnimate'])
24300 .controller('ExampleController', ['$scope', function($scope) {
24302 [ { name: 'template1.html', url: 'template1.html'},
24303 { name: 'template2.html', url: 'template2.html'} ];
24304 $scope.template = $scope.templates[0];
24307 <file name="template1.html">
24308 Content of template1.html
24310 <file name="template2.html">
24311 Content of template2.html
24313 <file name="animations.css">
24314 .slide-animate-container {
24317 border:1px solid black;
24326 .slide-animate.ng-enter, .slide-animate.ng-leave {
24327 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
24338 .slide-animate.ng-enter {
24341 .slide-animate.ng-enter.ng-enter-active {
24345 .slide-animate.ng-leave {
24348 .slide-animate.ng-leave.ng-leave-active {
24352 <file name="protractor.js" type="protractor">
24353 var templateSelect = element(by.model('template'));
24354 var includeElem = element(by.css('[ng-include]'));
24356 it('should load template1.html', function() {
24357 expect(includeElem.getText()).toMatch(/Content of template1.html/);
24360 it('should load template2.html', function() {
24361 if (browser.params.browser == 'firefox') {
24362 // Firefox can't handle using selects
24363 // See https://github.com/angular/protractor/issues/480
24366 templateSelect.click();
24367 templateSelect.all(by.css('option')).get(2).click();
24368 expect(includeElem.getText()).toMatch(/Content of template2.html/);
24371 it('should change to blank', function() {
24372 if (browser.params.browser == 'firefox') {
24373 // Firefox can't handle using selects
24376 templateSelect.click();
24377 templateSelect.all(by.css('option')).get(0).click();
24378 expect(includeElem.isPresent()).toBe(false);
24387 * @name ngInclude#$includeContentRequested
24388 * @eventType emit on the scope ngInclude was declared in
24390 * Emitted every time the ngInclude content is requested.
24392 * @param {Object} angularEvent Synthetic event object.
24393 * @param {String} src URL of content to load.
24399 * @name ngInclude#$includeContentLoaded
24400 * @eventType emit on the current ngInclude scope
24402 * Emitted every time the ngInclude content is reloaded.
24404 * @param {Object} angularEvent Synthetic event object.
24405 * @param {String} src URL of content to load.
24411 * @name ngInclude#$includeContentError
24412 * @eventType emit on the scope ngInclude was declared in
24414 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299)
24416 * @param {Object} angularEvent Synthetic event object.
24417 * @param {String} src URL of content to load.
24419 var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate',
24420 function($templateRequest, $anchorScroll, $animate) {
24425 transclude: 'element',
24426 controller: angular.noop,
24427 compile: function(element, attr) {
24428 var srcExp = attr.ngInclude || attr.src,
24429 onloadExp = attr.onload || '',
24430 autoScrollExp = attr.autoscroll;
24432 return function(scope, $element, $attr, ctrl, $transclude) {
24433 var changeCounter = 0,
24438 var cleanupLastIncludeContent = function() {
24439 if (previousElement) {
24440 previousElement.remove();
24441 previousElement = null;
24443 if (currentScope) {
24444 currentScope.$destroy();
24445 currentScope = null;
24447 if (currentElement) {
24448 $animate.leave(currentElement).then(function() {
24449 previousElement = null;
24451 previousElement = currentElement;
24452 currentElement = null;
24456 scope.$watch(srcExp, function ngIncludeWatchAction(src) {
24457 var afterAnimation = function() {
24458 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
24462 var thisChangeId = ++changeCounter;
24465 //set the 2nd param to true to ignore the template request error so that the inner
24466 //contents and scope can be cleaned up.
24467 $templateRequest(src, true).then(function(response) {
24468 if (thisChangeId !== changeCounter) return;
24469 var newScope = scope.$new();
24470 ctrl.template = response;
24472 // Note: This will also link all children of ng-include that were contained in the original
24473 // html. If that content contains controllers, ... they could pollute/change the scope.
24474 // However, using ng-include on an element with additional content does not make sense...
24475 // Note: We can't remove them in the cloneAttchFn of $transclude as that
24476 // function is called before linking the content, which would apply child
24477 // directives to non existing elements.
24478 var clone = $transclude(newScope, function(clone) {
24479 cleanupLastIncludeContent();
24480 $animate.enter(clone, null, $element).then(afterAnimation);
24483 currentScope = newScope;
24484 currentElement = clone;
24486 currentScope.$emit('$includeContentLoaded', src);
24487 scope.$eval(onloadExp);
24489 if (thisChangeId === changeCounter) {
24490 cleanupLastIncludeContent();
24491 scope.$emit('$includeContentError', src);
24494 scope.$emit('$includeContentRequested', src);
24496 cleanupLastIncludeContent();
24497 ctrl.template = null;
24505 // This directive is called during the $transclude call of the first `ngInclude` directive.
24506 // It will replace and compile the content of the element with the loaded template.
24507 // We need this directive so that the element content is already filled when
24508 // the link function of another directive on the same element as ngInclude
24510 var ngIncludeFillContentDirective = ['$compile',
24511 function($compile) {
24515 require: 'ngInclude',
24516 link: function(scope, $element, $attr, ctrl) {
24517 if (/SVG/.test($element[0].toString())) {
24518 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not
24519 // support innerHTML, so detect this here and try to generate the contents
24522 $compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope,
24523 function namespaceAdaptedClone(clone) {
24524 $element.append(clone);
24525 }, {futureParentElement: $element});
24529 $element.html(ctrl.template);
24530 $compile($element.contents())(scope);
24541 * The `ngInit` directive allows you to evaluate an expression in the
24544 * <div class="alert alert-danger">
24545 * This directive can be abused to add unnecessary amounts of logic into your templates.
24546 * There are only a few appropriate uses of `ngInit`, such as for aliasing special properties of
24547 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below; and for injecting data via
24548 * server side scripting. Besides these few cases, you should use {@link guide/controller controllers}
24549 * rather than `ngInit` to initialize values on a scope.
24552 * <div class="alert alert-warning">
24553 * **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make
24554 * sure you have parentheses to ensure correct operator precedence:
24555 * <pre class="prettyprint">
24556 * `<div ng-init="test1 = ($index | toString)"></div>`
24563 * @param {expression} ngInit {@link guide/expression Expression} to eval.
24566 <example module="initExample">
24567 <file name="index.html">
24569 angular.module('initExample', [])
24570 .controller('ExampleController', ['$scope', function($scope) {
24571 $scope.list = [['a', 'b'], ['c', 'd']];
24574 <div ng-controller="ExampleController">
24575 <div ng-repeat="innerList in list" ng-init="outerIndex = $index">
24576 <div ng-repeat="value in innerList" ng-init="innerIndex = $index">
24577 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span>
24582 <file name="protractor.js" type="protractor">
24583 it('should alias index positions', function() {
24584 var elements = element.all(by.css('.example-init'));
24585 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;');
24586 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;');
24587 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;');
24588 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;');
24593 var ngInitDirective = ngDirective({
24595 compile: function() {
24597 pre: function(scope, element, attrs) {
24598 scope.$eval(attrs.ngInit);
24609 * Text input that converts between a delimited string and an array of strings. The default
24610 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom
24611 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`.
24613 * The behaviour of the directive is affected by the use of the `ngTrim` attribute.
24614 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each
24615 * list item is respected. This implies that the user of the directive is responsible for
24616 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a
24617 * tab or newline character.
24618 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected
24619 * when joining the list items back together) and whitespace around each list item is stripped
24620 * before it is added to the model.
24622 * ### Example with Validation
24624 * <example name="ngList-directive" module="listExample">
24625 * <file name="app.js">
24626 * angular.module('listExample', [])
24627 * .controller('ExampleController', ['$scope', function($scope) {
24628 * $scope.names = ['morpheus', 'neo', 'trinity'];
24631 * <file name="index.html">
24632 * <form name="myForm" ng-controller="ExampleController">
24633 * <label>List: <input name="namesInput" ng-model="names" ng-list required></label>
24634 * <span role="alert">
24635 * <span class="error" ng-show="myForm.namesInput.$error.required">
24639 * <tt>names = {{names}}</tt><br/>
24640 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
24641 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
24642 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
24643 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
24646 * <file name="protractor.js" type="protractor">
24647 * var listInput = element(by.model('names'));
24648 * var names = element(by.exactBinding('names'));
24649 * var valid = element(by.binding('myForm.namesInput.$valid'));
24650 * var error = element(by.css('span.error'));
24652 * it('should initialize to model', function() {
24653 * expect(names.getText()).toContain('["morpheus","neo","trinity"]');
24654 * expect(valid.getText()).toContain('true');
24655 * expect(error.getCssValue('display')).toBe('none');
24658 * it('should be invalid if empty', function() {
24659 * listInput.clear();
24660 * listInput.sendKeys('');
24662 * expect(names.getText()).toContain('');
24663 * expect(valid.getText()).toContain('false');
24664 * expect(error.getCssValue('display')).not.toBe('none');
24669 * ### Example - splitting on newline
24670 * <example name="ngList-directive-newlines">
24671 * <file name="index.html">
24672 * <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea>
24673 * <pre>{{ list | json }}</pre>
24675 * <file name="protractor.js" type="protractor">
24676 * it("should split the text by newlines", function() {
24677 * var listInput = element(by.model('list'));
24678 * var output = element(by.binding('list | json'));
24679 * listInput.sendKeys('abc\ndef\nghi');
24680 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]');
24686 * @param {string=} ngList optional delimiter that should be used to split the value.
24688 var ngListDirective = function() {
24692 require: 'ngModel',
24693 link: function(scope, element, attr, ctrl) {
24694 // We want to control whitespace trimming so we use this convoluted approach
24695 // to access the ngList attribute, which doesn't pre-trim the attribute
24696 var ngList = element.attr(attr.$attr.ngList) || ', ';
24697 var trimValues = attr.ngTrim !== 'false';
24698 var separator = trimValues ? trim(ngList) : ngList;
24700 var parse = function(viewValue) {
24701 // If the viewValue is invalid (say required but empty) it will be `undefined`
24702 if (isUndefined(viewValue)) return;
24707 forEach(viewValue.split(separator), function(value) {
24708 if (value) list.push(trimValues ? trim(value) : value);
24715 ctrl.$parsers.push(parse);
24716 ctrl.$formatters.push(function(value) {
24717 if (isArray(value)) {
24718 return value.join(ngList);
24724 // Override the standard $isEmpty because an empty array means the input is empty.
24725 ctrl.$isEmpty = function(value) {
24726 return !value || !value.length;
24732 /* global VALID_CLASS: true,
24733 INVALID_CLASS: true,
24734 PRISTINE_CLASS: true,
24736 UNTOUCHED_CLASS: true,
24737 TOUCHED_CLASS: true,
24740 var VALID_CLASS = 'ng-valid',
24741 INVALID_CLASS = 'ng-invalid',
24742 PRISTINE_CLASS = 'ng-pristine',
24743 DIRTY_CLASS = 'ng-dirty',
24744 UNTOUCHED_CLASS = 'ng-untouched',
24745 TOUCHED_CLASS = 'ng-touched',
24746 PENDING_CLASS = 'ng-pending';
24748 var ngModelMinErr = minErr('ngModel');
24752 * @name ngModel.NgModelController
24754 * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a
24755 * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue
24757 * @property {*} $modelValue The value in the model that the control is bound to.
24758 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
24759 the control reads value from the DOM. The functions are called in array order, each passing
24760 its return value through to the next. The last return value is forwarded to the
24761 {@link ngModel.NgModelController#$validators `$validators`} collection.
24763 Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue
24766 Returning `undefined` from a parser means a parse error occurred. In that case,
24767 no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel`
24768 will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`}
24769 is set to `true`. The parse error is stored in `ngModel.$error.parse`.
24772 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
24773 the model value changes. The functions are called in reverse array order, each passing the value through to the
24774 next. The last return value is used as the actual DOM value.
24775 Used to format / convert values for display in the control.
24777 * function formatter(value) {
24779 * return value.toUpperCase();
24782 * ngModel.$formatters.push(formatter);
24785 * @property {Object.<string, function>} $validators A collection of validators that are applied
24786 * whenever the model value changes. The key value within the object refers to the name of the
24787 * validator while the function refers to the validation operation. The validation operation is
24788 * provided with the model value as an argument and must return a true or false value depending
24789 * on the response of that validation.
24792 * ngModel.$validators.validCharacters = function(modelValue, viewValue) {
24793 * var value = modelValue || viewValue;
24794 * return /[0-9]+/.test(value) &&
24795 * /[a-z]+/.test(value) &&
24796 * /[A-Z]+/.test(value) &&
24797 * /\W+/.test(value);
24801 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to
24802 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided
24803 * is expected to return a promise when it is run during the model validation process. Once the promise
24804 * is delivered then the validation status will be set to true when fulfilled and false when rejected.
24805 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model
24806 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator
24807 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators
24808 * will only run once all synchronous validators have passed.
24810 * Please note that if $http is used then it is important that the server returns a success HTTP response code
24811 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation.
24814 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
24815 * var value = modelValue || viewValue;
24817 * // Lookup user by username
24818 * return $http.get('/api/users/' + value).
24819 * then(function resolved() {
24820 * //username exists, this means validation fails
24821 * return $q.reject('exists');
24822 * }, function rejected() {
24823 * //username does not exist, therefore this validation passes
24829 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the
24830 * view value has changed. It is called with no arguments, and its return value is ignored.
24831 * This can be used in place of additional $watches against the model value.
24833 * @property {Object} $error An object hash with all failing validator ids as keys.
24834 * @property {Object} $pending An object hash with all pending validator ids as keys.
24836 * @property {boolean} $untouched True if control has not lost focus yet.
24837 * @property {boolean} $touched True if control has lost focus.
24838 * @property {boolean} $pristine True if user has not interacted with the control yet.
24839 * @property {boolean} $dirty True if user has already interacted with the control.
24840 * @property {boolean} $valid True if there is no error.
24841 * @property {boolean} $invalid True if at least one error on the control.
24842 * @property {string} $name The name attribute of the control.
24846 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive.
24847 * The controller contains services for data-binding, validation, CSS updates, and value formatting
24848 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or
24849 * listening to DOM events.
24850 * Such DOM related logic should be provided by other directives which make use of
24851 * `NgModelController` for data-binding to control elements.
24852 * Angular provides this DOM logic for most {@link input `input`} elements.
24853 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example
24854 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements.
24857 * ### Custom Control Example
24858 * This example shows how to use `NgModelController` with a custom control to achieve
24859 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
24860 * collaborate together to achieve the desired result.
24862 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element
24863 * contents be edited in place by the user.
24865 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize}
24866 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`).
24867 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks
24868 * that content using the `$sce` service.
24870 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js">
24871 <file name="style.css">
24872 [contenteditable] {
24873 border: 1px solid black;
24874 background-color: white;
24879 border: 1px solid red;
24883 <file name="script.js">
24884 angular.module('customControl', ['ngSanitize']).
24885 directive('contenteditable', ['$sce', function($sce) {
24887 restrict: 'A', // only activate on element attribute
24888 require: '?ngModel', // get a hold of NgModelController
24889 link: function(scope, element, attrs, ngModel) {
24890 if (!ngModel) return; // do nothing if no ng-model
24892 // Specify how UI should be updated
24893 ngModel.$render = function() {
24894 element.html($sce.getTrustedHtml(ngModel.$viewValue || ''));
24897 // Listen for change events to enable binding
24898 element.on('blur keyup change', function() {
24899 scope.$evalAsync(read);
24901 read(); // initialize
24903 // Write data to the model
24905 var html = element.html();
24906 // When we clear the content editable the browser leaves a <br> behind
24907 // If strip-br attribute is provided then we strip this out
24908 if ( attrs.stripBr && html == '<br>' ) {
24911 ngModel.$setViewValue(html);
24917 <file name="index.html">
24918 <form name="myForm">
24919 <div contenteditable
24920 name="myWidget" ng-model="userContent"
24922 required>Change me!</div>
24923 <span ng-show="myForm.myWidget.$error.required">Required!</span>
24925 <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea>
24928 <file name="protractor.js" type="protractor">
24929 it('should data-bind and become invalid', function() {
24930 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') {
24931 // SafariDriver can't handle contenteditable
24932 // and Firefox driver can't clear contenteditables very well
24935 var contentEditable = element(by.css('[contenteditable]'));
24936 var content = 'Change me!';
24938 expect(contentEditable.getText()).toEqual(content);
24940 contentEditable.clear();
24941 contentEditable.sendKeys(protractor.Key.BACK_SPACE);
24942 expect(contentEditable.getText()).toEqual('');
24943 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/);
24950 var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate',
24951 function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) {
24952 this.$viewValue = Number.NaN;
24953 this.$modelValue = Number.NaN;
24954 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity.
24955 this.$validators = {};
24956 this.$asyncValidators = {};
24957 this.$parsers = [];
24958 this.$formatters = [];
24959 this.$viewChangeListeners = [];
24960 this.$untouched = true;
24961 this.$touched = false;
24962 this.$pristine = true;
24963 this.$dirty = false;
24964 this.$valid = true;
24965 this.$invalid = false;
24966 this.$error = {}; // keep invalid keys here
24967 this.$$success = {}; // keep valid keys here
24968 this.$pending = undefined; // keep pending keys here
24969 this.$name = $interpolate($attr.name || '', false)($scope);
24970 this.$$parentForm = nullFormCtrl;
24972 var parsedNgModel = $parse($attr.ngModel),
24973 parsedNgModelAssign = parsedNgModel.assign,
24974 ngModelGet = parsedNgModel,
24975 ngModelSet = parsedNgModelAssign,
24976 pendingDebounce = null,
24980 this.$$setOptions = function(options) {
24981 ctrl.$options = options;
24982 if (options && options.getterSetter) {
24983 var invokeModelGetter = $parse($attr.ngModel + '()'),
24984 invokeModelSetter = $parse($attr.ngModel + '($$$p)');
24986 ngModelGet = function($scope) {
24987 var modelValue = parsedNgModel($scope);
24988 if (isFunction(modelValue)) {
24989 modelValue = invokeModelGetter($scope);
24993 ngModelSet = function($scope, newValue) {
24994 if (isFunction(parsedNgModel($scope))) {
24995 invokeModelSetter($scope, {$$$p: ctrl.$modelValue});
24997 parsedNgModelAssign($scope, ctrl.$modelValue);
25000 } else if (!parsedNgModel.assign) {
25001 throw ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}",
25002 $attr.ngModel, startingTag($element));
25008 * @name ngModel.NgModelController#$render
25011 * Called when the view needs to be updated. It is expected that the user of the ng-model
25012 * directive will implement this method.
25014 * The `$render()` method is invoked in the following situations:
25016 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last
25017 * committed value then `$render()` is called to update the input control.
25018 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and
25019 * the `$viewValue` are different from last time.
25021 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of
25022 * `$modelValue` and `$viewValue` are actually different from their previous value. If `$modelValue`
25023 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be
25024 * invoked if you only change a property on the objects.
25026 this.$render = noop;
25030 * @name ngModel.NgModelController#$isEmpty
25033 * This is called when we need to determine if the value of an input is empty.
25035 * For instance, the required directive does this to work out if the input has data or not.
25037 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`.
25039 * You can override this for input directives whose concept of being empty is different from the
25040 * default. The `checkboxInputType` directive does this because in its case a value of `false`
25043 * @param {*} value The value of the input to check for emptiness.
25044 * @returns {boolean} True if `value` is "empty".
25046 this.$isEmpty = function(value) {
25047 return isUndefined(value) || value === '' || value === null || value !== value;
25050 var currentValidationRunId = 0;
25054 * @name ngModel.NgModelController#$setValidity
25057 * Change the validity state, and notify the form.
25059 * This method can be called within $parsers/$formatters or a custom validation implementation.
25060 * However, in most cases it should be sufficient to use the `ngModel.$validators` and
25061 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically.
25063 * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned
25064 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]`
25065 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding.
25066 * The `validationErrorKey` should be in camelCase and will get converted into dash-case
25067 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
25068 * class and can be bound to as `{{someForm.someControl.$error.myError}}` .
25069 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined),
25070 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`.
25071 * Skipped is used by Angular when validators do not run because of parse errors and
25072 * when `$asyncValidators` do not run because any of the `$validators` failed.
25074 addSetValidityMethod({
25076 $element: $element,
25077 set: function(object, property) {
25078 object[property] = true;
25080 unset: function(object, property) {
25081 delete object[property];
25088 * @name ngModel.NgModelController#$setPristine
25091 * Sets the control to its pristine state.
25093 * This method can be called to remove the `ng-dirty` class and set the control to its pristine
25094 * state (`ng-pristine` class). A model is considered to be pristine when the control
25095 * has not been changed from when first compiled.
25097 this.$setPristine = function() {
25098 ctrl.$dirty = false;
25099 ctrl.$pristine = true;
25100 $animate.removeClass($element, DIRTY_CLASS);
25101 $animate.addClass($element, PRISTINE_CLASS);
25106 * @name ngModel.NgModelController#$setDirty
25109 * Sets the control to its dirty state.
25111 * This method can be called to remove the `ng-pristine` class and set the control to its dirty
25112 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed
25113 * from when first compiled.
25115 this.$setDirty = function() {
25116 ctrl.$dirty = true;
25117 ctrl.$pristine = false;
25118 $animate.removeClass($element, PRISTINE_CLASS);
25119 $animate.addClass($element, DIRTY_CLASS);
25120 ctrl.$$parentForm.$setDirty();
25125 * @name ngModel.NgModelController#$setUntouched
25128 * Sets the control to its untouched state.
25130 * This method can be called to remove the `ng-touched` class and set the control to its
25131 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched
25132 * by default, however this function can be used to restore that state if the model has
25133 * already been touched by the user.
25135 this.$setUntouched = function() {
25136 ctrl.$touched = false;
25137 ctrl.$untouched = true;
25138 $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS);
25143 * @name ngModel.NgModelController#$setTouched
25146 * Sets the control to its touched state.
25148 * This method can be called to remove the `ng-untouched` class and set the control to its
25149 * touched state (`ng-touched` class). A model is considered to be touched when the user has
25150 * first focused the control element and then shifted focus away from the control (blur event).
25152 this.$setTouched = function() {
25153 ctrl.$touched = true;
25154 ctrl.$untouched = false;
25155 $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS);
25160 * @name ngModel.NgModelController#$rollbackViewValue
25163 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`,
25164 * which may be caused by a pending debounced event or because the input is waiting for a some
25167 * If you have an input that uses `ng-model-options` to set up debounced events or events such
25168 * as blur you can have a situation where there is a period when the `$viewValue`
25169 * is out of synch with the ngModel's `$modelValue`.
25171 * In this case, you can run into difficulties if you try to update the ngModel's `$modelValue`
25172 * programmatically before these debounced/future events have resolved/occurred, because Angular's
25173 * dirty checking mechanism is not able to tell whether the model has actually changed or not.
25175 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an
25176 * input which may have such events pending. This is important in order to make sure that the
25177 * input field will be updated with the new model value and any pending operations are cancelled.
25179 * <example name="ng-model-cancel-update" module="cancel-update-example">
25180 * <file name="app.js">
25181 * angular.module('cancel-update-example', [])
25183 * .controller('CancelUpdateController', ['$scope', function($scope) {
25184 * $scope.resetWithCancel = function(e) {
25185 * if (e.keyCode == 27) {
25186 * $scope.myForm.myInput1.$rollbackViewValue();
25187 * $scope.myValue = '';
25190 * $scope.resetWithoutCancel = function(e) {
25191 * if (e.keyCode == 27) {
25192 * $scope.myValue = '';
25197 * <file name="index.html">
25198 * <div ng-controller="CancelUpdateController">
25199 * <p>Try typing something in each input. See that the model only updates when you
25200 * blur off the input.
25202 * <p>Now see what happens if you start typing then press the Escape key</p>
25204 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }">
25205 * <p id="inputDescription1">With $rollbackViewValue()</p>
25206 * <input name="myInput1" aria-describedby="inputDescription1" ng-model="myValue"
25207 * ng-keydown="resetWithCancel($event)"><br/>
25208 * myValue: "{{ myValue }}"
25210 * <p id="inputDescription2">Without $rollbackViewValue()</p>
25211 * <input name="myInput2" aria-describedby="inputDescription2" ng-model="myValue"
25212 * ng-keydown="resetWithoutCancel($event)"><br/>
25213 * myValue: "{{ myValue }}"
25219 this.$rollbackViewValue = function() {
25220 $timeout.cancel(pendingDebounce);
25221 ctrl.$viewValue = ctrl.$$lastCommittedViewValue;
25227 * @name ngModel.NgModelController#$validate
25230 * Runs each of the registered validators (first synchronous validators and then
25231 * asynchronous validators).
25232 * If the validity changes to invalid, the model will be set to `undefined`,
25233 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`.
25234 * If the validity changes to valid, it will set the model to the last available valid
25235 * `$modelValue`, i.e. either the last parsed value or the last value set from the scope.
25237 this.$validate = function() {
25238 // ignore $validate before model is initialized
25239 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
25243 var viewValue = ctrl.$$lastCommittedViewValue;
25244 // Note: we use the $$rawModelValue as $modelValue might have been
25245 // set to undefined during a view -> model update that found validation
25246 // errors. We can't parse the view here, since that could change
25247 // the model although neither viewValue nor the model on the scope changed
25248 var modelValue = ctrl.$$rawModelValue;
25250 var prevValid = ctrl.$valid;
25251 var prevModelValue = ctrl.$modelValue;
25253 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
25255 ctrl.$$runValidators(modelValue, viewValue, function(allValid) {
25256 // If there was no change in validity, don't update the model
25257 // This prevents changing an invalid modelValue to undefined
25258 if (!allowInvalid && prevValid !== allValid) {
25259 // Note: Don't check ctrl.$valid here, as we could have
25260 // external validators (e.g. calculated on the server),
25261 // that just call $setValidity and need the model value
25262 // to calculate their validity.
25263 ctrl.$modelValue = allValid ? modelValue : undefined;
25265 if (ctrl.$modelValue !== prevModelValue) {
25266 ctrl.$$writeModelToScope();
25273 this.$$runValidators = function(modelValue, viewValue, doneCallback) {
25274 currentValidationRunId++;
25275 var localValidationRunId = currentValidationRunId;
25277 // check parser error
25278 if (!processParseErrors()) {
25279 validationDone(false);
25282 if (!processSyncValidators()) {
25283 validationDone(false);
25286 processAsyncValidators();
25288 function processParseErrors() {
25289 var errorKey = ctrl.$$parserName || 'parse';
25290 if (isUndefined(parserValid)) {
25291 setValidity(errorKey, null);
25293 if (!parserValid) {
25294 forEach(ctrl.$validators, function(v, name) {
25295 setValidity(name, null);
25297 forEach(ctrl.$asyncValidators, function(v, name) {
25298 setValidity(name, null);
25301 // Set the parse error last, to prevent unsetting it, should a $validators key == parserName
25302 setValidity(errorKey, parserValid);
25303 return parserValid;
25308 function processSyncValidators() {
25309 var syncValidatorsValid = true;
25310 forEach(ctrl.$validators, function(validator, name) {
25311 var result = validator(modelValue, viewValue);
25312 syncValidatorsValid = syncValidatorsValid && result;
25313 setValidity(name, result);
25315 if (!syncValidatorsValid) {
25316 forEach(ctrl.$asyncValidators, function(v, name) {
25317 setValidity(name, null);
25324 function processAsyncValidators() {
25325 var validatorPromises = [];
25326 var allValid = true;
25327 forEach(ctrl.$asyncValidators, function(validator, name) {
25328 var promise = validator(modelValue, viewValue);
25329 if (!isPromiseLike(promise)) {
25330 throw ngModelMinErr("$asyncValidators",
25331 "Expected asynchronous validator to return a promise but got '{0}' instead.", promise);
25333 setValidity(name, undefined);
25334 validatorPromises.push(promise.then(function() {
25335 setValidity(name, true);
25336 }, function(error) {
25338 setValidity(name, false);
25341 if (!validatorPromises.length) {
25342 validationDone(true);
25344 $q.all(validatorPromises).then(function() {
25345 validationDone(allValid);
25350 function setValidity(name, isValid) {
25351 if (localValidationRunId === currentValidationRunId) {
25352 ctrl.$setValidity(name, isValid);
25356 function validationDone(allValid) {
25357 if (localValidationRunId === currentValidationRunId) {
25359 doneCallback(allValid);
25366 * @name ngModel.NgModelController#$commitViewValue
25369 * Commit a pending update to the `$modelValue`.
25371 * Updates may be pending by a debounced event or because the input is waiting for a some future
25372 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController`
25373 * usually handles calling this in response to input events.
25375 this.$commitViewValue = function() {
25376 var viewValue = ctrl.$viewValue;
25378 $timeout.cancel(pendingDebounce);
25380 // If the view value has not changed then we should just exit, except in the case where there is
25381 // a native validator on the element. In this case the validation state may have changed even though
25382 // the viewValue has stayed empty.
25383 if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) {
25386 ctrl.$$lastCommittedViewValue = viewValue;
25389 if (ctrl.$pristine) {
25392 this.$$parseAndValidate();
25395 this.$$parseAndValidate = function() {
25396 var viewValue = ctrl.$$lastCommittedViewValue;
25397 var modelValue = viewValue;
25398 parserValid = isUndefined(modelValue) ? undefined : true;
25401 for (var i = 0; i < ctrl.$parsers.length; i++) {
25402 modelValue = ctrl.$parsers[i](modelValue);
25403 if (isUndefined(modelValue)) {
25404 parserValid = false;
25409 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
25410 // ctrl.$modelValue has not been touched yet...
25411 ctrl.$modelValue = ngModelGet($scope);
25413 var prevModelValue = ctrl.$modelValue;
25414 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
25415 ctrl.$$rawModelValue = modelValue;
25417 if (allowInvalid) {
25418 ctrl.$modelValue = modelValue;
25419 writeToModelIfNeeded();
25422 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
25423 // This can happen if e.g. $setViewValue is called from inside a parser
25424 ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
25425 if (!allowInvalid) {
25426 // Note: Don't check ctrl.$valid here, as we could have
25427 // external validators (e.g. calculated on the server),
25428 // that just call $setValidity and need the model value
25429 // to calculate their validity.
25430 ctrl.$modelValue = allValid ? modelValue : undefined;
25431 writeToModelIfNeeded();
25435 function writeToModelIfNeeded() {
25436 if (ctrl.$modelValue !== prevModelValue) {
25437 ctrl.$$writeModelToScope();
25442 this.$$writeModelToScope = function() {
25443 ngModelSet($scope, ctrl.$modelValue);
25444 forEach(ctrl.$viewChangeListeners, function(listener) {
25448 $exceptionHandler(e);
25455 * @name ngModel.NgModelController#$setViewValue
25458 * Update the view value.
25460 * This method should be called when a control wants to change the view value; typically,
25461 * this is done from within a DOM event handler. For example, the {@link ng.directive:input input}
25462 * directive calls it when the value of the input changes and {@link ng.directive:select select}
25463 * calls it when an option is selected.
25465 * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers`
25466 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged
25467 * value sent directly for processing, finally to be applied to `$modelValue` and then the
25468 * **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners,
25469 * in the `$viewChangeListeners` list, are called.
25471 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn`
25472 * and the `default` trigger is not listed, all those actions will remain pending until one of the
25473 * `updateOn` events is triggered on the DOM element.
25474 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions}
25475 * directive is used with a custom debounce for this particular event.
25476 * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce`
25477 * is specified, once the timer runs out.
25479 * When used with standard inputs, the view value will always be a string (which is in some cases
25480 * parsed into another type, such as a `Date` object for `input[date]`.)
25481 * However, custom controls might also pass objects to this method. In this case, we should make
25482 * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not
25483 * perform a deep watch of objects, it only looks for a change of identity. If you only change
25484 * the property of the object then ngModel will not realise that the object has changed and
25485 * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should
25486 * not change properties of the copy once it has been passed to `$setViewValue`.
25487 * Otherwise you may cause the model value on the scope to change incorrectly.
25489 * <div class="alert alert-info">
25490 * In any case, the value passed to the method should always reflect the current value
25491 * of the control. For example, if you are calling `$setViewValue` for an input element,
25492 * you should pass the input DOM value. Otherwise, the control and the scope model become
25493 * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change
25494 * the control's DOM value in any way. If we want to change the control's DOM value
25495 * programmatically, we should update the `ngModel` scope expression. Its new value will be
25496 * picked up by the model controller, which will run it through the `$formatters`, `$render` it
25497 * to update the DOM, and finally call `$validate` on it.
25500 * @param {*} value value from the view.
25501 * @param {string} trigger Event that triggered the update.
25503 this.$setViewValue = function(value, trigger) {
25504 ctrl.$viewValue = value;
25505 if (!ctrl.$options || ctrl.$options.updateOnDefault) {
25506 ctrl.$$debounceViewValueCommit(trigger);
25510 this.$$debounceViewValueCommit = function(trigger) {
25511 var debounceDelay = 0,
25512 options = ctrl.$options,
25515 if (options && isDefined(options.debounce)) {
25516 debounce = options.debounce;
25517 if (isNumber(debounce)) {
25518 debounceDelay = debounce;
25519 } else if (isNumber(debounce[trigger])) {
25520 debounceDelay = debounce[trigger];
25521 } else if (isNumber(debounce['default'])) {
25522 debounceDelay = debounce['default'];
25526 $timeout.cancel(pendingDebounce);
25527 if (debounceDelay) {
25528 pendingDebounce = $timeout(function() {
25529 ctrl.$commitViewValue();
25531 } else if ($rootScope.$$phase) {
25532 ctrl.$commitViewValue();
25534 $scope.$apply(function() {
25535 ctrl.$commitViewValue();
25541 // Note: we cannot use a normal scope.$watch as we want to detect the following:
25542 // 1. scope value is 'a'
25543 // 2. user enters 'b'
25544 // 3. ng-change kicks in and reverts scope value to 'a'
25545 // -> scope value did not change since the last digest as
25546 // ng-change executes in apply phase
25547 // 4. view should be changed back to 'a'
25548 $scope.$watch(function ngModelWatch() {
25549 var modelValue = ngModelGet($scope);
25551 // if scope model value and ngModel value are out of sync
25552 // TODO(perf): why not move this to the action fn?
25553 if (modelValue !== ctrl.$modelValue &&
25554 // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator
25555 (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue)
25557 ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
25558 parserValid = undefined;
25560 var formatters = ctrl.$formatters,
25561 idx = formatters.length;
25563 var viewValue = modelValue;
25565 viewValue = formatters[idx](viewValue);
25567 if (ctrl.$viewValue !== viewValue) {
25568 ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
25571 ctrl.$$runValidators(modelValue, viewValue, noop);
25588 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a
25589 * property on the scope using {@link ngModel.NgModelController NgModelController},
25590 * which is created and exposed by this directive.
25592 * `ngModel` is responsible for:
25594 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select`
25596 * - Providing validation behavior (i.e. required, number, email, url).
25597 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors).
25598 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations.
25599 * - Registering the control with its parent {@link ng.directive:form form}.
25601 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the
25602 * current scope. If the property doesn't already exist on this scope, it will be created
25603 * implicitly and added to the scope.
25605 * For best practices on using `ngModel`, see:
25607 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes)
25609 * For basic examples, how to use `ngModel`, see:
25611 * - {@link ng.directive:input input}
25612 * - {@link input[text] text}
25613 * - {@link input[checkbox] checkbox}
25614 * - {@link input[radio] radio}
25615 * - {@link input[number] number}
25616 * - {@link input[email] email}
25617 * - {@link input[url] url}
25618 * - {@link input[date] date}
25619 * - {@link input[datetime-local] datetime-local}
25620 * - {@link input[time] time}
25621 * - {@link input[month] month}
25622 * - {@link input[week] week}
25623 * - {@link ng.directive:select select}
25624 * - {@link ng.directive:textarea textarea}
25627 * The following CSS classes are added and removed on the associated input/select/textarea element
25628 * depending on the validity of the model.
25630 * - `ng-valid`: the model is valid
25631 * - `ng-invalid`: the model is invalid
25632 * - `ng-valid-[key]`: for each valid key added by `$setValidity`
25633 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity`
25634 * - `ng-pristine`: the control hasn't been interacted with yet
25635 * - `ng-dirty`: the control has been interacted with
25636 * - `ng-touched`: the control has been blurred
25637 * - `ng-untouched`: the control hasn't been blurred
25638 * - `ng-pending`: any `$asyncValidators` are unfulfilled
25640 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
25642 * ## Animation Hooks
25644 * Animations within models are triggered when any of the associated CSS classes are added and removed
25645 * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`,
25646 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself.
25647 * The animations that are triggered within ngModel are similar to how they work in ngClass and
25648 * animations can be hooked into using CSS transitions, keyframes as well as JS animations.
25650 * The following example shows a simple way to utilize CSS transitions to style an input element
25651 * that has been rendered as invalid after it has been validated:
25654 * //be sure to include ngAnimate as a module to hook into more
25655 * //advanced animations
25657 * transition:0.5s linear all;
25658 * background: white;
25660 * .my-input.ng-invalid {
25667 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample">
25668 <file name="index.html">
25670 angular.module('inputExample', [])
25671 .controller('ExampleController', ['$scope', function($scope) {
25677 transition:all linear 0.5s;
25678 background: transparent;
25680 .my-input.ng-invalid {
25685 <p id="inputDescription">
25686 Update input to see transitions when valid/invalid.
25687 Integer is a valid value.
25689 <form name="testForm" ng-controller="ExampleController">
25690 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input"
25691 aria-describedby="inputDescription" />
25696 * ## Binding to a getter/setter
25698 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a
25699 * function that returns a representation of the model when called with zero arguments, and sets
25700 * the internal state of a model when called with an argument. It's sometimes useful to use this
25701 * for models that have an internal representation that's different from what the model exposes
25704 * <div class="alert alert-success">
25705 * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more
25706 * frequently than other parts of your code.
25709 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that
25710 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to
25711 * a `<form>`, which will enable this behavior for all `<input>`s within it. See
25712 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more.
25714 * The following example shows how to use `ngModel` with a getter/setter:
25717 * <example name="ngModel-getter-setter" module="getterSetterExample">
25718 <file name="index.html">
25719 <div ng-controller="ExampleController">
25720 <form name="userForm">
25722 <input type="text" name="userName"
25723 ng-model="user.name"
25724 ng-model-options="{ getterSetter: true }" />
25727 <pre>user.name = <span ng-bind="user.name()"></span></pre>
25730 <file name="app.js">
25731 angular.module('getterSetterExample', [])
25732 .controller('ExampleController', ['$scope', function($scope) {
25733 var _name = 'Brian';
25735 name: function(newName) {
25736 // Note that newName can be undefined for two reasons:
25737 // 1. Because it is called as a getter and thus called with no arguments
25738 // 2. Because the property should actually be set to undefined. This happens e.g. if the
25739 // input is invalid
25740 return arguments.length ? (_name = newName) : _name;
25747 var ngModelDirective = ['$rootScope', function($rootScope) {
25750 require: ['ngModel', '^?form', '^?ngModelOptions'],
25751 controller: NgModelController,
25752 // Prelink needs to run before any input directive
25753 // so that we can set the NgModelOptions in NgModelController
25754 // before anyone else uses it.
25756 compile: function ngModelCompile(element) {
25757 // Setup initial state of the control
25758 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);
25761 pre: function ngModelPreLink(scope, element, attr, ctrls) {
25762 var modelCtrl = ctrls[0],
25763 formCtrl = ctrls[1] || modelCtrl.$$parentForm;
25765 modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
25767 // notify others, especially parent forms
25768 formCtrl.$addControl(modelCtrl);
25770 attr.$observe('name', function(newValue) {
25771 if (modelCtrl.$name !== newValue) {
25772 modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue);
25776 scope.$on('$destroy', function() {
25777 modelCtrl.$$parentForm.$removeControl(modelCtrl);
25780 post: function ngModelPostLink(scope, element, attr, ctrls) {
25781 var modelCtrl = ctrls[0];
25782 if (modelCtrl.$options && modelCtrl.$options.updateOn) {
25783 element.on(modelCtrl.$options.updateOn, function(ev) {
25784 modelCtrl.$$debounceViewValueCommit(ev && ev.type);
25788 element.on('blur', function(ev) {
25789 if (modelCtrl.$touched) return;
25791 if ($rootScope.$$phase) {
25792 scope.$evalAsync(modelCtrl.$setTouched);
25794 scope.$apply(modelCtrl.$setTouched);
25803 var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
25807 * @name ngModelOptions
25810 * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of
25811 * events that will trigger a model update and/or a debouncing delay so that the actual update only
25812 * takes place when a timer expires; this timer will be reset after another change takes place.
25814 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might
25815 * be different from the value in the actual model. This means that if you update the model you
25816 * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in
25817 * order to make sure it is synchronized with the model and that any debounced action is canceled.
25819 * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`}
25820 * method is by making sure the input is placed inside a form that has a `name` attribute. This is
25821 * important because `form` controllers are published to the related scope under the name in their
25822 * `name` attribute.
25824 * Any pending changes will take place immediately when an enclosing form is submitted via the
25825 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
25826 * to have access to the updated model.
25828 * `ngModelOptions` has an effect on the element it's declared on and its descendants.
25830 * @param {Object} ngModelOptions options to apply to the current model. Valid keys are:
25831 * - `updateOn`: string specifying which event should the input be bound to. You can set several
25832 * events using an space delimited list. There is a special event called `default` that
25833 * matches the default events belonging of the control.
25834 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A
25835 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a
25836 * custom value for each event. For example:
25837 * `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }"`
25838 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did
25839 * not validate correctly instead of the default behavior of setting the model to undefined.
25840 * - `getterSetter`: boolean value which determines whether or not to treat functions bound to
25841 `ngModel` as getters/setters.
25842 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
25843 * `<input type="date">`, `<input type="time">`, ... . It understands UTC/GMT and the
25844 * continental US time zone abbreviations, but for general use, use a time zone offset, for
25845 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
25846 * If not specified, the timezone of the browser will be used.
25850 The following example shows how to override immediate updates. Changes on the inputs within the
25851 form will update the model only when the control loses focus (blur event). If `escape` key is
25852 pressed while the input field is focused, the value is reset to the value in the current model.
25854 <example name="ngModelOptions-directive-blur" module="optionsExample">
25855 <file name="index.html">
25856 <div ng-controller="ExampleController">
25857 <form name="userForm">
25859 <input type="text" name="userName"
25860 ng-model="user.name"
25861 ng-model-options="{ updateOn: 'blur' }"
25862 ng-keyup="cancel($event)" />
25865 <input type="text" ng-model="user.data" />
25868 <pre>user.name = <span ng-bind="user.name"></span></pre>
25869 <pre>user.data = <span ng-bind="user.data"></span></pre>
25872 <file name="app.js">
25873 angular.module('optionsExample', [])
25874 .controller('ExampleController', ['$scope', function($scope) {
25875 $scope.user = { name: 'John', data: '' };
25877 $scope.cancel = function(e) {
25878 if (e.keyCode == 27) {
25879 $scope.userForm.userName.$rollbackViewValue();
25884 <file name="protractor.js" type="protractor">
25885 var model = element(by.binding('user.name'));
25886 var input = element(by.model('user.name'));
25887 var other = element(by.model('user.data'));
25889 it('should allow custom events', function() {
25890 input.sendKeys(' Doe');
25892 expect(model.getText()).toEqual('John');
25894 expect(model.getText()).toEqual('John Doe');
25897 it('should $rollbackViewValue when model changes', function() {
25898 input.sendKeys(' Doe');
25899 expect(input.getAttribute('value')).toEqual('John Doe');
25900 input.sendKeys(protractor.Key.ESCAPE);
25901 expect(input.getAttribute('value')).toEqual('John');
25903 expect(model.getText()).toEqual('John');
25908 This one shows how to debounce model changes. Model will be updated only 1 sec after last change.
25909 If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.
25911 <example name="ngModelOptions-directive-debounce" module="optionsExample">
25912 <file name="index.html">
25913 <div ng-controller="ExampleController">
25914 <form name="userForm">
25916 <input type="text" name="userName"
25917 ng-model="user.name"
25918 ng-model-options="{ debounce: 1000 }" />
25920 <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button>
25923 <pre>user.name = <span ng-bind="user.name"></span></pre>
25926 <file name="app.js">
25927 angular.module('optionsExample', [])
25928 .controller('ExampleController', ['$scope', function($scope) {
25929 $scope.user = { name: 'Igor' };
25934 This one shows how to bind to getter/setters:
25936 <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample">
25937 <file name="index.html">
25938 <div ng-controller="ExampleController">
25939 <form name="userForm">
25941 <input type="text" name="userName"
25942 ng-model="user.name"
25943 ng-model-options="{ getterSetter: true }" />
25946 <pre>user.name = <span ng-bind="user.name()"></span></pre>
25949 <file name="app.js">
25950 angular.module('getterSetterExample', [])
25951 .controller('ExampleController', ['$scope', function($scope) {
25952 var _name = 'Brian';
25954 name: function(newName) {
25955 // Note that newName can be undefined for two reasons:
25956 // 1. Because it is called as a getter and thus called with no arguments
25957 // 2. Because the property should actually be set to undefined. This happens e.g. if the
25958 // input is invalid
25959 return arguments.length ? (_name = newName) : _name;
25966 var ngModelOptionsDirective = function() {
25969 controller: ['$scope', '$attrs', function($scope, $attrs) {
25971 this.$options = copy($scope.$eval($attrs.ngModelOptions));
25972 // Allow adding/overriding bound events
25973 if (isDefined(this.$options.updateOn)) {
25974 this.$options.updateOnDefault = false;
25975 // extract "default" pseudo-event from list of events that can trigger a model update
25976 this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() {
25977 that.$options.updateOnDefault = true;
25981 this.$options.updateOnDefault = true;
25990 function addSetValidityMethod(context) {
25991 var ctrl = context.ctrl,
25992 $element = context.$element,
25995 unset = context.unset,
25996 $animate = context.$animate;
25998 classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS));
26000 ctrl.$setValidity = setValidity;
26002 function setValidity(validationErrorKey, state, controller) {
26003 if (isUndefined(state)) {
26004 createAndSet('$pending', validationErrorKey, controller);
26006 unsetAndCleanup('$pending', validationErrorKey, controller);
26008 if (!isBoolean(state)) {
26009 unset(ctrl.$error, validationErrorKey, controller);
26010 unset(ctrl.$$success, validationErrorKey, controller);
26013 unset(ctrl.$error, validationErrorKey, controller);
26014 set(ctrl.$$success, validationErrorKey, controller);
26016 set(ctrl.$error, validationErrorKey, controller);
26017 unset(ctrl.$$success, validationErrorKey, controller);
26020 if (ctrl.$pending) {
26021 cachedToggleClass(PENDING_CLASS, true);
26022 ctrl.$valid = ctrl.$invalid = undefined;
26023 toggleValidationCss('', null);
26025 cachedToggleClass(PENDING_CLASS, false);
26026 ctrl.$valid = isObjectEmpty(ctrl.$error);
26027 ctrl.$invalid = !ctrl.$valid;
26028 toggleValidationCss('', ctrl.$valid);
26031 // re-read the state as the set/unset methods could have
26032 // combined state in ctrl.$error[validationError] (used for forms),
26033 // where setting/unsetting only increments/decrements the value,
26034 // and does not replace it.
26036 if (ctrl.$pending && ctrl.$pending[validationErrorKey]) {
26037 combinedState = undefined;
26038 } else if (ctrl.$error[validationErrorKey]) {
26039 combinedState = false;
26040 } else if (ctrl.$$success[validationErrorKey]) {
26041 combinedState = true;
26043 combinedState = null;
26046 toggleValidationCss(validationErrorKey, combinedState);
26047 ctrl.$$parentForm.$setValidity(validationErrorKey, combinedState, ctrl);
26050 function createAndSet(name, value, controller) {
26054 set(ctrl[name], value, controller);
26057 function unsetAndCleanup(name, value, controller) {
26059 unset(ctrl[name], value, controller);
26061 if (isObjectEmpty(ctrl[name])) {
26062 ctrl[name] = undefined;
26066 function cachedToggleClass(className, switchValue) {
26067 if (switchValue && !classCache[className]) {
26068 $animate.addClass($element, className);
26069 classCache[className] = true;
26070 } else if (!switchValue && classCache[className]) {
26071 $animate.removeClass($element, className);
26072 classCache[className] = false;
26076 function toggleValidationCss(validationErrorKey, isValid) {
26077 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
26079 cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true);
26080 cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false);
26084 function isObjectEmpty(obj) {
26086 for (var prop in obj) {
26087 if (obj.hasOwnProperty(prop)) {
26097 * @name ngNonBindable
26102 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current
26103 * DOM element. This is useful if the element contains what appears to be Angular directives and
26104 * bindings but which should be ignored by Angular. This could be the case if you have a site that
26105 * displays snippets of code, for instance.
26110 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present,
26111 * but the one wrapped in `ngNonBindable` is left alone.
26115 <file name="index.html">
26116 <div>Normal: {{1 + 2}}</div>
26117 <div ng-non-bindable>Ignored: {{1 + 2}}</div>
26119 <file name="protractor.js" type="protractor">
26120 it('should check ng-non-bindable', function() {
26121 expect(element(by.binding('1 + 2')).getText()).toContain('3');
26122 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/);
26127 var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
26129 /* global jqLiteRemove */
26131 var ngOptionsMinErr = minErr('ngOptions');
26140 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
26141 * elements for the `<select>` element using the array or object obtained by evaluating the
26142 * `ngOptions` comprehension expression.
26144 * In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
26145 * similar result. However, `ngOptions` provides some benefits such as reducing memory and
26146 * increasing speed by not creating a new scope for each repeated instance, as well as providing
26147 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
26148 * comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound
26149 * to a non-string value. This is because an option element can only be bound to string values at
26152 * When an item in the `<select>` menu is selected, the array element or object property
26153 * represented by the selected option will be bound to the model identified by the `ngModel`
26156 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
26157 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
26158 * option. See example below for demonstration.
26160 * ## Complex Models (objects or collections)
26162 * By default, `ngModel` watches the model by reference, not value. This is important to know when
26163 * binding the select to a model that is an object or a collection.
26165 * One issue occurs if you want to preselect an option. For example, if you set
26166 * the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection,
26167 * because the objects are not identical. So by default, you should always reference the item in your collection
26168 * for preselections, e.g.: `$scope.selected = $scope.collection[3]`.
26170 * Another solution is to use a `track by` clause, because then `ngOptions` will track the identity
26171 * of the item not by reference, but by the result of the `track by` expression. For example, if your
26172 * collection items have an id property, you would `track by item.id`.
26174 * A different issue with objects or collections is that ngModel won't detect if an object property or
26175 * a collection item changes. For that reason, `ngOptions` additionally watches the model using
26176 * `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute.
26177 * This allows ngOptions to trigger a re-rendering of the options even if the actual object/collection
26178 * has not changed identity, but only a property on the object or an item in the collection changes.
26180 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection
26181 * if the model is an array). This means that changing a property deeper than the first level inside the
26182 * object/collection will not trigger a re-rendering.
26184 * ## `select` **`as`**
26186 * Using `select` **`as`** will bind the result of the `select` expression to the model, but
26187 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
26188 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression
26189 * is used, the result of that expression will be set as the value of the `option` and `select` elements.
26192 * ### `select` **`as`** and **`track by`**
26194 * <div class="alert alert-warning">
26195 * Be careful when using `select` **`as`** and **`track by`** in the same expression.
26198 * Given this array of items on the $scope:
26201 * $scope.items = [{
26204 * subItem: { name: 'aSubItem' }
26208 * subItem: { name: 'bSubItem' }
26215 * <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select>
26218 * $scope.selected = $scope.items[0];
26221 * but this will not work:
26224 * <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select>
26227 * $scope.selected = $scope.items[0].subItem;
26230 * In both examples, the **`track by`** expression is applied successfully to each `item` in the
26231 * `items` array. Because the selected option has been set programmatically in the controller, the
26232 * **`track by`** expression is also applied to the `ngModel` value. In the first example, the
26233 * `ngModel` value is `items[0]` and the **`track by`** expression evaluates to `items[0].id` with
26234 * no issue. In the second example, the `ngModel` value is `items[0].subItem` and the **`track by`**
26235 * expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value
26236 * is not matched against any `<option>` and the `<select>` appears as having no selected value.
26239 * @param {string} ngModel Assignable angular expression to data-bind to.
26240 * @param {string=} name Property name of the form under which the control is published.
26241 * @param {string=} required The control is considered valid only if value is entered.
26242 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
26243 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
26244 * `required` when you want to data-bind to the `required` attribute.
26245 * @param {comprehension_expression=} ngOptions in one of the following forms:
26247 * * for array data sources:
26248 * * `label` **`for`** `value` **`in`** `array`
26249 * * `select` **`as`** `label` **`for`** `value` **`in`** `array`
26250 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
26251 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array`
26252 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
26253 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
26254 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
26255 * (for including a filter with `track by`)
26256 * * for object data sources:
26257 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
26258 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
26259 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
26260 * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object`
26261 * * `select` **`as`** `label` **`group by`** `group`
26262 * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
26263 * * `select` **`as`** `label` **`disable when`** `disable`
26264 * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
26268 * * `array` / `object`: an expression which evaluates to an array / object to iterate over.
26269 * * `value`: local variable which will refer to each item in the `array` or each property value
26270 * of `object` during iteration.
26271 * * `key`: local variable which will refer to a property name in `object` during iteration.
26272 * * `label`: The result of this expression will be the label for `<option>` element. The
26273 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
26274 * * `select`: The result of this expression will be bound to the model of the parent `<select>`
26275 * element. If not specified, `select` expression will default to `value`.
26276 * * `group`: The result of this expression will be used to group options using the `<optgroup>`
26278 * * `disable`: The result of this expression will be used to disable the rendered `<option>`
26279 * element. Return `true` to disable.
26280 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be
26281 * used to identify the objects in the array. The `trackexpr` will most likely refer to the
26282 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved
26283 * even when the options are recreated (e.g. reloaded from the server).
26286 <example module="selectExample">
26287 <file name="index.html">
26289 angular.module('selectExample', [])
26290 .controller('ExampleController', ['$scope', function($scope) {
26292 {name:'black', shade:'dark'},
26293 {name:'white', shade:'light', notAnOption: true},
26294 {name:'red', shade:'dark'},
26295 {name:'blue', shade:'dark', notAnOption: true},
26296 {name:'yellow', shade:'light', notAnOption: false}
26298 $scope.myColor = $scope.colors[2]; // red
26301 <div ng-controller="ExampleController">
26303 <li ng-repeat="color in colors">
26304 <label>Name: <input ng-model="color.name"></label>
26305 <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label>
26306 <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button>
26309 <button ng-click="colors.push({})">add</button>
26313 <label>Color (null not allowed):
26314 <select ng-model="myColor" ng-options="color.name for color in colors"></select>
26316 <label>Color (null allowed):
26317 <span class="nullable">
26318 <select ng-model="myColor" ng-options="color.name for color in colors">
26319 <option value="">-- choose color --</option>
26321 </span></label><br/>
26323 <label>Color grouped by shade:
26324 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors">
26328 <label>Color grouped by shade, with some disabled:
26329 <select ng-model="myColor"
26330 ng-options="color.name group by color.shade disable when color.notAnOption for color in colors">
26336 Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>.
26339 Currently selected: {{ {selected_color:myColor} }}
26340 <div style="border:solid 1px black; height:20px"
26341 ng-style="{'background-color':myColor.name}">
26345 <file name="protractor.js" type="protractor">
26346 it('should check ng-options', function() {
26347 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red');
26348 element.all(by.model('myColor')).first().click();
26349 element.all(by.css('select[ng-model="myColor"] option')).first().click();
26350 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black');
26351 element(by.css('.nullable select[ng-model="myColor"]')).click();
26352 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click();
26353 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null');
26359 // jshint maxlen: false
26360 // //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555550000000006666666666666660000000777777777777777000000000000000888888888800000000000000000009999999999
26361 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]+?))?$/;
26362 // 1: value expression (valueFn)
26363 // 2: label expression (displayFn)
26364 // 3: group by expression (groupByFn)
26365 // 4: disable when expression (disableWhenFn)
26366 // 5: array item variable name
26367 // 6: object item key variable name
26368 // 7: object item value variable name
26369 // 8: collection expression
26370 // 9: track by expression
26371 // jshint maxlen: 100
26374 var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
26376 function parseOptionsExpression(optionsExp, selectElement, scope) {
26378 var match = optionsExp.match(NG_OPTIONS_REGEXP);
26380 throw ngOptionsMinErr('iexp',
26381 "Expected expression in form of " +
26382 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
26383 " but got '{0}'. Element: {1}",
26384 optionsExp, startingTag(selectElement));
26387 // Extract the parts from the ngOptions expression
26389 // The variable name for the value of the item in the collection
26390 var valueName = match[5] || match[7];
26391 // The variable name for the key of the item in the collection
26392 var keyName = match[6];
26394 // An expression that generates the viewValue for an option if there is a label expression
26395 var selectAs = / as /.test(match[0]) && match[1];
26396 // An expression that is used to track the id of each object in the options collection
26397 var trackBy = match[9];
26398 // An expression that generates the viewValue for an option if there is no label expression
26399 var valueFn = $parse(match[2] ? match[1] : valueName);
26400 var selectAsFn = selectAs && $parse(selectAs);
26401 var viewValueFn = selectAsFn || valueFn;
26402 var trackByFn = trackBy && $parse(trackBy);
26404 // Get the value by which we are going to track the option
26405 // if we have a trackFn then use that (passing scope and locals)
26406 // otherwise just hash the given viewValue
26407 var getTrackByValueFn = trackBy ?
26408 function(value, locals) { return trackByFn(scope, locals); } :
26409 function getHashOfValue(value) { return hashKey(value); };
26410 var getTrackByValue = function(value, key) {
26411 return getTrackByValueFn(value, getLocals(value, key));
26414 var displayFn = $parse(match[2] || match[1]);
26415 var groupByFn = $parse(match[3] || '');
26416 var disableWhenFn = $parse(match[4] || '');
26417 var valuesFn = $parse(match[8]);
26420 var getLocals = keyName ? function(value, key) {
26421 locals[keyName] = key;
26422 locals[valueName] = value;
26424 } : function(value) {
26425 locals[valueName] = value;
26430 function Option(selectValue, viewValue, label, group, disabled) {
26431 this.selectValue = selectValue;
26432 this.viewValue = viewValue;
26433 this.label = label;
26434 this.group = group;
26435 this.disabled = disabled;
26438 function getOptionValuesKeys(optionValues) {
26439 var optionValuesKeys;
26441 if (!keyName && isArrayLike(optionValues)) {
26442 optionValuesKeys = optionValues;
26444 // if object, extract keys, in enumeration order, unsorted
26445 optionValuesKeys = [];
26446 for (var itemKey in optionValues) {
26447 if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') {
26448 optionValuesKeys.push(itemKey);
26452 return optionValuesKeys;
26457 getTrackByValue: getTrackByValue,
26458 getWatchables: $parse(valuesFn, function(optionValues) {
26459 // Create a collection of things that we would like to watch (watchedArray)
26460 // so that they can all be watched using a single $watchCollection
26461 // that only runs the handler once if anything changes
26462 var watchedArray = [];
26463 optionValues = optionValues || [];
26465 var optionValuesKeys = getOptionValuesKeys(optionValues);
26466 var optionValuesLength = optionValuesKeys.length;
26467 for (var index = 0; index < optionValuesLength; index++) {
26468 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
26469 var value = optionValues[key];
26471 var locals = getLocals(optionValues[key], key);
26472 var selectValue = getTrackByValueFn(optionValues[key], locals);
26473 watchedArray.push(selectValue);
26475 // Only need to watch the displayFn if there is a specific label expression
26476 if (match[2] || match[1]) {
26477 var label = displayFn(scope, locals);
26478 watchedArray.push(label);
26481 // Only need to watch the disableWhenFn if there is a specific disable expression
26483 var disableWhen = disableWhenFn(scope, locals);
26484 watchedArray.push(disableWhen);
26487 return watchedArray;
26490 getOptions: function() {
26492 var optionItems = [];
26493 var selectValueMap = {};
26495 // The option values were already computed in the `getWatchables` fn,
26496 // which must have been called to trigger `getOptions`
26497 var optionValues = valuesFn(scope) || [];
26498 var optionValuesKeys = getOptionValuesKeys(optionValues);
26499 var optionValuesLength = optionValuesKeys.length;
26501 for (var index = 0; index < optionValuesLength; index++) {
26502 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
26503 var value = optionValues[key];
26504 var locals = getLocals(value, key);
26505 var viewValue = viewValueFn(scope, locals);
26506 var selectValue = getTrackByValueFn(viewValue, locals);
26507 var label = displayFn(scope, locals);
26508 var group = groupByFn(scope, locals);
26509 var disabled = disableWhenFn(scope, locals);
26510 var optionItem = new Option(selectValue, viewValue, label, group, disabled);
26512 optionItems.push(optionItem);
26513 selectValueMap[selectValue] = optionItem;
26517 items: optionItems,
26518 selectValueMap: selectValueMap,
26519 getOptionFromViewValue: function(value) {
26520 return selectValueMap[getTrackByValue(value)];
26522 getViewValueFromOption: function(option) {
26523 // If the viewValue could be an object that may be mutated by the application,
26524 // we need to make a copy and not return the reference to the value on the option.
26525 return trackBy ? angular.copy(option.viewValue) : option.viewValue;
26533 // we can't just jqLite('<option>') since jqLite is not smart enough
26534 // to create it in <select> and IE barfs otherwise.
26535 var optionTemplate = document.createElement('option'),
26536 optGroupTemplate = document.createElement('optgroup');
26539 function ngOptionsPostLink(scope, selectElement, attr, ctrls) {
26541 // if ngModel is not defined, we don't need to do anything
26542 var ngModelCtrl = ctrls[1];
26543 if (!ngModelCtrl) return;
26545 var selectCtrl = ctrls[0];
26546 var multiple = attr.multiple;
26548 // The emptyOption allows the application developer to provide their own custom "empty"
26549 // option when the viewValue does not match any of the option values.
26551 for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) {
26552 if (children[i].value === '') {
26553 emptyOption = children.eq(i);
26558 var providedEmptyOption = !!emptyOption;
26560 var unknownOption = jqLite(optionTemplate.cloneNode(false));
26561 unknownOption.val('?');
26564 var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope);
26567 var renderEmptyOption = function() {
26568 if (!providedEmptyOption) {
26569 selectElement.prepend(emptyOption);
26571 selectElement.val('');
26572 emptyOption.prop('selected', true); // needed for IE
26573 emptyOption.attr('selected', true);
26576 var removeEmptyOption = function() {
26577 if (!providedEmptyOption) {
26578 emptyOption.remove();
26583 var renderUnknownOption = function() {
26584 selectElement.prepend(unknownOption);
26585 selectElement.val('?');
26586 unknownOption.prop('selected', true); // needed for IE
26587 unknownOption.attr('selected', true);
26590 var removeUnknownOption = function() {
26591 unknownOption.remove();
26594 // Update the controller methods for multiple selectable options
26597 selectCtrl.writeValue = function writeNgOptionsValue(value) {
26598 var option = options.getOptionFromViewValue(value);
26600 if (option && !option.disabled) {
26601 if (selectElement[0].value !== option.selectValue) {
26602 removeUnknownOption();
26603 removeEmptyOption();
26605 selectElement[0].value = option.selectValue;
26606 option.element.selected = true;
26607 option.element.setAttribute('selected', 'selected');
26610 if (value === null || providedEmptyOption) {
26611 removeUnknownOption();
26612 renderEmptyOption();
26614 removeEmptyOption();
26615 renderUnknownOption();
26620 selectCtrl.readValue = function readNgOptionsValue() {
26622 var selectedOption = options.selectValueMap[selectElement.val()];
26624 if (selectedOption && !selectedOption.disabled) {
26625 removeEmptyOption();
26626 removeUnknownOption();
26627 return options.getViewValueFromOption(selectedOption);
26632 // If we are using `track by` then we must watch the tracked value on the model
26633 // since ngModel only watches for object identity change
26634 if (ngOptions.trackBy) {
26636 function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); },
26637 function() { ngModelCtrl.$render(); }
26643 ngModelCtrl.$isEmpty = function(value) {
26644 return !value || value.length === 0;
26648 selectCtrl.writeValue = function writeNgOptionsMultiple(value) {
26649 options.items.forEach(function(option) {
26650 option.element.selected = false;
26654 value.forEach(function(item) {
26655 var option = options.getOptionFromViewValue(item);
26656 if (option && !option.disabled) option.element.selected = true;
26662 selectCtrl.readValue = function readNgOptionsMultiple() {
26663 var selectedValues = selectElement.val() || [],
26666 forEach(selectedValues, function(value) {
26667 var option = options.selectValueMap[value];
26668 if (option && !option.disabled) selections.push(options.getViewValueFromOption(option));
26674 // If we are using `track by` then we must watch these tracked values on the model
26675 // since ngModel only watches for object identity change
26676 if (ngOptions.trackBy) {
26678 scope.$watchCollection(function() {
26679 if (isArray(ngModelCtrl.$viewValue)) {
26680 return ngModelCtrl.$viewValue.map(function(value) {
26681 return ngOptions.getTrackByValue(value);
26685 ngModelCtrl.$render();
26692 if (providedEmptyOption) {
26694 // we need to remove it before calling selectElement.empty() because otherwise IE will
26695 // remove the label from the element. wtf?
26696 emptyOption.remove();
26698 // compile the element since there might be bindings in it
26699 $compile(emptyOption)(scope);
26701 // remove the class, which is added automatically because we recompile the element and it
26702 // becomes the compilation root
26703 emptyOption.removeClass('ng-scope');
26705 emptyOption = jqLite(optionTemplate.cloneNode(false));
26708 // We need to do this here to ensure that the options object is defined
26709 // when we first hit it in writeNgOptionsValue
26712 // We will re-render the option elements if the option values or labels change
26713 scope.$watchCollection(ngOptions.getWatchables, updateOptions);
26715 // ------------------------------------------------------------------ //
26718 function updateOptionElement(option, element) {
26719 option.element = element;
26720 element.disabled = option.disabled;
26721 // NOTE: The label must be set before the value, otherwise IE10/11/EDGE create unresponsive
26722 // selects in certain circumstances when multiple selects are next to each other and display
26723 // the option list in listbox style, i.e. the select is [multiple], or specifies a [size].
26724 // See https://github.com/angular/angular.js/issues/11314 for more info.
26725 // This is unfortunately untestable with unit / e2e tests
26726 if (option.label !== element.label) {
26727 element.label = option.label;
26728 element.textContent = option.label;
26730 if (option.value !== element.value) element.value = option.selectValue;
26733 function addOrReuseElement(parent, current, type, templateElement) {
26735 // Check whether we can reuse the next element
26736 if (current && lowercase(current.nodeName) === type) {
26737 // The next element is the right type so reuse it
26740 // The next element is not the right type so create a new one
26741 element = templateElement.cloneNode(false);
26743 // There are no more elements so just append it to the select
26744 parent.appendChild(element);
26746 // The next element is not a group so insert the new one
26747 parent.insertBefore(element, current);
26754 function removeExcessElements(current) {
26757 next = current.nextSibling;
26758 jqLiteRemove(current);
26764 function skipEmptyAndUnknownOptions(current) {
26765 var emptyOption_ = emptyOption && emptyOption[0];
26766 var unknownOption_ = unknownOption && unknownOption[0];
26768 // We cannot rely on the extracted empty option being the same as the compiled empty option,
26769 // because the compiled empty option might have been replaced by a comment because
26770 // it had an "element" transclusion directive on it (such as ngIf)
26771 if (emptyOption_ || unknownOption_) {
26773 (current === emptyOption_ ||
26774 current === unknownOption_ ||
26775 current.nodeType === NODE_TYPE_COMMENT ||
26776 current.value === '')) {
26777 current = current.nextSibling;
26784 function updateOptions() {
26786 var previousValue = options && selectCtrl.readValue();
26788 options = ngOptions.getOptions();
26791 var currentElement = selectElement[0].firstChild;
26793 // Ensure that the empty option is always there if it was explicitly provided
26794 if (providedEmptyOption) {
26795 selectElement.prepend(emptyOption);
26798 currentElement = skipEmptyAndUnknownOptions(currentElement);
26800 options.items.forEach(function updateOption(option) {
26805 if (option.group) {
26807 // This option is to live in a group
26808 // See if we have already created this group
26809 group = groupMap[option.group];
26813 // We have not already created this group
26814 groupElement = addOrReuseElement(selectElement[0],
26818 // Move to the next element
26819 currentElement = groupElement.nextSibling;
26821 // Update the label on the group element
26822 groupElement.label = option.group;
26824 // Store it for use later
26825 group = groupMap[option.group] = {
26826 groupElement: groupElement,
26827 currentOptionElement: groupElement.firstChild
26832 // So now we have a group for this option we add the option to the group
26833 optionElement = addOrReuseElement(group.groupElement,
26834 group.currentOptionElement,
26837 updateOptionElement(option, optionElement);
26838 // Move to the next element
26839 group.currentOptionElement = optionElement.nextSibling;
26843 // This option is not in a group
26844 optionElement = addOrReuseElement(selectElement[0],
26848 updateOptionElement(option, optionElement);
26849 // Move to the next element
26850 currentElement = optionElement.nextSibling;
26855 // Now remove all excess options and group
26856 Object.keys(groupMap).forEach(function(key) {
26857 removeExcessElements(groupMap[key].currentOptionElement);
26859 removeExcessElements(currentElement);
26861 ngModelCtrl.$render();
26863 // Check to see if the value has changed due to the update to the options
26864 if (!ngModelCtrl.$isEmpty(previousValue)) {
26865 var nextValue = selectCtrl.readValue();
26866 if (ngOptions.trackBy ? !equals(previousValue, nextValue) : previousValue !== nextValue) {
26867 ngModelCtrl.$setViewValue(nextValue);
26868 ngModelCtrl.$render();
26878 require: ['select', '?ngModel'],
26880 pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) {
26881 // Deactivate the SelectController.register method to prevent
26882 // option directives from accidentally registering themselves
26883 // (and unwanted $destroy handlers etc.)
26884 ctrls[0].registerOption = noop;
26886 post: ngOptionsPostLink
26893 * @name ngPluralize
26897 * `ngPluralize` is a directive that displays messages according to en-US localization rules.
26898 * These rules are bundled with angular.js, but can be overridden
26899 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive
26900 * by specifying the mappings between
26901 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
26902 * and the strings to be displayed.
26904 * # Plural categories and explicit number rules
26906 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
26907 * in Angular's default en-US locale: "one" and "other".
26909 * While a plural category may match many numbers (for example, in en-US locale, "other" can match
26910 * any number that is not 1), an explicit number rule can only match one number. For example, the
26911 * explicit number rule for "3" matches the number 3. There are examples of plural categories
26912 * and explicit number rules throughout the rest of this documentation.
26914 * # Configuring ngPluralize
26915 * You configure ngPluralize by providing 2 attributes: `count` and `when`.
26916 * You can also provide an optional attribute, `offset`.
26918 * The value of the `count` attribute can be either a string or an {@link guide/expression
26919 * Angular expression}; these are evaluated on the current scope for its bound value.
26921 * The `when` attribute specifies the mappings between plural categories and the actual
26922 * string to be displayed. The value of the attribute should be a JSON object.
26924 * The following example shows how to configure ngPluralize:
26927 * <ng-pluralize count="personCount"
26928 when="{'0': 'Nobody is viewing.',
26929 * 'one': '1 person is viewing.',
26930 * 'other': '{} people are viewing.'}">
26934 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not
26935 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing"
26936 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for
26937 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can
26938 * show "a dozen people are viewing".
26940 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted
26941 * into pluralized strings. In the previous example, Angular will replace `{}` with
26942 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
26943 * for <span ng-non-bindable>{{numberExpression}}</span>.
26945 * If no rule is defined for a category, then an empty string is displayed and a warning is generated.
26946 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`.
26948 * # Configuring ngPluralize with offset
26949 * The `offset` attribute allows further customization of pluralized text, which can result in
26950 * a better user experience. For example, instead of the message "4 people are viewing this document",
26951 * you might display "John, Kate and 2 others are viewing this document".
26952 * The offset attribute allows you to offset a number by any desired value.
26953 * Let's take a look at an example:
26956 * <ng-pluralize count="personCount" offset=2
26957 * when="{'0': 'Nobody is viewing.',
26958 * '1': '{{person1}} is viewing.',
26959 * '2': '{{person1}} and {{person2}} are viewing.',
26960 * 'one': '{{person1}}, {{person2}} and one other person are viewing.',
26961 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
26965 * Notice that we are still using two plural categories(one, other), but we added
26966 * three explicit number rules 0, 1 and 2.
26967 * When one person, perhaps John, views the document, "John is viewing" will be shown.
26968 * When three people view the document, no explicit number rule is found, so
26969 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category.
26970 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing"
26973 * Note that when you specify offsets, you must provide explicit number rules for
26974 * numbers from 0 up to and including the offset. If you use an offset of 3, for example,
26975 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for
26976 * plural categories "one" and "other".
26978 * @param {string|expression} count The variable to be bound to.
26979 * @param {string} when The mapping between plural category to its corresponding strings.
26980 * @param {number=} offset Offset to deduct from the total number.
26983 <example module="pluralizeExample">
26984 <file name="index.html">
26986 angular.module('pluralizeExample', [])
26987 .controller('ExampleController', ['$scope', function($scope) {
26988 $scope.person1 = 'Igor';
26989 $scope.person2 = 'Misko';
26990 $scope.personCount = 1;
26993 <div ng-controller="ExampleController">
26994 <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/>
26995 <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/>
26996 <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/>
26998 <!--- Example with simple pluralization rules for en locale --->
27000 <ng-pluralize count="personCount"
27001 when="{'0': 'Nobody is viewing.',
27002 'one': '1 person is viewing.',
27003 'other': '{} people are viewing.'}">
27004 </ng-pluralize><br>
27006 <!--- Example with offset --->
27008 <ng-pluralize count="personCount" offset=2
27009 when="{'0': 'Nobody is viewing.',
27010 '1': '{{person1}} is viewing.',
27011 '2': '{{person1}} and {{person2}} are viewing.',
27012 'one': '{{person1}}, {{person2}} and one other person are viewing.',
27013 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
27017 <file name="protractor.js" type="protractor">
27018 it('should show correct pluralized string', function() {
27019 var withoutOffset = element.all(by.css('ng-pluralize')).get(0);
27020 var withOffset = element.all(by.css('ng-pluralize')).get(1);
27021 var countInput = element(by.model('personCount'));
27023 expect(withoutOffset.getText()).toEqual('1 person is viewing.');
27024 expect(withOffset.getText()).toEqual('Igor is viewing.');
27026 countInput.clear();
27027 countInput.sendKeys('0');
27029 expect(withoutOffset.getText()).toEqual('Nobody is viewing.');
27030 expect(withOffset.getText()).toEqual('Nobody is viewing.');
27032 countInput.clear();
27033 countInput.sendKeys('2');
27035 expect(withoutOffset.getText()).toEqual('2 people are viewing.');
27036 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.');
27038 countInput.clear();
27039 countInput.sendKeys('3');
27041 expect(withoutOffset.getText()).toEqual('3 people are viewing.');
27042 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.');
27044 countInput.clear();
27045 countInput.sendKeys('4');
27047 expect(withoutOffset.getText()).toEqual('4 people are viewing.');
27048 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.');
27050 it('should show data-bound names', function() {
27051 var withOffset = element.all(by.css('ng-pluralize')).get(1);
27052 var personCount = element(by.model('personCount'));
27053 var person1 = element(by.model('person1'));
27054 var person2 = element(by.model('person2'));
27055 personCount.clear();
27056 personCount.sendKeys('4');
27058 person1.sendKeys('Di');
27060 person2.sendKeys('Vojta');
27061 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.');
27066 var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) {
27068 IS_WHEN = /^when(Minus)?(.+)$/;
27071 link: function(scope, element, attr) {
27072 var numberExp = attr.count,
27073 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs
27074 offset = attr.offset || 0,
27075 whens = scope.$eval(whenExp) || {},
27077 startSymbol = $interpolate.startSymbol(),
27078 endSymbol = $interpolate.endSymbol(),
27079 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol,
27080 watchRemover = angular.noop,
27083 forEach(attr, function(expression, attributeName) {
27084 var tmpMatch = IS_WHEN.exec(attributeName);
27086 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]);
27087 whens[whenKey] = element.attr(attr.$attr[attributeName]);
27090 forEach(whens, function(expression, key) {
27091 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement));
27095 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) {
27096 var count = parseFloat(newVal);
27097 var countIsNaN = isNaN(count);
27099 if (!countIsNaN && !(count in whens)) {
27100 // If an explicit number rule such as 1, 2, 3... is defined, just use it.
27101 // Otherwise, check it against pluralization rules in $locale service.
27102 count = $locale.pluralCat(count - offset);
27105 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch.
27106 // In JS `NaN !== NaN`, so we have to exlicitly check.
27107 if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) {
27109 var whenExpFn = whensExpFns[count];
27110 if (isUndefined(whenExpFn)) {
27111 if (newVal != null) {
27112 $log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp);
27114 watchRemover = noop;
27115 updateElementText();
27117 watchRemover = scope.$watch(whenExpFn, updateElementText);
27123 function updateElementText(newText) {
27124 element.text(newText || '');
27136 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template
27137 * instance gets its own scope, where the given loop variable is set to the current collection item,
27138 * and `$index` is set to the item index or key.
27140 * Special properties are exposed on the local scope of each template instance, including:
27142 * | Variable | Type | Details |
27143 * |-----------|-----------------|-----------------------------------------------------------------------------|
27144 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) |
27145 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. |
27146 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. |
27147 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. |
27148 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). |
27149 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). |
27151 * <div class="alert alert-info">
27152 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
27153 * This may be useful when, for instance, nesting ngRepeats.
27157 * # Iterating over object properties
27159 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following
27163 * <div ng-repeat="(key, value) in myObj"> ... </div>
27166 * You need to be aware that the JavaScript specification does not define the order of keys
27167 * returned for an object. (To mitigate this in Angular 1.3 the `ngRepeat` directive
27168 * used to sort the keys alphabetically.)
27170 * Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser
27171 * when running `for key in myObj`. It seems that browsers generally follow the strategy of providing
27172 * keys in the order in which they were defined, although there are exceptions when keys are deleted
27173 * 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).
27175 * If this is not desired, the recommended workaround is to convert your object into an array
27176 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
27177 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter)
27178 * or implement a `$watch` on the object yourself.
27181 * # Tracking and Duplicates
27183 * `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in
27184 * the collection. When a change happens, ngRepeat then makes the corresponding changes to the DOM:
27186 * * When an item is added, a new instance of the template is added to the DOM.
27187 * * When an item is removed, its template instance is removed from the DOM.
27188 * * When items are reordered, their respective templates are reordered in the DOM.
27190 * To minimize creation of DOM elements, `ngRepeat` uses a function
27191 * to "keep track" of all items in the collection and their corresponding DOM elements.
27192 * For example, if an item is added to the collection, ngRepeat will know that all other items
27193 * already have DOM elements, and will not re-render them.
27195 * The default tracking function (which tracks items by their identity) does not allow
27196 * duplicate items in arrays. This is because when there are duplicates, it is not possible
27197 * to maintain a one-to-one mapping between collection items and DOM elements.
27199 * If you do need to repeat duplicate items, you can substitute the default tracking behavior
27200 * with your own using the `track by` expression.
27202 * For example, you may track items by the index of each item in the collection, using the
27203 * special scope property `$index`:
27205 * <div ng-repeat="n in [42, 42, 43, 43] track by $index">
27210 * You may also use arbitrary expressions in `track by`, including references to custom functions
27213 * <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)">
27218 * <div class="alert alert-success">
27219 * If you are working with objects that have an identifier property, you should track
27220 * by the identifier instead of the whole object. Should you reload your data later, `ngRepeat`
27221 * will not have to rebuild the DOM elements for items it has already rendered, even if the
27222 * JavaScript objects in the collection have been substituted for new ones. For large collections,
27223 * this signifincantly improves rendering performance. If you don't have a unique identifier,
27224 * `track by $index` can also provide a performance boost.
27227 * <div ng-repeat="model in collection track by model.id">
27232 * When no `track by` expression is provided, it is equivalent to tracking by the built-in
27233 * `$id` function, which tracks items by their identity:
27235 * <div ng-repeat="obj in collection track by $id(obj)">
27240 * <div class="alert alert-warning">
27241 * **Note:** `track by` must always be the last expression:
27244 * <div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id">
27249 * # Special repeat start and end points
27250 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
27251 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively.
27252 * 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)
27253 * up to and including the ending HTML tag where **ng-repeat-end** is placed.
27255 * The example below makes use of this feature:
27257 * <header ng-repeat-start="item in items">
27258 * Header {{ item }}
27260 * <div class="body">
27263 * <footer ng-repeat-end>
27264 * Footer {{ item }}
27268 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to:
27273 * <div class="body">
27282 * <div class="body">
27290 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such
27291 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**).
27294 * **.enter** - when a new item is added to the list or when an item is revealed after a filter
27296 * **.leave** - when an item is removed from the list or when an item is filtered out
27298 * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered
27303 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These
27304 * formats are currently supported:
27306 * * `variable in expression` – where variable is the user defined loop variable and `expression`
27307 * is a scope expression giving the collection to enumerate.
27309 * For example: `album in artist.albums`.
27311 * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers,
27312 * and `expression` is the scope expression giving the collection to enumerate.
27314 * For example: `(name, age) in {'adam':10, 'amalie':12}`.
27316 * * `variable in expression track by tracking_expression` – You can also provide an optional tracking expression
27317 * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression
27318 * is specified, ng-repeat associates elements by identity. It is an error to have
27319 * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are
27320 * mapped to the same DOM element, which is not possible.)
27322 * Note that the tracking expression must come last, after any filters, and the alias expression.
27324 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements
27325 * will be associated by item identity in the array.
27327 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique
27328 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements
27329 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM
27330 * element in the same way in the DOM.
27332 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this
27333 * case the object identity does not matter. Two objects are considered equivalent as long as their `id`
27334 * property is same.
27336 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter
27337 * to items in conjunction with a tracking expression.
27339 * * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the
27340 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message
27341 * when a filter is active on the repeater, but the filtered result set is empty.
27343 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after
27344 * the items have been processed through the filter.
27346 * 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
27347 * (and not as operator, inside an expression).
27349 * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` .
27352 * This example initializes the scope to a list of names and
27353 * then uses `ngRepeat` to display every person:
27354 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27355 <file name="index.html">
27356 <div ng-init="friends = [
27357 {name:'John', age:25, gender:'boy'},
27358 {name:'Jessie', age:30, gender:'girl'},
27359 {name:'Johanna', age:28, gender:'girl'},
27360 {name:'Joy', age:15, gender:'girl'},
27361 {name:'Mary', age:28, gender:'girl'},
27362 {name:'Peter', age:95, gender:'boy'},
27363 {name:'Sebastian', age:50, gender:'boy'},
27364 {name:'Erika', age:27, gender:'girl'},
27365 {name:'Patrick', age:40, gender:'boy'},
27366 {name:'Samantha', age:60, gender:'girl'}
27368 I have {{friends.length}} friends. They are:
27369 <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" />
27370 <ul class="example-animate-container">
27371 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results">
27372 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
27374 <li class="animate-repeat" ng-if="results.length == 0">
27375 <strong>No results found...</strong>
27380 <file name="animations.css">
27381 .example-animate-container {
27383 border:1px solid black;
27392 box-sizing:border-box;
27395 .animate-repeat.ng-move,
27396 .animate-repeat.ng-enter,
27397 .animate-repeat.ng-leave {
27398 transition:all linear 0.5s;
27401 .animate-repeat.ng-leave.ng-leave-active,
27402 .animate-repeat.ng-move,
27403 .animate-repeat.ng-enter {
27408 .animate-repeat.ng-leave,
27409 .animate-repeat.ng-move.ng-move-active,
27410 .animate-repeat.ng-enter.ng-enter-active {
27415 <file name="protractor.js" type="protractor">
27416 var friends = element.all(by.repeater('friend in friends'));
27418 it('should render initial data set', function() {
27419 expect(friends.count()).toBe(10);
27420 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.');
27421 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.');
27422 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.');
27423 expect(element(by.binding('friends.length')).getText())
27424 .toMatch("I have 10 friends. They are:");
27427 it('should update repeater when filter predicate changes', function() {
27428 expect(friends.count()).toBe(10);
27430 element(by.model('q')).sendKeys('ma');
27432 expect(friends.count()).toBe(2);
27433 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.');
27434 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.');
27439 var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
27440 var NG_REMOVED = '$$NG_REMOVED';
27441 var ngRepeatMinErr = minErr('ngRepeat');
27443 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) {
27444 // TODO(perf): generate setters to shave off ~40ms or 1-1.5%
27445 scope[valueIdentifier] = value;
27446 if (keyIdentifier) scope[keyIdentifier] = key;
27447 scope.$index = index;
27448 scope.$first = (index === 0);
27449 scope.$last = (index === (arrayLength - 1));
27450 scope.$middle = !(scope.$first || scope.$last);
27451 // jshint bitwise: false
27452 scope.$odd = !(scope.$even = (index&1) === 0);
27453 // jshint bitwise: true
27456 var getBlockStart = function(block) {
27457 return block.clone[0];
27460 var getBlockEnd = function(block) {
27461 return block.clone[block.clone.length - 1];
27467 multiElement: true,
27468 transclude: 'element',
27472 compile: function ngRepeatCompile($element, $attr) {
27473 var expression = $attr.ngRepeat;
27474 var ngRepeatEndComment = document.createComment(' end ngRepeat: ' + expression + ' ');
27476 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*$/);
27479 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.",
27483 var lhs = match[1];
27484 var rhs = match[2];
27485 var aliasAs = match[3];
27486 var trackByExp = match[4];
27488 match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);
27491 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.",
27494 var valueIdentifier = match[3] || match[1];
27495 var keyIdentifier = match[2];
27497 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) ||
27498 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) {
27499 throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.",
27503 var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn;
27504 var hashFnLocals = {$id: hashKey};
27507 trackByExpGetter = $parse(trackByExp);
27509 trackByIdArrayFn = function(key, value) {
27510 return hashKey(value);
27512 trackByIdObjFn = function(key) {
27517 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) {
27519 if (trackByExpGetter) {
27520 trackByIdExpFn = function(key, value, index) {
27521 // assign key, value, and $index to the locals so that they can be used in hash functions
27522 if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
27523 hashFnLocals[valueIdentifier] = value;
27524 hashFnLocals.$index = index;
27525 return trackByExpGetter($scope, hashFnLocals);
27529 // Store a list of elements from previous run. This is a hash where key is the item from the
27530 // iterator, and the value is objects with following properties.
27531 // - scope: bound scope
27532 // - element: previous element.
27533 // - index: position
27535 // We are using no-proto object so that we don't need to guard against inherited props via
27537 var lastBlockMap = createMap();
27540 $scope.$watchCollection(rhs, function ngRepeatAction(collection) {
27542 previousNode = $element[0], // node that cloned nodes should be inserted after
27543 // initialized to the comment node anchor
27545 // Same as lastBlockMap but it has the current state. It will become the
27546 // lastBlockMap on the next iteration.
27547 nextBlockMap = createMap(),
27549 key, value, // key/value of iteration
27553 block, // last object information {scope, element, id}
27558 $scope[aliasAs] = collection;
27561 if (isArrayLike(collection)) {
27562 collectionKeys = collection;
27563 trackByIdFn = trackByIdExpFn || trackByIdArrayFn;
27565 trackByIdFn = trackByIdExpFn || trackByIdObjFn;
27566 // if object, extract keys, in enumeration order, unsorted
27567 collectionKeys = [];
27568 for (var itemKey in collection) {
27569 if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') {
27570 collectionKeys.push(itemKey);
27575 collectionLength = collectionKeys.length;
27576 nextBlockOrder = new Array(collectionLength);
27578 // locate existing items
27579 for (index = 0; index < collectionLength; index++) {
27580 key = (collection === collectionKeys) ? index : collectionKeys[index];
27581 value = collection[key];
27582 trackById = trackByIdFn(key, value, index);
27583 if (lastBlockMap[trackById]) {
27584 // found previously seen block
27585 block = lastBlockMap[trackById];
27586 delete lastBlockMap[trackById];
27587 nextBlockMap[trackById] = block;
27588 nextBlockOrder[index] = block;
27589 } else if (nextBlockMap[trackById]) {
27590 // if collision detected. restore lastBlockMap and throw an error
27591 forEach(nextBlockOrder, function(block) {
27592 if (block && block.scope) lastBlockMap[block.id] = block;
27594 throw ngRepeatMinErr('dupes',
27595 "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}",
27596 expression, trackById, value);
27598 // new never before seen block
27599 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined};
27600 nextBlockMap[trackById] = true;
27604 // remove leftover items
27605 for (var blockKey in lastBlockMap) {
27606 block = lastBlockMap[blockKey];
27607 elementsToRemove = getBlockNodes(block.clone);
27608 $animate.leave(elementsToRemove);
27609 if (elementsToRemove[0].parentNode) {
27610 // if the element was not removed yet because of pending animation, mark it as deleted
27611 // so that we can ignore it later
27612 for (index = 0, length = elementsToRemove.length; index < length; index++) {
27613 elementsToRemove[index][NG_REMOVED] = true;
27616 block.scope.$destroy();
27619 // we are not using forEach for perf reasons (trying to avoid #call)
27620 for (index = 0; index < collectionLength; index++) {
27621 key = (collection === collectionKeys) ? index : collectionKeys[index];
27622 value = collection[key];
27623 block = nextBlockOrder[index];
27626 // if we have already seen this object, then we need to reuse the
27627 // associated scope/element
27629 nextNode = previousNode;
27631 // skip nodes that are already pending removal via leave animation
27633 nextNode = nextNode.nextSibling;
27634 } while (nextNode && nextNode[NG_REMOVED]);
27636 if (getBlockStart(block) != nextNode) {
27637 // existing item which got moved
27638 $animate.move(getBlockNodes(block.clone), null, jqLite(previousNode));
27640 previousNode = getBlockEnd(block);
27641 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
27643 // new item which we don't know about
27644 $transclude(function ngRepeatTransclude(clone, scope) {
27645 block.scope = scope;
27646 // http://jsperf.com/clone-vs-createcomment
27647 var endNode = ngRepeatEndComment.cloneNode(false);
27648 clone[clone.length++] = endNode;
27650 // TODO(perf): support naked previousNode in `enter` to avoid creation of jqLite wrapper?
27651 $animate.enter(clone, null, jqLite(previousNode));
27652 previousNode = endNode;
27653 // Note: We only need the first/last node of the cloned nodes.
27654 // However, we need to keep the reference to the jqlite wrapper as it might be changed later
27655 // by a directive with templateUrl when its template arrives.
27656 block.clone = clone;
27657 nextBlockMap[block.id] = block;
27658 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
27662 lastBlockMap = nextBlockMap;
27669 var NG_HIDE_CLASS = 'ng-hide';
27670 var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
27677 * The `ngShow` directive shows or hides the given HTML element based on the expression
27678 * provided to the `ngShow` attribute. The element is shown or hidden by removing or adding
27679 * the `.ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined
27680 * in AngularJS and sets the display style to none (using an !important flag).
27681 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
27684 * <!-- when $scope.myValue is truthy (element is visible) -->
27685 * <div ng-show="myValue"></div>
27687 * <!-- when $scope.myValue is falsy (element is hidden) -->
27688 * <div ng-show="myValue" class="ng-hide"></div>
27691 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added to the class
27692 * attribute on the element causing it to become hidden. When truthy, the `.ng-hide` CSS class is removed
27693 * from the element causing the element not to appear hidden.
27695 * ## Why is !important used?
27697 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector
27698 * can be easily overridden by heavier selectors. For example, something as simple
27699 * as changing the display style on a HTML list item would make hidden elements appear visible.
27700 * This also becomes a bigger issue when dealing with CSS frameworks.
27702 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector
27703 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the
27704 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code.
27706 * ### Overriding `.ng-hide`
27708 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
27709 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
27710 * class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope
27711 * with extra animation classes that can be added.
27714 * .ng-hide:not(.ng-hide-animate) {
27715 * /* this is just another form of hiding an element */
27716 * display: block!important;
27717 * position: absolute;
27723 * By default you don't need to override in CSS anything and the animations will work around the display style.
27725 * ## A note about animations with `ngShow`
27727 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
27728 * is true and false. This system works like the animation system present with ngClass except that
27729 * you must also include the !important flag to override the display property
27730 * so that you can perform an animation when the element is hidden during the time of the animation.
27734 * //a working example can be found at the bottom of this page
27736 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
27737 * /* this is required as of 1.3x to properly
27738 * apply all styling in a show/hide animation */
27739 * transition: 0s linear all;
27742 * .my-element.ng-hide-add-active,
27743 * .my-element.ng-hide-remove-active {
27744 * /* the transition is defined in the active class */
27745 * transition: 1s linear all;
27748 * .my-element.ng-hide-add { ... }
27749 * .my-element.ng-hide-add.ng-hide-add-active { ... }
27750 * .my-element.ng-hide-remove { ... }
27751 * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
27754 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display
27755 * property to block during animation states--ngAnimate will handle the style toggling automatically for you.
27758 * addClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a truthy value and the just before contents are set to visible
27759 * removeClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden
27762 * @param {expression} ngShow If the {@link guide/expression expression} is truthy
27763 * then the element is shown or hidden respectively.
27766 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27767 <file name="index.html">
27768 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br/>
27771 <div class="check-element animate-show" ng-show="checked">
27772 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked.
27777 <div class="check-element animate-show" ng-hide="checked">
27778 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked.
27782 <file name="glyphicons.css">
27783 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
27785 <file name="animations.css">
27790 border: 1px solid black;
27794 .animate-show.ng-hide-add, .animate-show.ng-hide-remove {
27795 transition: all linear 0.5s;
27798 .animate-show.ng-hide {
27806 border: 1px solid black;
27810 <file name="protractor.js" type="protractor">
27811 var thumbsUp = element(by.css('span.glyphicon-thumbs-up'));
27812 var thumbsDown = element(by.css('span.glyphicon-thumbs-down'));
27814 it('should check ng-show / ng-hide', function() {
27815 expect(thumbsUp.isDisplayed()).toBeFalsy();
27816 expect(thumbsDown.isDisplayed()).toBeTruthy();
27818 element(by.model('checked')).click();
27820 expect(thumbsUp.isDisplayed()).toBeTruthy();
27821 expect(thumbsDown.isDisplayed()).toBeFalsy();
27826 var ngShowDirective = ['$animate', function($animate) {
27829 multiElement: true,
27830 link: function(scope, element, attr) {
27831 scope.$watch(attr.ngShow, function ngShowWatchAction(value) {
27832 // we're adding a temporary, animation-specific class for ng-hide since this way
27833 // we can control when the element is actually displayed on screen without having
27834 // to have a global/greedy CSS selector that breaks when other animations are run.
27835 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845
27836 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, {
27837 tempClasses: NG_HIDE_IN_PROGRESS_CLASS
27851 * The `ngHide` directive shows or hides the given HTML element based on the expression
27852 * provided to the `ngHide` attribute. The element is shown or hidden by removing or adding
27853 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined
27854 * in AngularJS and sets the display style to none (using an !important flag).
27855 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
27858 * <!-- when $scope.myValue is truthy (element is hidden) -->
27859 * <div ng-hide="myValue" class="ng-hide"></div>
27861 * <!-- when $scope.myValue is falsy (element is visible) -->
27862 * <div ng-hide="myValue"></div>
27865 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added to the class
27866 * attribute on the element causing it to become hidden. When falsy, the `.ng-hide` CSS class is removed
27867 * from the element causing the element not to appear hidden.
27869 * ## Why is !important used?
27871 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector
27872 * can be easily overridden by heavier selectors. For example, something as simple
27873 * as changing the display style on a HTML list item would make hidden elements appear visible.
27874 * This also becomes a bigger issue when dealing with CSS frameworks.
27876 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector
27877 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the
27878 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code.
27880 * ### Overriding `.ng-hide`
27882 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
27883 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
27888 * /* this is just another form of hiding an element */
27889 * display: block!important;
27890 * position: absolute;
27896 * By default you don't need to override in CSS anything and the animations will work around the display style.
27898 * ## A note about animations with `ngHide`
27900 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
27901 * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide`
27902 * CSS class is added and removed for you instead of your own CSS class.
27906 * //a working example can be found at the bottom of this page
27908 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
27909 * transition: 0.5s linear all;
27912 * .my-element.ng-hide-add { ... }
27913 * .my-element.ng-hide-add.ng-hide-add-active { ... }
27914 * .my-element.ng-hide-remove { ... }
27915 * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
27918 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display
27919 * property to block during animation states--ngAnimate will handle the style toggling automatically for you.
27922 * removeClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden
27923 * addClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a non truthy value and just before the contents are set to visible
27926 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then
27927 * the element is shown or hidden respectively.
27930 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27931 <file name="index.html">
27932 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br/>
27935 <div class="check-element animate-hide" ng-show="checked">
27936 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked.
27941 <div class="check-element animate-hide" ng-hide="checked">
27942 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked.
27946 <file name="glyphicons.css">
27947 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
27949 <file name="animations.css">
27951 transition: all linear 0.5s;
27955 border: 1px solid black;
27959 .animate-hide.ng-hide {
27967 border: 1px solid black;
27971 <file name="protractor.js" type="protractor">
27972 var thumbsUp = element(by.css('span.glyphicon-thumbs-up'));
27973 var thumbsDown = element(by.css('span.glyphicon-thumbs-down'));
27975 it('should check ng-show / ng-hide', function() {
27976 expect(thumbsUp.isDisplayed()).toBeFalsy();
27977 expect(thumbsDown.isDisplayed()).toBeTruthy();
27979 element(by.model('checked')).click();
27981 expect(thumbsUp.isDisplayed()).toBeTruthy();
27982 expect(thumbsDown.isDisplayed()).toBeFalsy();
27987 var ngHideDirective = ['$animate', function($animate) {
27990 multiElement: true,
27991 link: function(scope, element, attr) {
27992 scope.$watch(attr.ngHide, function ngHideWatchAction(value) {
27993 // The comment inside of the ngShowDirective explains why we add and
27994 // remove a temporary class for the show/hide animation
27995 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, {
27996 tempClasses: NG_HIDE_IN_PROGRESS_CLASS
28009 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally.
28012 * @param {expression} ngStyle
28014 * {@link guide/expression Expression} which evals to an
28015 * object whose keys are CSS style names and values are corresponding values for those CSS
28018 * Since some CSS style names are not valid keys for an object, they must be quoted.
28019 * See the 'background-color' style in the example below.
28023 <file name="index.html">
28024 <input type="button" value="set color" ng-click="myStyle={color:'red'}">
28025 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}">
28026 <input type="button" value="clear" ng-click="myStyle={}">
28028 <span ng-style="myStyle">Sample Text</span>
28029 <pre>myStyle={{myStyle}}</pre>
28031 <file name="style.css">
28036 <file name="protractor.js" type="protractor">
28037 var colorSpan = element(by.css('span'));
28039 it('should check ng-style', function() {
28040 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
28041 element(by.css('input[value=\'set color\']')).click();
28042 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)');
28043 element(by.css('input[value=clear]')).click();
28044 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
28049 var ngStyleDirective = ngDirective(function(scope, element, attr) {
28050 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
28051 if (oldStyles && (newStyles !== oldStyles)) {
28052 forEach(oldStyles, function(val, style) { element.css(style, '');});
28054 if (newStyles) element.css(newStyles);
28064 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression.
28065 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location
28066 * as specified in the template.
28068 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it
28069 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element
28070 * matches the value obtained from the evaluated expression. In other words, you define a container element
28071 * (where you place the directive), place an expression on the **`on="..."` attribute**
28072 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place
28073 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on
28074 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default
28075 * attribute is displayed.
28077 * <div class="alert alert-info">
28078 * Be aware that the attribute values to match against cannot be expressions. They are interpreted
28079 * as literal string values to match against.
28080 * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the
28081 * value of the expression `$scope.someVal`.
28085 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container
28086 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM
28091 * <ANY ng-switch="expression">
28092 * <ANY ng-switch-when="matchValue1">...</ANY>
28093 * <ANY ng-switch-when="matchValue2">...</ANY>
28094 * <ANY ng-switch-default>...</ANY>
28101 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>.
28102 * On child elements add:
28104 * * `ngSwitchWhen`: the case statement to match against. If match then this
28105 * case will be displayed. If the same match appears multiple times, all the
28106 * elements will be displayed.
28107 * * `ngSwitchDefault`: the default case when no other case match. If there
28108 * are multiple default cases, all of them will be displayed when no other
28113 <example module="switchExample" deps="angular-animate.js" animations="true">
28114 <file name="index.html">
28115 <div ng-controller="ExampleController">
28116 <select ng-model="selection" ng-options="item for item in items">
28118 <code>selection={{selection}}</code>
28120 <div class="animate-switch-container"
28121 ng-switch on="selection">
28122 <div class="animate-switch" ng-switch-when="settings">Settings Div</div>
28123 <div class="animate-switch" ng-switch-when="home">Home Span</div>
28124 <div class="animate-switch" ng-switch-default>default</div>
28128 <file name="script.js">
28129 angular.module('switchExample', ['ngAnimate'])
28130 .controller('ExampleController', ['$scope', function($scope) {
28131 $scope.items = ['settings', 'home', 'other'];
28132 $scope.selection = $scope.items[0];
28135 <file name="animations.css">
28136 .animate-switch-container {
28139 border:1px solid black;
28148 .animate-switch.ng-animate {
28149 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
28158 .animate-switch.ng-leave.ng-leave-active,
28159 .animate-switch.ng-enter {
28162 .animate-switch.ng-leave,
28163 .animate-switch.ng-enter.ng-enter-active {
28167 <file name="protractor.js" type="protractor">
28168 var switchElem = element(by.css('[ng-switch]'));
28169 var select = element(by.model('selection'));
28171 it('should start in settings', function() {
28172 expect(switchElem.getText()).toMatch(/Settings Div/);
28174 it('should change to home', function() {
28175 select.all(by.css('option')).get(1).click();
28176 expect(switchElem.getText()).toMatch(/Home Span/);
28178 it('should select default', function() {
28179 select.all(by.css('option')).get(2).click();
28180 expect(switchElem.getText()).toMatch(/default/);
28185 var ngSwitchDirective = ['$animate', function($animate) {
28187 require: 'ngSwitch',
28189 // asks for $scope to fool the BC controller module
28190 controller: ['$scope', function ngSwitchController() {
28193 link: function(scope, element, attr, ngSwitchController) {
28194 var watchExpr = attr.ngSwitch || attr.on,
28195 selectedTranscludes = [],
28196 selectedElements = [],
28197 previousLeaveAnimations = [],
28198 selectedScopes = [];
28200 var spliceFactory = function(array, index) {
28201 return function() { array.splice(index, 1); };
28204 scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
28206 for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) {
28207 $animate.cancel(previousLeaveAnimations[i]);
28209 previousLeaveAnimations.length = 0;
28211 for (i = 0, ii = selectedScopes.length; i < ii; ++i) {
28212 var selected = getBlockNodes(selectedElements[i].clone);
28213 selectedScopes[i].$destroy();
28214 var promise = previousLeaveAnimations[i] = $animate.leave(selected);
28215 promise.then(spliceFactory(previousLeaveAnimations, i));
28218 selectedElements.length = 0;
28219 selectedScopes.length = 0;
28221 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) {
28222 forEach(selectedTranscludes, function(selectedTransclude) {
28223 selectedTransclude.transclude(function(caseElement, selectedScope) {
28224 selectedScopes.push(selectedScope);
28225 var anchor = selectedTransclude.element;
28226 caseElement[caseElement.length++] = document.createComment(' end ngSwitchWhen: ');
28227 var block = { clone: caseElement };
28229 selectedElements.push(block);
28230 $animate.enter(caseElement, anchor.parent(), anchor);
28239 var ngSwitchWhenDirective = ngDirective({
28240 transclude: 'element',
28242 require: '^ngSwitch',
28243 multiElement: true,
28244 link: function(scope, element, attrs, ctrl, $transclude) {
28245 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
28246 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element });
28250 var ngSwitchDefaultDirective = ngDirective({
28251 transclude: 'element',
28253 require: '^ngSwitch',
28254 multiElement: true,
28255 link: function(scope, element, attr, ctrl, $transclude) {
28256 ctrl.cases['?'] = (ctrl.cases['?'] || []);
28257 ctrl.cases['?'].push({ transclude: $transclude, element: element });
28263 * @name ngTransclude
28267 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.
28269 * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted.
28274 <example module="transcludeExample">
28275 <file name="index.html">
28277 angular.module('transcludeExample', [])
28278 .directive('pane', function(){
28282 scope: { title:'@' },
28283 template: '<div style="border: 1px solid black;">' +
28284 '<div style="background-color: gray">{{title}}</div>' +
28285 '<ng-transclude></ng-transclude>' +
28289 .controller('ExampleController', ['$scope', function($scope) {
28290 $scope.title = 'Lorem Ipsum';
28291 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
28294 <div ng-controller="ExampleController">
28295 <input ng-model="title" aria-label="title"> <br/>
28296 <textarea ng-model="text" aria-label="text"></textarea> <br/>
28297 <pane title="{{title}}">{{text}}</pane>
28300 <file name="protractor.js" type="protractor">
28301 it('should have transcluded', function() {
28302 var titleElement = element(by.model('title'));
28303 titleElement.clear();
28304 titleElement.sendKeys('TITLE');
28305 var textElement = element(by.model('text'));
28306 textElement.clear();
28307 textElement.sendKeys('TEXT');
28308 expect(element(by.binding('title')).getText()).toEqual('TITLE');
28309 expect(element(by.binding('text')).getText()).toEqual('TEXT');
28315 var ngTranscludeDirective = ngDirective({
28317 link: function($scope, $element, $attrs, controller, $transclude) {
28318 if (!$transclude) {
28319 throw minErr('ngTransclude')('orphan',
28320 'Illegal use of ngTransclude directive in the template! ' +
28321 'No parent directive that requires a transclusion found. ' +
28323 startingTag($element));
28326 $transclude(function(clone) {
28328 $element.append(clone);
28339 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the
28340 * template can be used by {@link ng.directive:ngInclude `ngInclude`},
28341 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the
28342 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be
28343 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`.
28345 * @param {string} type Must be set to `'text/ng-template'`.
28346 * @param {string} id Cache name of the template.
28350 <file name="index.html">
28351 <script type="text/ng-template" id="/tpl.html">
28352 Content of the template.
28355 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a>
28356 <div id="tpl-content" ng-include src="currentTpl"></div>
28358 <file name="protractor.js" type="protractor">
28359 it('should load template defined inside script tag', function() {
28360 element(by.css('#tpl-link')).click();
28361 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/);
28366 var scriptDirective = ['$templateCache', function($templateCache) {
28370 compile: function(element, attr) {
28371 if (attr.type == 'text/ng-template') {
28372 var templateUrl = attr.id,
28373 text = element[0].text;
28375 $templateCache.put(templateUrl, text);
28381 var noopNgModelController = { $setViewValue: noop, $render: noop };
28383 function chromeHack(optionElement) {
28384 // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459
28385 // Adding an <option selected="selected"> element to a <select required="required"> should
28386 // automatically select the new element
28387 if (optionElement[0].hasAttribute('selected')) {
28388 optionElement[0].selected = true;
28394 * @name select.SelectController
28396 * The controller for the `<select>` directive. This provides support for reading
28397 * and writing the selected value(s) of the control and also coordinates dynamically
28398 * added `<option>` elements, perhaps by an `ngRepeat` directive.
28400 var SelectController =
28401 ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
28404 optionsMap = new HashMap();
28406 // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors
28407 self.ngModelCtrl = noopNgModelController;
28409 // The "unknown" option is one that is prepended to the list if the viewValue
28410 // does not match any of the options. When it is rendered the value of the unknown
28411 // option is '? XXX ?' where XXX is the hashKey of the value that is not known.
28413 // We can't just jqLite('<option>') since jqLite is not smart enough
28414 // to create it in <select> and IE barfs otherwise.
28415 self.unknownOption = jqLite(document.createElement('option'));
28416 self.renderUnknownOption = function(val) {
28417 var unknownVal = '? ' + hashKey(val) + ' ?';
28418 self.unknownOption.val(unknownVal);
28419 $element.prepend(self.unknownOption);
28420 $element.val(unknownVal);
28423 $scope.$on('$destroy', function() {
28424 // disable unknown option so that we don't do work when the whole select is being destroyed
28425 self.renderUnknownOption = noop;
28428 self.removeUnknownOption = function() {
28429 if (self.unknownOption.parent()) self.unknownOption.remove();
28433 // Read the value of the select control, the implementation of this changes depending
28434 // upon whether the select can have multiple values and whether ngOptions is at work.
28435 self.readValue = function readSingleValue() {
28436 self.removeUnknownOption();
28437 return $element.val();
28441 // Write the value to the select control, the implementation of this changes depending
28442 // upon whether the select can have multiple values and whether ngOptions is at work.
28443 self.writeValue = function writeSingleValue(value) {
28444 if (self.hasOption(value)) {
28445 self.removeUnknownOption();
28446 $element.val(value);
28447 if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy
28449 if (value == null && self.emptyOption) {
28450 self.removeUnknownOption();
28453 self.renderUnknownOption(value);
28459 // Tell the select control that an option, with the given value, has been added
28460 self.addOption = function(value, element) {
28461 assertNotHasOwnProperty(value, '"option value"');
28462 if (value === '') {
28463 self.emptyOption = element;
28465 var count = optionsMap.get(value) || 0;
28466 optionsMap.put(value, count + 1);
28467 self.ngModelCtrl.$render();
28468 chromeHack(element);
28471 // Tell the select control that an option, with the given value, has been removed
28472 self.removeOption = function(value) {
28473 var count = optionsMap.get(value);
28476 optionsMap.remove(value);
28477 if (value === '') {
28478 self.emptyOption = undefined;
28481 optionsMap.put(value, count - 1);
28486 // Check whether the select control has an option matching the given value
28487 self.hasOption = function(value) {
28488 return !!optionsMap.get(value);
28492 self.registerOption = function(optionScope, optionElement, optionAttrs, interpolateValueFn, interpolateTextFn) {
28494 if (interpolateValueFn) {
28495 // The value attribute is interpolated
28497 optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {
28498 if (isDefined(oldVal)) {
28499 self.removeOption(oldVal);
28502 self.addOption(newVal, optionElement);
28504 } else if (interpolateTextFn) {
28505 // The text content is interpolated
28506 optionScope.$watch(interpolateTextFn, function interpolateWatchAction(newVal, oldVal) {
28507 optionAttrs.$set('value', newVal);
28508 if (oldVal !== newVal) {
28509 self.removeOption(oldVal);
28511 self.addOption(newVal, optionElement);
28514 // The value attribute is static
28515 self.addOption(optionAttrs.value, optionElement);
28518 optionElement.on('$destroy', function() {
28519 self.removeOption(optionAttrs.value);
28520 self.ngModelCtrl.$render();
28531 * HTML `SELECT` element with angular data-binding.
28533 * The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding
28534 * between the scope and the `<select>` control (including setting default values).
28535 * Ìt also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or
28536 * {@link ngOptions `ngOptions`} directives.
28538 * When an item in the `<select>` menu is selected, the value of the selected option will be bound
28539 * to the model identified by the `ngModel` directive. With static or repeated options, this is
28540 * the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing.
28541 * If you want dynamic value attributes, you can use interpolation inside the value attribute.
28543 * <div class="alert alert-warning">
28544 * Note that the value of a `select` directive used without `ngOptions` is always a string.
28545 * When the model needs to be bound to a non-string value, you must either explictly convert it
28546 * using a directive (see example below) or use `ngOptions` to specify the set of options.
28547 * This is because an option element can only be bound to string values at present.
28550 * If the viewValue of `ngModel` does not match any of the options, then the control
28551 * will automatically add an "unknown" option, which it then removes when the mismatch is resolved.
28553 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
28554 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
28555 * option. See example below for demonstration.
28557 * <div class="alert alert-info">
28558 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions
28559 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits, such as
28560 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
28561 * comprehension expression, and additionally in reducing memory and increasing speed by not creating
28562 * a new scope for each repeated instance.
28566 * @param {string} ngModel Assignable angular expression to data-bind to.
28567 * @param {string=} name Property name of the form under which the control is published.
28568 * @param {string=} multiple Allows multiple options to be selected. The selected values will be
28569 * bound to the model as an array.
28570 * @param {string=} required Sets `required` validation error key if the value is not entered.
28571 * @param {string=} ngRequired Adds required attribute and required validation constraint to
28572 * the element when the ngRequired expression evaluates to true. Use ngRequired instead of required
28573 * when you want to data-bind to the required attribute.
28574 * @param {string=} ngChange Angular expression to be executed when selected option(s) changes due to user
28575 * interaction with the select element.
28576 * @param {string=} ngOptions sets the options that the select is populated with and defines what is
28577 * set on the model on selection. See {@link ngOptions `ngOptions`}.
28580 * ### Simple `select` elements with static options
28582 * <example name="static-select" module="staticSelect">
28583 * <file name="index.html">
28584 * <div ng-controller="ExampleController">
28585 * <form name="myForm">
28586 * <label for="singleSelect"> Single select: </label><br>
28587 * <select name="singleSelect" ng-model="data.singleSelect">
28588 * <option value="option-1">Option 1</option>
28589 * <option value="option-2">Option 2</option>
28592 * <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br>
28593 * <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect">
28594 * <option value="">---Please select---</option> <!-- not selected / blank option -->
28595 * <option value="{{data.option1}}">Option 1</option> <!-- interpolation -->
28596 * <option value="option-2">Option 2</option>
28598 * <button ng-click="forceUnknownOption()">Force unknown option</button><br>
28599 * <tt>singleSelect = {{data.singleSelect}}</tt>
28602 * <label for="multipleSelect"> Multiple select: </label><br>
28603 * <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple>
28604 * <option value="option-1">Option 1</option>
28605 * <option value="option-2">Option 2</option>
28606 * <option value="option-3">Option 3</option>
28608 * <tt>multipleSelect = {{data.multipleSelect}}</tt><br/>
28612 * <file name="app.js">
28613 * angular.module('staticSelect', [])
28614 * .controller('ExampleController', ['$scope', function($scope) {
28616 * singleSelect: null,
28617 * multipleSelect: [],
28618 * option1: 'option-1',
28621 * $scope.forceUnknownOption = function() {
28622 * $scope.data.singleSelect = 'nonsense';
28628 * ### Using `ngRepeat` to generate `select` options
28629 * <example name="ngrepeat-select" module="ngrepeatSelect">
28630 * <file name="index.html">
28631 * <div ng-controller="ExampleController">
28632 * <form name="myForm">
28633 * <label for="repeatSelect"> Repeat select: </label>
28634 * <select name="repeatSelect" id="repeatSelect" ng-model="data.repeatSelect">
28635 * <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option>
28639 * <tt>repeatSelect = {{data.repeatSelect}}</tt><br/>
28642 * <file name="app.js">
28643 * angular.module('ngrepeatSelect', [])
28644 * .controller('ExampleController', ['$scope', function($scope) {
28646 * repeatSelect: null,
28647 * availableOptions: [
28648 * {id: '1', name: 'Option A'},
28649 * {id: '2', name: 'Option B'},
28650 * {id: '3', name: 'Option C'}
28658 * ### Using `select` with `ngOptions` and setting a default value
28659 * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples.
28661 * <example name="select-with-default-values" module="defaultValueSelect">
28662 * <file name="index.html">
28663 * <div ng-controller="ExampleController">
28664 * <form name="myForm">
28665 * <label for="mySelect">Make a choice:</label>
28666 * <select name="mySelect" id="mySelect"
28667 * ng-options="option.name for option in data.availableOptions track by option.id"
28668 * ng-model="data.selectedOption"></select>
28671 * <tt>option = {{data.selectedOption}}</tt><br/>
28674 * <file name="app.js">
28675 * angular.module('defaultValueSelect', [])
28676 * .controller('ExampleController', ['$scope', function($scope) {
28678 * availableOptions: [
28679 * {id: '1', name: 'Option A'},
28680 * {id: '2', name: 'Option B'},
28681 * {id: '3', name: 'Option C'}
28683 * selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui
28690 * ### Binding `select` to a non-string value via `ngModel` parsing / formatting
28692 * <example name="select-with-non-string-options" module="nonStringSelect">
28693 * <file name="index.html">
28694 * <select ng-model="model.id" convert-to-number>
28695 * <option value="0">Zero</option>
28696 * <option value="1">One</option>
28697 * <option value="2">Two</option>
28701 * <file name="app.js">
28702 * angular.module('nonStringSelect', [])
28703 * .run(function($rootScope) {
28704 * $rootScope.model = { id: 2 };
28706 * .directive('convertToNumber', function() {
28708 * require: 'ngModel',
28709 * link: function(scope, element, attrs, ngModel) {
28710 * ngModel.$parsers.push(function(val) {
28711 * return parseInt(val, 10);
28713 * ngModel.$formatters.push(function(val) {
28720 * <file name="protractor.js" type="protractor">
28721 * it('should initialize to model', function() {
28722 * var select = element(by.css('select'));
28723 * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two');
28729 var selectDirective = function() {
28733 require: ['select', '?ngModel'],
28734 controller: SelectController,
28741 function selectPreLink(scope, element, attr, ctrls) {
28743 // if ngModel is not defined, we don't need to do anything
28744 var ngModelCtrl = ctrls[1];
28745 if (!ngModelCtrl) return;
28747 var selectCtrl = ctrls[0];
28749 selectCtrl.ngModelCtrl = ngModelCtrl;
28751 // We delegate rendering to the `writeValue` method, which can be changed
28752 // if the select can have multiple selected values or if the options are being
28753 // generated by `ngOptions`
28754 ngModelCtrl.$render = function() {
28755 selectCtrl.writeValue(ngModelCtrl.$viewValue);
28758 // When the selected item(s) changes we delegate getting the value of the select control
28759 // to the `readValue` method, which can be changed if the select can have multiple
28760 // selected values or if the options are being generated by `ngOptions`
28761 element.on('change', function() {
28762 scope.$apply(function() {
28763 ngModelCtrl.$setViewValue(selectCtrl.readValue());
28767 // If the select allows multiple values then we need to modify how we read and write
28768 // values from and to the control; also what it means for the value to be empty and
28769 // we have to add an extra watch since ngModel doesn't work well with arrays - it
28770 // doesn't trigger rendering if only an item in the array changes.
28771 if (attr.multiple) {
28773 // Read value now needs to check each option to see if it is selected
28774 selectCtrl.readValue = function readMultipleValue() {
28776 forEach(element.find('option'), function(option) {
28777 if (option.selected) {
28778 array.push(option.value);
28784 // Write value now needs to set the selected property of each matching option
28785 selectCtrl.writeValue = function writeMultipleValue(value) {
28786 var items = new HashMap(value);
28787 forEach(element.find('option'), function(option) {
28788 option.selected = isDefined(items.get(option.value));
28792 // we have to do it on each watch since ngModel watches reference, but
28793 // we need to work of an array, so we need to see if anything was inserted/removed
28794 var lastView, lastViewRef = NaN;
28795 scope.$watch(function selectMultipleWatch() {
28796 if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) {
28797 lastView = shallowCopy(ngModelCtrl.$viewValue);
28798 ngModelCtrl.$render();
28800 lastViewRef = ngModelCtrl.$viewValue;
28803 // If we are a multiple select then value is now a collection
28804 // so the meaning of $isEmpty changes
28805 ngModelCtrl.$isEmpty = function(value) {
28806 return !value || value.length === 0;
28814 // The option directive is purely designed to communicate the existence (or lack of)
28815 // of dynamically created (and destroyed) option elements to their containing select
28816 // directive via its controller.
28817 var optionDirective = ['$interpolate', function($interpolate) {
28821 compile: function(element, attr) {
28823 if (isDefined(attr.value)) {
28824 // If the value attribute is defined, check if it contains an interpolation
28825 var interpolateValueFn = $interpolate(attr.value, true);
28827 // If the value attribute is not defined then we fall back to the
28828 // text content of the option element, which may be interpolated
28829 var interpolateTextFn = $interpolate(element.text(), true);
28830 if (!interpolateTextFn) {
28831 attr.$set('value', element.text());
28835 return function(scope, element, attr) {
28837 // This is an optimization over using ^^ since we don't want to have to search
28838 // all the way to the root of the DOM for every single option element
28839 var selectCtrlName = '$selectController',
28840 parent = element.parent(),
28841 selectCtrl = parent.data(selectCtrlName) ||
28842 parent.parent().data(selectCtrlName); // in case we are in optgroup
28845 selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn);
28852 var styleDirective = valueFn({
28857 var requiredDirective = function() {
28860 require: '?ngModel',
28861 link: function(scope, elm, attr, ctrl) {
28863 attr.required = true; // force truthy in case we are on non input element
28865 ctrl.$validators.required = function(modelValue, viewValue) {
28866 return !attr.required || !ctrl.$isEmpty(viewValue);
28869 attr.$observe('required', function() {
28877 var patternDirective = function() {
28880 require: '?ngModel',
28881 link: function(scope, elm, attr, ctrl) {
28884 var regexp, patternExp = attr.ngPattern || attr.pattern;
28885 attr.$observe('pattern', function(regex) {
28886 if (isString(regex) && regex.length > 0) {
28887 regex = new RegExp('^' + regex + '$');
28890 if (regex && !regex.test) {
28891 throw minErr('ngPattern')('noregexp',
28892 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
28893 regex, startingTag(elm));
28896 regexp = regex || undefined;
28900 ctrl.$validators.pattern = function(modelValue, viewValue) {
28901 // HTML5 pattern constraint validates the input value, so we validate the viewValue
28902 return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue);
28909 var maxlengthDirective = function() {
28912 require: '?ngModel',
28913 link: function(scope, elm, attr, ctrl) {
28916 var maxlength = -1;
28917 attr.$observe('maxlength', function(value) {
28918 var intVal = toInt(value);
28919 maxlength = isNaN(intVal) ? -1 : intVal;
28922 ctrl.$validators.maxlength = function(modelValue, viewValue) {
28923 return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength);
28929 var minlengthDirective = function() {
28932 require: '?ngModel',
28933 link: function(scope, elm, attr, ctrl) {
28937 attr.$observe('minlength', function(value) {
28938 minlength = toInt(value) || 0;
28941 ctrl.$validators.minlength = function(modelValue, viewValue) {
28942 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
28948 if (window.angular.bootstrap) {
28949 //AngularJS is already loaded, so we can return here...
28950 console.log('WARNING: Tried to load angular more than once.');
28954 //try to bind to jquery now so that one can write jqLite(document).ready()
28955 //but we will rebind on bootstrap again.
28958 publishExternalAPI(angular);
28960 angular.module("ngLocale", [], ["$provide", function($provide) {
28961 var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"};
28962 function getDecimals(n) {
28964 var i = n.indexOf('.');
28965 return (i == -1) ? 0 : n.length - i - 1;
28968 function getVF(n, opt_precision) {
28969 var v = opt_precision;
28971 if (undefined === v) {
28972 v = Math.min(getDecimals(n), 3);
28975 var base = Math.pow(10, v);
28976 var f = ((n * base) | 0) % base;
28977 return {v: v, f: f};
28980 $provide.value("$locale", {
28981 "DATETIME_FORMATS": {
29003 "FIRSTDAYOFWEEK": 6,
29045 "fullDate": "EEEE, MMMM d, y",
29046 "longDate": "MMMM d, y",
29047 "medium": "MMM d, y h:mm:ss a",
29048 "mediumDate": "MMM d, y",
29049 "mediumTime": "h:mm:ss a",
29050 "short": "M/d/yy h:mm a",
29051 "shortDate": "M/d/yy",
29052 "shortTime": "h:mm a"
29054 "NUMBER_FORMATS": {
29055 "CURRENCY_SYM": "$",
29056 "DECIMAL_SEP": ".",
29076 "negPre": "-\u00a4",
29078 "posPre": "\u00a4",
29084 "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;}
29088 jqLite(document).ready(function() {
29089 angularInit(document, bootstrap);
29092 })(window, document);
29094 !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>');
29098 /***/ function(module, exports) {
29101 * State-based routing for AngularJS
29103 * @link http://angular-ui.github.com/
29104 * @license MIT License, http://www.opensource.org/licenses/MIT
29107 /* commonjs package manager support (eg componentjs) */
29108 if (typeof module !== "undefined" && typeof exports !== "undefined" && module.exports === exports){
29109 module.exports = 'ui.router';
29112 (function (window, angular, undefined) {
29113 /*jshint globalstrict:true*/
29114 /*global angular:false*/
29117 var isDefined = angular.isDefined,
29118 isFunction = angular.isFunction,
29119 isString = angular.isString,
29120 isObject = angular.isObject,
29121 isArray = angular.isArray,
29122 forEach = angular.forEach,
29123 extend = angular.extend,
29124 copy = angular.copy;
29126 function inherit(parent, extra) {
29127 return extend(new (extend(function() {}, { prototype: parent }))(), extra);
29130 function merge(dst) {
29131 forEach(arguments, function(obj) {
29133 forEach(obj, function(value, key) {
29134 if (!dst.hasOwnProperty(key)) dst[key] = value;
29142 * Finds the common ancestor path between two states.
29144 * @param {Object} first The first state.
29145 * @param {Object} second The second state.
29146 * @return {Array} Returns an array of state names in descending order, not including the root.
29148 function ancestors(first, second) {
29151 for (var n in first.path) {
29152 if (first.path[n] !== second.path[n]) break;
29153 path.push(first.path[n]);
29159 * IE8-safe wrapper for `Object.keys()`.
29161 * @param {Object} object A JavaScript object.
29162 * @return {Array} Returns the keys of the object as an array.
29164 function objectKeys(object) {
29166 return Object.keys(object);
29170 forEach(object, function(val, key) {
29177 * IE8-safe wrapper for `Array.prototype.indexOf()`.
29179 * @param {Array} array A JavaScript array.
29180 * @param {*} value A value to search the array for.
29181 * @return {Number} Returns the array index value of `value`, or `-1` if not present.
29183 function indexOf(array, value) {
29184 if (Array.prototype.indexOf) {
29185 return array.indexOf(value, Number(arguments[2]) || 0);
29187 var len = array.length >>> 0, from = Number(arguments[2]) || 0;
29188 from = (from < 0) ? Math.ceil(from) : Math.floor(from);
29190 if (from < 0) from += len;
29192 for (; from < len; from++) {
29193 if (from in array && array[from] === value) return from;
29199 * Merges a set of parameters with all parameters inherited between the common parents of the
29200 * current state and a given destination state.
29202 * @param {Object} currentParams The value of the current state parameters ($stateParams).
29203 * @param {Object} newParams The set of parameters which will be composited with inherited params.
29204 * @param {Object} $current Internal definition of object representing the current state.
29205 * @param {Object} $to Internal definition of object representing state to transition to.
29207 function inheritParams(currentParams, newParams, $current, $to) {
29208 var parents = ancestors($current, $to), parentParams, inherited = {}, inheritList = [];
29210 for (var i in parents) {
29211 if (!parents[i].params) continue;
29212 parentParams = objectKeys(parents[i].params);
29213 if (!parentParams.length) continue;
29215 for (var j in parentParams) {
29216 if (indexOf(inheritList, parentParams[j]) >= 0) continue;
29217 inheritList.push(parentParams[j]);
29218 inherited[parentParams[j]] = currentParams[parentParams[j]];
29221 return extend({}, inherited, newParams);
29225 * Performs a non-strict comparison of the subset of two objects, defined by a list of keys.
29227 * @param {Object} a The first object.
29228 * @param {Object} b The second object.
29229 * @param {Array} keys The list of keys within each object to compare. If the list is empty or not specified,
29230 * it defaults to the list of keys in `a`.
29231 * @return {Boolean} Returns `true` if the keys match, otherwise `false`.
29233 function equalForKeys(a, b, keys) {
29236 for (var n in a) keys.push(n); // Used instead of Object.keys() for IE8 compatibility
29239 for (var i=0; i<keys.length; i++) {
29241 if (a[k] != b[k]) return false; // Not '===', values aren't necessarily normalized
29247 * Returns the subset of an object, based on a list of keys.
29249 * @param {Array} keys
29250 * @param {Object} values
29251 * @return {Boolean} Returns a subset of `values`.
29253 function filterByKeys(keys, values) {
29256 forEach(keys, function (name) {
29257 filtered[name] = values[name];
29263 // when you know that your index values will be unique, or you want last-one-in to win
29264 function indexBy(array, propName) {
29266 forEach(array, function(item) {
29267 result[item[propName]] = item;
29272 // extracted from underscore.js
29273 // Return a copy of the object only containing the whitelisted properties.
29274 function pick(obj) {
29276 var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1));
29277 forEach(keys, function(key) {
29278 if (key in obj) copy[key] = obj[key];
29283 // extracted from underscore.js
29284 // Return a copy of the object omitting the blacklisted properties.
29285 function omit(obj) {
29287 var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1));
29288 for (var key in obj) {
29289 if (indexOf(keys, key) == -1) copy[key] = obj[key];
29294 function pluck(collection, key) {
29295 var result = isArray(collection) ? [] : {};
29297 forEach(collection, function(val, i) {
29298 result[i] = isFunction(key) ? key(val) : val[key];
29303 function filter(collection, callback) {
29304 var array = isArray(collection);
29305 var result = array ? [] : {};
29306 forEach(collection, function(val, i) {
29307 if (callback(val, i)) {
29308 result[array ? result.length : i] = val;
29314 function map(collection, callback) {
29315 var result = isArray(collection) ? [] : {};
29317 forEach(collection, function(val, i) {
29318 result[i] = callback(val, i);
29325 * @name ui.router.util
29328 * # ui.router.util sub-module
29330 * This module is a dependency of other sub-modules. Do not include this module as a dependency
29331 * in your angular app (use {@link ui.router} module instead).
29334 angular.module('ui.router.util', ['ng']);
29338 * @name ui.router.router
29340 * @requires ui.router.util
29343 * # ui.router.router sub-module
29345 * This module is a dependency of other sub-modules. Do not include this module as a dependency
29346 * in your angular app (use {@link ui.router} module instead).
29348 angular.module('ui.router.router', ['ui.router.util']);
29352 * @name ui.router.state
29354 * @requires ui.router.router
29355 * @requires ui.router.util
29358 * # ui.router.state sub-module
29360 * This module is a dependency of the main ui.router module. Do not include this module as a dependency
29361 * in your angular app (use {@link ui.router} module instead).
29364 angular.module('ui.router.state', ['ui.router.router', 'ui.router.util']);
29370 * @requires ui.router.state
29375 * ## The main module for ui.router
29376 * There are several sub-modules included with the ui.router module, however only this module is needed
29377 * as a dependency within your angular app. The other modules are for organization purposes.
29380 * * ui.router - the main "umbrella" module
29381 * * ui.router.router -
29383 * *You'll need to include **only** this module as the dependency within your angular app.*
29387 * <html ng-app="myApp">
29389 * <script src="js/angular.js"></script>
29390 * <!-- Include the ui-router script -->
29391 * <script src="js/angular-ui-router.min.js"></script>
29393 * // ...and add 'ui.router' as a dependency
29394 * var myApp = angular.module('myApp', ['ui.router']);
29402 angular.module('ui.router', ['ui.router.state']);
29404 angular.module('ui.router.compat', ['ui.router']);
29408 * @name ui.router.util.$resolve
29411 * @requires $injector
29414 * Manages resolution of (acyclic) graphs of promises.
29416 $Resolve.$inject = ['$q', '$injector'];
29417 function $Resolve( $q, $injector) {
29419 var VISIT_IN_PROGRESS = 1,
29422 NO_DEPENDENCIES = [],
29423 NO_LOCALS = NOTHING,
29424 NO_PARENT = extend($q.when(NOTHING), { $$promises: NOTHING, $$values: NOTHING });
29429 * @name ui.router.util.$resolve#study
29430 * @methodOf ui.router.util.$resolve
29433 * Studies a set of invocables that are likely to be used multiple times.
29435 * $resolve.study(invocables)(locals, parent, self)
29439 * $resolve.resolve(invocables, locals, parent, self)
29441 * but the former is more efficient (in fact `resolve` just calls `study`
29444 * @param {object} invocables Invocable objects
29445 * @return {function} a function to pass in locals, parent and self
29447 this.study = function (invocables) {
29448 if (!isObject(invocables)) throw new Error("'invocables' must be an object");
29449 var invocableKeys = objectKeys(invocables || {});
29451 // Perform a topological sort of invocables to build an ordered plan
29452 var plan = [], cycle = [], visited = {};
29453 function visit(value, key) {
29454 if (visited[key] === VISIT_DONE) return;
29457 if (visited[key] === VISIT_IN_PROGRESS) {
29458 cycle.splice(0, indexOf(cycle, key));
29459 throw new Error("Cyclic dependency: " + cycle.join(" -> "));
29461 visited[key] = VISIT_IN_PROGRESS;
29463 if (isString(value)) {
29464 plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES);
29466 var params = $injector.annotate(value);
29467 forEach(params, function (param) {
29468 if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param);
29470 plan.push(key, value, params);
29474 visited[key] = VISIT_DONE;
29476 forEach(invocables, visit);
29477 invocables = cycle = visited = null; // plan is all that's required
29479 function isResolve(value) {
29480 return isObject(value) && value.then && value.$$promises;
29483 return function (locals, parent, self) {
29484 if (isResolve(locals) && self === undefined) {
29485 self = parent; parent = locals; locals = null;
29487 if (!locals) locals = NO_LOCALS;
29488 else if (!isObject(locals)) {
29489 throw new Error("'locals' must be an object");
29491 if (!parent) parent = NO_PARENT;
29492 else if (!isResolve(parent)) {
29493 throw new Error("'parent' must be a promise returned by $resolve.resolve()");
29496 // To complete the overall resolution, we have to wait for the parent
29497 // promise and for the promise for each invokable in our plan.
29498 var resolution = $q.defer(),
29499 result = resolution.promise,
29500 promises = result.$$promises = {},
29501 values = extend({}, locals),
29502 wait = 1 + plan.length/3,
29506 // Merge parent values we haven't got yet and publish our own $$values
29508 if (!merged) merge(values, parent.$$values);
29509 result.$$values = values;
29510 result.$$promises = result.$$promises || true; // keep for isResolve()
29511 delete result.$$inheritedValues;
29512 resolution.resolve(values);
29516 function fail(reason) {
29517 result.$$failure = reason;
29518 resolution.reject(reason);
29521 // Short-circuit if parent has already failed
29522 if (isDefined(parent.$$failure)) {
29523 fail(parent.$$failure);
29527 if (parent.$$inheritedValues) {
29528 merge(values, omit(parent.$$inheritedValues, invocableKeys));
29531 // Merge parent values if the parent has already resolved, or merge
29532 // parent promises and wait if the parent resolve is still in progress.
29533 extend(promises, parent.$$promises);
29534 if (parent.$$values) {
29535 merged = merge(values, omit(parent.$$values, invocableKeys));
29536 result.$$inheritedValues = omit(parent.$$values, invocableKeys);
29539 if (parent.$$inheritedValues) {
29540 result.$$inheritedValues = omit(parent.$$inheritedValues, invocableKeys);
29542 parent.then(done, fail);
29545 // Process each invocable in the plan, but ignore any where a local of the same name exists.
29546 for (var i=0, ii=plan.length; i<ii; i+=3) {
29547 if (locals.hasOwnProperty(plan[i])) done();
29548 else invoke(plan[i], plan[i+1], plan[i+2]);
29551 function invoke(key, invocable, params) {
29552 // Create a deferred for this invocation. Failures will propagate to the resolution as well.
29553 var invocation = $q.defer(), waitParams = 0;
29554 function onfailure(reason) {
29555 invocation.reject(reason);
29558 // Wait for any parameter that we have a promise for (either from parent or from this
29559 // resolve; in that case study() will have made sure it's ordered before us in the plan).
29560 forEach(params, function (dep) {
29561 if (promises.hasOwnProperty(dep) && !locals.hasOwnProperty(dep)) {
29563 promises[dep].then(function (result) {
29564 values[dep] = result;
29565 if (!(--waitParams)) proceed();
29569 if (!waitParams) proceed();
29570 function proceed() {
29571 if (isDefined(result.$$failure)) return;
29573 invocation.resolve($injector.invoke(invocable, self, values));
29574 invocation.promise.then(function (result) {
29575 values[key] = result;
29582 // Publish promise synchronously; invocations further down in the plan may depend on it.
29583 promises[key] = invocation.promise;
29592 * @name ui.router.util.$resolve#resolve
29593 * @methodOf ui.router.util.$resolve
29596 * Resolves a set of invocables. An invocable is a function to be invoked via
29597 * `$injector.invoke()`, and can have an arbitrary number of dependencies.
29598 * An invocable can either return a value directly,
29599 * or a `$q` promise. If a promise is returned it will be resolved and the
29600 * resulting value will be used instead. Dependencies of invocables are resolved
29601 * (in this order of precedence)
29603 * - from the specified `locals`
29604 * - from another invocable that is part of this `$resolve` call
29605 * - from an invocable that is inherited from a `parent` call to `$resolve`
29607 * - from any ancestor `$resolve` of that parent).
29609 * The return value of `$resolve` is a promise for an object that contains
29610 * (in this order of precedence)
29612 * - any `locals` (if specified)
29613 * - the resolved return values of all injectables
29614 * - any values inherited from a `parent` call to `$resolve` (if specified)
29616 * The promise will resolve after the `parent` promise (if any) and all promises
29617 * returned by injectables have been resolved. If any invocable
29618 * (or `$injector.invoke`) throws an exception, or if a promise returned by an
29619 * invocable is rejected, the `$resolve` promise is immediately rejected with the
29620 * same error. A rejection of a `parent` promise (if specified) will likewise be
29621 * propagated immediately. Once the `$resolve` promise has been rejected, no
29622 * further invocables will be called.
29624 * Cyclic dependencies between invocables are not permitted and will caues `$resolve`
29625 * to throw an error. As a special case, an injectable can depend on a parameter
29626 * with the same name as the injectable, which will be fulfilled from the `parent`
29627 * injectable of the same name. This allows inherited values to be decorated.
29628 * Note that in this case any other injectable in the same `$resolve` with the same
29629 * dependency would see the decorated value, not the inherited value.
29631 * Note that missing dependencies -- unlike cyclic dependencies -- will cause an
29632 * (asynchronous) rejection of the `$resolve` promise rather than a (synchronous)
29635 * Invocables are invoked eagerly as soon as all dependencies are available.
29636 * This is true even for dependencies inherited from a `parent` call to `$resolve`.
29638 * As a special case, an invocable can be a string, in which case it is taken to
29639 * be a service name to be passed to `$injector.get()`. This is supported primarily
29640 * for backwards-compatibility with the `resolve` property of `$routeProvider`
29643 * @param {object} invocables functions to invoke or
29644 * `$injector` services to fetch.
29645 * @param {object} locals values to make available to the injectables
29646 * @param {object} parent a promise returned by another call to `$resolve`.
29647 * @param {object} self the `this` for the invoked methods
29648 * @return {object} Promise for an object that contains the resolved return value
29649 * of all invocables, as well as any inherited and local values.
29651 this.resolve = function (invocables, locals, parent, self) {
29652 return this.study(invocables)(locals, parent, self);
29656 angular.module('ui.router.util').service('$resolve', $Resolve);
29661 * @name ui.router.util.$templateFactory
29664 * @requires $templateCache
29665 * @requires $injector
29668 * Service. Manages loading of templates.
29670 $TemplateFactory.$inject = ['$http', '$templateCache', '$injector'];
29671 function $TemplateFactory( $http, $templateCache, $injector) {
29675 * @name ui.router.util.$templateFactory#fromConfig
29676 * @methodOf ui.router.util.$templateFactory
29679 * Creates a template from a configuration object.
29681 * @param {object} config Configuration object for which to load a template.
29682 * The following properties are search in the specified order, and the first one
29683 * that is defined is used to create the template:
29685 * @param {string|object} config.template html string template or function to
29686 * load via {@link ui.router.util.$templateFactory#fromString fromString}.
29687 * @param {string|object} config.templateUrl url to load or a function returning
29688 * the url to load via {@link ui.router.util.$templateFactory#fromUrl fromUrl}.
29689 * @param {Function} config.templateProvider function to invoke via
29690 * {@link ui.router.util.$templateFactory#fromProvider fromProvider}.
29691 * @param {object} params Parameters to pass to the template function.
29692 * @param {object} locals Locals to pass to `invoke` if the template is loaded
29693 * via a `templateProvider`. Defaults to `{ params: params }`.
29695 * @return {string|object} The template html as a string, or a promise for
29696 * that string,or `null` if no template is configured.
29698 this.fromConfig = function (config, params, locals) {
29700 isDefined(config.template) ? this.fromString(config.template, params) :
29701 isDefined(config.templateUrl) ? this.fromUrl(config.templateUrl, params) :
29702 isDefined(config.templateProvider) ? this.fromProvider(config.templateProvider, params, locals) :
29709 * @name ui.router.util.$templateFactory#fromString
29710 * @methodOf ui.router.util.$templateFactory
29713 * Creates a template from a string or a function returning a string.
29715 * @param {string|object} template html template as a string or function that
29716 * returns an html template as a string.
29717 * @param {object} params Parameters to pass to the template function.
29719 * @return {string|object} The template html as a string, or a promise for that
29722 this.fromString = function (template, params) {
29723 return isFunction(template) ? template(params) : template;
29728 * @name ui.router.util.$templateFactory#fromUrl
29729 * @methodOf ui.router.util.$templateFactory
29732 * Loads a template from the a URL via `$http` and `$templateCache`.
29734 * @param {string|Function} url url of the template to load, or a function
29735 * that returns a url.
29736 * @param {Object} params Parameters to pass to the url function.
29737 * @return {string|Promise.<string>} The template html as a string, or a promise
29740 this.fromUrl = function (url, params) {
29741 if (isFunction(url)) url = url(params);
29742 if (url == null) return null;
29744 .get(url, { cache: $templateCache, headers: { Accept: 'text/html' }})
29745 .then(function(response) { return response.data; });
29750 * @name ui.router.util.$templateFactory#fromProvider
29751 * @methodOf ui.router.util.$templateFactory
29754 * Creates a template by invoking an injectable provider function.
29756 * @param {Function} provider Function to invoke via `$injector.invoke`
29757 * @param {Object} params Parameters for the template.
29758 * @param {Object} locals Locals to pass to `invoke`. Defaults to
29759 * `{ params: params }`.
29760 * @return {string|Promise.<string>} The template html as a string, or a promise
29763 this.fromProvider = function (provider, params, locals) {
29764 return $injector.invoke(provider, null, locals || { params: params });
29768 angular.module('ui.router.util').service('$templateFactory', $TemplateFactory);
29770 var $$UMFP; // reference to $UrlMatcherFactoryProvider
29774 * @name ui.router.util.type:UrlMatcher
29777 * Matches URLs against patterns and extracts named parameters from the path or the search
29778 * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list
29779 * of search parameters. Multiple search parameter names are separated by '&'. Search parameters
29780 * do not influence whether or not a URL is matched, but their values are passed through into
29781 * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}.
29783 * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace
29784 * syntax, which optionally allows a regular expression for the parameter to be specified:
29786 * * `':'` name - colon placeholder
29787 * * `'*'` name - catch-all placeholder
29788 * * `'{' name '}'` - curly placeholder
29789 * * `'{' name ':' regexp|type '}'` - curly placeholder with regexp or type name. Should the
29790 * regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash.
29792 * Parameter names may contain only word characters (latin letters, digits, and underscore) and
29793 * must be unique within the pattern (across both path and search parameters). For colon
29794 * placeholders or curly placeholders without an explicit regexp, a path parameter matches any
29795 * number of characters other than '/'. For catch-all placeholders the path parameter matches
29796 * any number of characters.
29800 * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for
29801 * trailing slashes, and patterns have to match the entire path, not just a prefix.
29802 * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or
29803 * '/user/bob/details'. The second path segment will be captured as the parameter 'id'.
29804 * * `'/user/{id}'` - Same as the previous example, but using curly brace syntax.
29805 * * `'/user/{id:[^/]*}'` - Same as the previous example.
29806 * * `'/user/{id:[0-9a-fA-F]{1,8}}'` - Similar to the previous example, but only matches if the id
29807 * parameter consists of 1 to 8 hex digits.
29808 * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the
29809 * path into the parameter 'path'.
29810 * * `'/files/*path'` - ditto.
29811 * * `'/calendar/{start:date}'` - Matches "/calendar/2014-11-12" (because the pattern defined
29812 * in the built-in `date` Type matches `2014-11-12`) and provides a Date object in $stateParams.start
29814 * @param {string} pattern The pattern to compile into a matcher.
29815 * @param {Object} config A configuration object hash:
29816 * @param {Object=} parentMatcher Used to concatenate the pattern/config onto
29817 * an existing UrlMatcher
29819 * * `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`.
29820 * * `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`.
29822 * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any
29823 * URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns
29824 * non-null) will start with this prefix.
29826 * @property {string} source The pattern that was passed into the constructor
29828 * @property {string} sourcePath The path portion of the source property
29830 * @property {string} sourceSearch The search portion of the source property
29832 * @property {string} regex The constructed regex that will be used to match against the url when
29833 * it is time to determine which url will match.
29835 * @returns {Object} New `UrlMatcher` object
29837 function UrlMatcher(pattern, config, parentMatcher) {
29838 config = extend({ params: {} }, isObject(config) ? config : {});
29840 // Find all placeholders and create a compiled pattern, using either classic or curly syntax:
29844 // '{' name ':' regexp '}'
29845 // The regular expression is somewhat complicated due to the need to allow curly braces
29846 // inside the regular expression. The placeholder regexp breaks down as follows:
29847 // ([:*])([\w\[\]]+) - classic placeholder ($1 / $2) (search version has - for snake-case)
29848 // \{([\w\[\]]+)(?:\:( ... ))?\} - curly brace placeholder ($3) with optional regexp/type ... ($4) (search version has - for snake-case
29849 // (?: ... | ... | ... )+ - the regexp consists of any number of atoms, an atom being either
29850 // [^{}\\]+ - anything other than curly braces or backslash
29851 // \\. - a backslash escape
29852 // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms
29853 var placeholder = /([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
29854 searchPlaceholder = /([:]?)([\w\[\]-]+)|\{([\w\[\]-]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
29855 compiled = '^', last = 0, m,
29856 segments = this.segments = [],
29857 parentParams = parentMatcher ? parentMatcher.params : {},
29858 params = this.params = parentMatcher ? parentMatcher.params.$$new() : new $$UMFP.ParamSet(),
29861 function addParameter(id, type, config, location) {
29862 paramNames.push(id);
29863 if (parentParams[id]) return parentParams[id];
29864 if (!/^\w+(-+\w+)*(?:\[\])?$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'");
29865 if (params[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'");
29866 params[id] = new $$UMFP.Param(id, type, config, location);
29870 function quoteRegExp(string, pattern, squash, optional) {
29871 var surroundPattern = ['',''], result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&");
29872 if (!pattern) return result;
29874 case false: surroundPattern = ['(', ')' + (optional ? "?" : "")]; break;
29875 case true: surroundPattern = ['?(', ')?']; break;
29876 default: surroundPattern = ['(' + squash + "|", ')?']; break;
29878 return result + surroundPattern[0] + pattern + surroundPattern[1];
29881 this.source = pattern;
29883 // Split into static segments separated by path parameter placeholders.
29884 // The number of segments is always 1 more than the number of parameters.
29885 function matchDetails(m, isSearch) {
29886 var id, regexp, segment, type, cfg, arrayMode;
29887 id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null
29888 cfg = config.params[id];
29889 segment = pattern.substring(last, m.index);
29890 regexp = isSearch ? m[4] : m[4] || (m[1] == '*' ? '.*' : null);
29891 type = $$UMFP.type(regexp || "string") || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp, config.caseInsensitive ? 'i' : undefined) });
29893 id: id, regexp: regexp, segment: segment, type: type, cfg: cfg
29897 var p, param, segment;
29898 while ((m = placeholder.exec(pattern))) {
29899 p = matchDetails(m, false);
29900 if (p.segment.indexOf('?') >= 0) break; // we're into the search part
29902 param = addParameter(p.id, p.type, p.cfg, "path");
29903 compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash, param.isOptional);
29904 segments.push(p.segment);
29905 last = placeholder.lastIndex;
29907 segment = pattern.substring(last);
29909 // Find any search parameter names and remove them from the last segment
29910 var i = segment.indexOf('?');
29913 var search = this.sourceSearch = segment.substring(i);
29914 segment = segment.substring(0, i);
29915 this.sourcePath = pattern.substring(0, last + i);
29917 if (search.length > 0) {
29919 while ((m = searchPlaceholder.exec(search))) {
29920 p = matchDetails(m, true);
29921 param = addParameter(p.id, p.type, p.cfg, "search");
29922 last = placeholder.lastIndex;
29927 this.sourcePath = pattern;
29928 this.sourceSearch = '';
29931 compiled += quoteRegExp(segment) + (config.strict === false ? '\/?' : '') + '$';
29932 segments.push(segment);
29934 this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined);
29935 this.prefix = segments[0];
29936 this.$$paramNames = paramNames;
29941 * @name ui.router.util.type:UrlMatcher#concat
29942 * @methodOf ui.router.util.type:UrlMatcher
29945 * Returns a new matcher for a pattern constructed by appending the path part and adding the
29946 * search parameters of the specified pattern to this pattern. The current pattern is not
29947 * modified. This can be understood as creating a pattern for URLs that are relative to (or
29948 * suffixes of) the current pattern.
29951 * The following two matchers are equivalent:
29953 * new UrlMatcher('/user/{id}?q').concat('/details?date');
29954 * new UrlMatcher('/user/{id}/details?q&date');
29957 * @param {string} pattern The pattern to append.
29958 * @param {Object} config An object hash of the configuration for the matcher.
29959 * @returns {UrlMatcher} A matcher for the concatenated pattern.
29961 UrlMatcher.prototype.concat = function (pattern, config) {
29962 // Because order of search parameters is irrelevant, we can add our own search
29963 // parameters to the end of the new pattern. Parse the new pattern by itself
29964 // and then join the bits together, but it's much easier to do this on a string level.
29965 var defaultConfig = {
29966 caseInsensitive: $$UMFP.caseInsensitive(),
29967 strict: $$UMFP.strictMode(),
29968 squash: $$UMFP.defaultSquashPolicy()
29970 return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch, extend(defaultConfig, config), this);
29973 UrlMatcher.prototype.toString = function () {
29974 return this.source;
29979 * @name ui.router.util.type:UrlMatcher#exec
29980 * @methodOf ui.router.util.type:UrlMatcher
29983 * Tests the specified path against this matcher, and returns an object containing the captured
29984 * parameter values, or null if the path does not match. The returned object contains the values
29985 * of any search parameters that are mentioned in the pattern, but their value may be null if
29986 * they are not present in `searchParams`. This means that search parameters are always treated
29991 * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', {
29992 * x: '1', q: 'hello'
29994 * // returns { id: 'bob', q: 'hello', r: null }
29997 * @param {string} path The URL path to match, e.g. `$location.path()`.
29998 * @param {Object} searchParams URL search parameters, e.g. `$location.search()`.
29999 * @returns {Object} The captured parameter values.
30001 UrlMatcher.prototype.exec = function (path, searchParams) {
30002 var m = this.regexp.exec(path);
30003 if (!m) return null;
30004 searchParams = searchParams || {};
30006 var paramNames = this.parameters(), nTotal = paramNames.length,
30007 nPath = this.segments.length - 1,
30008 values = {}, i, j, cfg, paramName;
30010 if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'");
30012 function decodePathArray(string) {
30013 function reverseString(str) { return str.split("").reverse().join(""); }
30014 function unquoteDashes(str) { return str.replace(/\\-/g, "-"); }
30016 var split = reverseString(string).split(/-(?!\\)/);
30017 var allReversed = map(split, reverseString);
30018 return map(allReversed, unquoteDashes).reverse();
30021 for (i = 0; i < nPath; i++) {
30022 paramName = paramNames[i];
30023 var param = this.params[paramName];
30024 var paramVal = m[i+1];
30025 // if the param value matches a pre-replace pair, replace the value before decoding.
30026 for (j = 0; j < param.replace; j++) {
30027 if (param.replace[j].from === paramVal) paramVal = param.replace[j].to;
30029 if (paramVal && param.array === true) paramVal = decodePathArray(paramVal);
30030 values[paramName] = param.value(paramVal);
30032 for (/**/; i < nTotal; i++) {
30033 paramName = paramNames[i];
30034 values[paramName] = this.params[paramName].value(searchParams[paramName]);
30042 * @name ui.router.util.type:UrlMatcher#parameters
30043 * @methodOf ui.router.util.type:UrlMatcher
30046 * Returns the names of all path and search parameters of this pattern in an unspecified order.
30048 * @returns {Array.<string>} An array of parameter names. Must be treated as read-only. If the
30049 * pattern has no parameters, an empty array is returned.
30051 UrlMatcher.prototype.parameters = function (param) {
30052 if (!isDefined(param)) return this.$$paramNames;
30053 return this.params[param] || null;
30058 * @name ui.router.util.type:UrlMatcher#validate
30059 * @methodOf ui.router.util.type:UrlMatcher
30062 * Checks an object hash of parameters to validate their correctness according to the parameter
30063 * types of this `UrlMatcher`.
30065 * @param {Object} params The object hash of parameters to validate.
30066 * @returns {boolean} Returns `true` if `params` validates, otherwise `false`.
30068 UrlMatcher.prototype.validates = function (params) {
30069 return this.params.$$validates(params);
30074 * @name ui.router.util.type:UrlMatcher#format
30075 * @methodOf ui.router.util.type:UrlMatcher
30078 * Creates a URL that matches this pattern by substituting the specified values
30079 * for the path and search parameters. Null values for path parameters are
30080 * treated as empty strings.
30084 * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' });
30085 * // returns '/user/bob?q=yes'
30088 * @param {Object} values the values to substitute for the parameters in this pattern.
30089 * @returns {string} the formatted URL (path and optionally search part).
30091 UrlMatcher.prototype.format = function (values) {
30092 values = values || {};
30093 var segments = this.segments, params = this.parameters(), paramset = this.params;
30094 if (!this.validates(values)) return null;
30096 var i, search = false, nPath = segments.length - 1, nTotal = params.length, result = segments[0];
30098 function encodeDashes(str) { // Replace dashes with encoded "\-"
30099 return encodeURIComponent(str).replace(/-/g, function(c) { return '%5C%' + c.charCodeAt(0).toString(16).toUpperCase(); });
30102 for (i = 0; i < nTotal; i++) {
30103 var isPathParam = i < nPath;
30104 var name = params[i], param = paramset[name], value = param.value(values[name]);
30105 var isDefaultValue = param.isOptional && param.type.equals(param.value(), value);
30106 var squash = isDefaultValue ? param.squash : false;
30107 var encoded = param.type.encode(value);
30110 var nextSegment = segments[i + 1];
30111 if (squash === false) {
30112 if (encoded != null) {
30113 if (isArray(encoded)) {
30114 result += map(encoded, encodeDashes).join("-");
30116 result += encodeURIComponent(encoded);
30119 result += nextSegment;
30120 } else if (squash === true) {
30121 var capture = result.match(/\/$/) ? /\/?(.*)/ : /(.*)/;
30122 result += nextSegment.match(capture)[1];
30123 } else if (isString(squash)) {
30124 result += squash + nextSegment;
30127 if (encoded == null || (isDefaultValue && squash !== false)) continue;
30128 if (!isArray(encoded)) encoded = [ encoded ];
30129 encoded = map(encoded, encodeURIComponent).join('&' + name + '=');
30130 result += (search ? '&' : '?') + (name + '=' + encoded);
30140 * @name ui.router.util.type:Type
30143 * Implements an interface to define custom parameter types that can be decoded from and encoded to
30144 * string parameters matched in a URL. Used by {@link ui.router.util.type:UrlMatcher `UrlMatcher`}
30145 * objects when matching or formatting URLs, or comparing or validating parameter values.
30147 * See {@link ui.router.util.$urlMatcherFactory#methods_type `$urlMatcherFactory#type()`} for more
30148 * information on registering custom types.
30150 * @param {Object} config A configuration object which contains the custom type definition. The object's
30151 * properties will override the default methods and/or pattern in `Type`'s public interface.
30155 * decode: function(val) { return parseInt(val, 10); },
30156 * encode: function(val) { return val && val.toString(); },
30157 * equals: function(a, b) { return this.is(a) && a === b; },
30158 * is: function(val) { return angular.isNumber(val) isFinite(val) && val % 1 === 0; },
30163 * @property {RegExp} pattern The regular expression pattern used to match values of this type when
30164 * coming from a substring of a URL.
30166 * @returns {Object} Returns a new `Type` object.
30168 function Type(config) {
30169 extend(this, config);
30174 * @name ui.router.util.type:Type#is
30175 * @methodOf ui.router.util.type:Type
30178 * Detects whether a value is of a particular type. Accepts a native (decoded) value
30179 * and determines whether it matches the current `Type` object.
30181 * @param {*} val The value to check.
30182 * @param {string} key Optional. If the type check is happening in the context of a specific
30183 * {@link ui.router.util.type:UrlMatcher `UrlMatcher`} object, this is the name of the
30184 * parameter in which `val` is stored. Can be used for meta-programming of `Type` objects.
30185 * @returns {Boolean} Returns `true` if the value matches the type, otherwise `false`.
30187 Type.prototype.is = function(val, key) {
30193 * @name ui.router.util.type:Type#encode
30194 * @methodOf ui.router.util.type:Type
30197 * Encodes a custom/native type value to a string that can be embedded in a URL. Note that the
30198 * return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it
30199 * only needs to be a representation of `val` that has been coerced to a string.
30201 * @param {*} val The value to encode.
30202 * @param {string} key The name of the parameter in which `val` is stored. Can be used for
30203 * meta-programming of `Type` objects.
30204 * @returns {string} Returns a string representation of `val` that can be encoded in a URL.
30206 Type.prototype.encode = function(val, key) {
30212 * @name ui.router.util.type:Type#decode
30213 * @methodOf ui.router.util.type:Type
30216 * Converts a parameter value (from URL string or transition param) to a custom/native value.
30218 * @param {string} val The URL parameter value to decode.
30219 * @param {string} key The name of the parameter in which `val` is stored. Can be used for
30220 * meta-programming of `Type` objects.
30221 * @returns {*} Returns a custom representation of the URL parameter value.
30223 Type.prototype.decode = function(val, key) {
30229 * @name ui.router.util.type:Type#equals
30230 * @methodOf ui.router.util.type:Type
30233 * Determines whether two decoded values are equivalent.
30235 * @param {*} a A value to compare against.
30236 * @param {*} b A value to compare against.
30237 * @returns {Boolean} Returns `true` if the values are equivalent/equal, otherwise `false`.
30239 Type.prototype.equals = function(a, b) {
30243 Type.prototype.$subPattern = function() {
30244 var sub = this.pattern.toString();
30245 return sub.substr(1, sub.length - 2);
30248 Type.prototype.pattern = /.*/;
30250 Type.prototype.toString = function() { return "{Type:" + this.name + "}"; };
30252 /** Given an encoded string, or a decoded object, returns a decoded object */
30253 Type.prototype.$normalize = function(val) {
30254 return this.is(val) ? val : this.decode(val);
30258 * Wraps an existing custom Type as an array of Type, depending on 'mode'.
30260 * - urlmatcher pattern "/path?{queryParam[]:int}"
30261 * - url: "/path?queryParam=1&queryParam=2
30262 * - $stateParams.queryParam will be [1, 2]
30263 * if `mode` is "auto", then
30264 * - url: "/path?queryParam=1 will create $stateParams.queryParam: 1
30265 * - url: "/path?queryParam=1&queryParam=2 will create $stateParams.queryParam: [1, 2]
30267 Type.prototype.$asArray = function(mode, isSearch) {
30268 if (!mode) return this;
30269 if (mode === "auto" && !isSearch) throw new Error("'auto' array mode is for query parameters only");
30271 function ArrayType(type, mode) {
30272 function bindTo(type, callbackName) {
30273 return function() {
30274 return type[callbackName].apply(type, arguments);
30278 // Wrap non-array value as array
30279 function arrayWrap(val) { return isArray(val) ? val : (isDefined(val) ? [ val ] : []); }
30280 // Unwrap array value for "auto" mode. Return undefined for empty array.
30281 function arrayUnwrap(val) {
30282 switch(val.length) {
30283 case 0: return undefined;
30284 case 1: return mode === "auto" ? val[0] : val;
30285 default: return val;
30288 function falsey(val) { return !val; }
30290 // Wraps type (.is/.encode/.decode) functions to operate on each value of an array
30291 function arrayHandler(callback, allTruthyMode) {
30292 return function handleArray(val) {
30293 val = arrayWrap(val);
30294 var result = map(val, callback);
30295 if (allTruthyMode === true)
30296 return filter(result, falsey).length === 0;
30297 return arrayUnwrap(result);
30301 // Wraps type (.equals) functions to operate on each value of an array
30302 function arrayEqualsHandler(callback) {
30303 return function handleArray(val1, val2) {
30304 var left = arrayWrap(val1), right = arrayWrap(val2);
30305 if (left.length !== right.length) return false;
30306 for (var i = 0; i < left.length; i++) {
30307 if (!callback(left[i], right[i])) return false;
30313 this.encode = arrayHandler(bindTo(type, 'encode'));
30314 this.decode = arrayHandler(bindTo(type, 'decode'));
30315 this.is = arrayHandler(bindTo(type, 'is'), true);
30316 this.equals = arrayEqualsHandler(bindTo(type, 'equals'));
30317 this.pattern = type.pattern;
30318 this.$normalize = arrayHandler(bindTo(type, '$normalize'));
30319 this.name = type.name;
30320 this.$arrayMode = mode;
30323 return new ArrayType(this, mode);
30330 * @name ui.router.util.$urlMatcherFactory
30333 * Factory for {@link ui.router.util.type:UrlMatcher `UrlMatcher`} instances. The factory
30334 * is also available to providers under the name `$urlMatcherFactoryProvider`.
30336 function $UrlMatcherFactory() {
30339 var isCaseInsensitive = false, isStrictMode = true, defaultSquashPolicy = false;
30341 function valToString(val) { return val != null ? val.toString().replace(/\//g, "%2F") : val; }
30342 function valFromString(val) { return val != null ? val.toString().replace(/%2F/g, "/") : val; }
30344 var $types = {}, enqueue = true, typeQueue = [], injector, defaultTypes = {
30346 encode: valToString,
30347 decode: valFromString,
30348 // TODO: in 1.0, make string .is() return false if value is undefined/null by default.
30349 // In 0.2.x, string params are optional by default for backwards compat
30350 is: function(val) { return val == null || !isDefined(val) || typeof val === "string"; },
30354 encode: valToString,
30355 decode: function(val) { return parseInt(val, 10); },
30356 is: function(val) { return isDefined(val) && this.decode(val.toString()) === val; },
30360 encode: function(val) { return val ? 1 : 0; },
30361 decode: function(val) { return parseInt(val, 10) !== 0; },
30362 is: function(val) { return val === true || val === false; },
30366 encode: function (val) {
30369 return [ val.getFullYear(),
30370 ('0' + (val.getMonth() + 1)).slice(-2),
30371 ('0' + val.getDate()).slice(-2)
30374 decode: function (val) {
30375 if (this.is(val)) return val;
30376 var match = this.capture.exec(val);
30377 return match ? new Date(match[1], match[2] - 1, match[3]) : undefined;
30379 is: function(val) { return val instanceof Date && !isNaN(val.valueOf()); },
30380 equals: function (a, b) { return this.is(a) && this.is(b) && a.toISOString() === b.toISOString(); },
30381 pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/,
30382 capture: /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/
30385 encode: angular.toJson,
30386 decode: angular.fromJson,
30387 is: angular.isObject,
30388 equals: angular.equals,
30391 any: { // does not encode/decode
30392 encode: angular.identity,
30393 decode: angular.identity,
30394 equals: angular.equals,
30399 function getDefaultConfig() {
30401 strict: isStrictMode,
30402 caseInsensitive: isCaseInsensitive
30406 function isInjectable(value) {
30407 return (isFunction(value) || (isArray(value) && isFunction(value[value.length - 1])));
30411 * [Internal] Get the default value of a parameter, which may be an injectable function.
30413 $UrlMatcherFactory.$$getDefaultValue = function(config) {
30414 if (!isInjectable(config.value)) return config.value;
30415 if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
30416 return injector.invoke(config.value);
30421 * @name ui.router.util.$urlMatcherFactory#caseInsensitive
30422 * @methodOf ui.router.util.$urlMatcherFactory
30425 * Defines whether URL matching should be case sensitive (the default behavior), or not.
30427 * @param {boolean} value `false` to match URL in a case sensitive manner; otherwise `true`;
30428 * @returns {boolean} the current value of caseInsensitive
30430 this.caseInsensitive = function(value) {
30431 if (isDefined(value))
30432 isCaseInsensitive = value;
30433 return isCaseInsensitive;
30438 * @name ui.router.util.$urlMatcherFactory#strictMode
30439 * @methodOf ui.router.util.$urlMatcherFactory
30442 * Defines whether URLs should match trailing slashes, or not (the default behavior).
30444 * @param {boolean=} value `false` to match trailing slashes in URLs, otherwise `true`.
30445 * @returns {boolean} the current value of strictMode
30447 this.strictMode = function(value) {
30448 if (isDefined(value))
30449 isStrictMode = value;
30450 return isStrictMode;
30455 * @name ui.router.util.$urlMatcherFactory#defaultSquashPolicy
30456 * @methodOf ui.router.util.$urlMatcherFactory
30459 * Sets the default behavior when generating or matching URLs with default parameter values.
30461 * @param {string} value A string that defines the default parameter URL squashing behavior.
30462 * `nosquash`: When generating an href with a default parameter value, do not squash the parameter value from the URL
30463 * `slash`: When generating an href with a default parameter value, squash (remove) the parameter value, and, if the
30464 * parameter is surrounded by slashes, squash (remove) one slash from the URL
30465 * any other string, e.g. "~": When generating an href with a default parameter value, squash (remove)
30466 * the parameter value from the URL and replace it with this string.
30468 this.defaultSquashPolicy = function(value) {
30469 if (!isDefined(value)) return defaultSquashPolicy;
30470 if (value !== true && value !== false && !isString(value))
30471 throw new Error("Invalid squash policy: " + value + ". Valid policies: false, true, arbitrary-string");
30472 defaultSquashPolicy = value;
30478 * @name ui.router.util.$urlMatcherFactory#compile
30479 * @methodOf ui.router.util.$urlMatcherFactory
30482 * Creates a {@link ui.router.util.type:UrlMatcher `UrlMatcher`} for the specified pattern.
30484 * @param {string} pattern The URL pattern.
30485 * @param {Object} config The config object hash.
30486 * @returns {UrlMatcher} The UrlMatcher.
30488 this.compile = function (pattern, config) {
30489 return new UrlMatcher(pattern, extend(getDefaultConfig(), config));
30494 * @name ui.router.util.$urlMatcherFactory#isMatcher
30495 * @methodOf ui.router.util.$urlMatcherFactory
30498 * Returns true if the specified object is a `UrlMatcher`, or false otherwise.
30500 * @param {Object} object The object to perform the type check against.
30501 * @returns {Boolean} Returns `true` if the object matches the `UrlMatcher` interface, by
30502 * implementing all the same methods.
30504 this.isMatcher = function (o) {
30505 if (!isObject(o)) return false;
30508 forEach(UrlMatcher.prototype, function(val, name) {
30509 if (isFunction(val)) {
30510 result = result && (isDefined(o[name]) && isFunction(o[name]));
30518 * @name ui.router.util.$urlMatcherFactory#type
30519 * @methodOf ui.router.util.$urlMatcherFactory
30522 * Registers a custom {@link ui.router.util.type:Type `Type`} object that can be used to
30523 * generate URLs with typed parameters.
30525 * @param {string} name The type name.
30526 * @param {Object|Function} definition The type definition. See
30527 * {@link ui.router.util.type:Type `Type`} for information on the values accepted.
30528 * @param {Object|Function} definitionFn (optional) A function that is injected before the app
30529 * runtime starts. The result of this function is merged into the existing `definition`.
30530 * See {@link ui.router.util.type:Type `Type`} for information on the values accepted.
30532 * @returns {Object} Returns `$urlMatcherFactoryProvider`.
30535 * This is a simple example of a custom type that encodes and decodes items from an
30536 * array, using the array index as the URL-encoded value:
30539 * var list = ['John', 'Paul', 'George', 'Ringo'];
30541 * $urlMatcherFactoryProvider.type('listItem', {
30542 * encode: function(item) {
30543 * // Represent the list item in the URL using its corresponding index
30544 * return list.indexOf(item);
30546 * decode: function(item) {
30547 * // Look up the list item by index
30548 * return list[parseInt(item, 10)];
30550 * is: function(item) {
30551 * // Ensure the item is valid by checking to see that it appears
30553 * return list.indexOf(item) > -1;
30557 * $stateProvider.state('list', {
30558 * url: "/list/{item:listItem}",
30559 * controller: function($scope, $stateParams) {
30560 * console.log($stateParams.item);
30566 * // Changes URL to '/list/3', logs "Ringo" to the console
30567 * $state.go('list', { item: "Ringo" });
30570 * This is a more complex example of a type that relies on dependency injection to
30571 * interact with services, and uses the parameter name from the URL to infer how to
30572 * handle encoding and decoding parameter values:
30575 * // Defines a custom type that gets a value from a service,
30576 * // where each service gets different types of values from
30577 * // a backend API:
30578 * $urlMatcherFactoryProvider.type('dbObject', {}, function(Users, Posts) {
30580 * // Matches up services to URL parameter names
30587 * encode: function(object) {
30588 * // Represent the object in the URL using its unique ID
30589 * return object.id;
30591 * decode: function(value, key) {
30592 * // Look up the object by ID, using the parameter
30593 * // name (key) to call the correct service
30594 * return services[key].findById(value);
30596 * is: function(object, key) {
30597 * // Check that object is a valid dbObject
30598 * return angular.isObject(object) && object.id && services[key];
30600 * equals: function(a, b) {
30601 * // Check the equality of decoded objects by comparing
30602 * // their unique IDs
30603 * return a.id === b.id;
30608 * // In a config() block, you can then attach URLs with
30609 * // type-annotated parameters:
30610 * $stateProvider.state('users', {
30613 * }).state('users.item', {
30614 * url: "/{user:dbObject}",
30615 * controller: function($scope, $stateParams) {
30616 * // $stateParams.user will now be an object returned from
30617 * // the Users service
30623 this.type = function (name, definition, definitionFn) {
30624 if (!isDefined(definition)) return $types[name];
30625 if ($types.hasOwnProperty(name)) throw new Error("A type named '" + name + "' has already been defined.");
30627 $types[name] = new Type(extend({ name: name }, definition));
30628 if (definitionFn) {
30629 typeQueue.push({ name: name, def: definitionFn });
30630 if (!enqueue) flushTypeQueue();
30635 // `flushTypeQueue()` waits until `$urlMatcherFactory` is injected before invoking the queued `definitionFn`s
30636 function flushTypeQueue() {
30637 while(typeQueue.length) {
30638 var type = typeQueue.shift();
30639 if (type.pattern) throw new Error("You cannot override a type's .pattern at runtime.");
30640 angular.extend($types[type.name], injector.invoke(type.def));
30644 // Register default types. Store them in the prototype of $types.
30645 forEach(defaultTypes, function(type, name) { $types[name] = new Type(extend({name: name}, type)); });
30646 $types = inherit($types, {});
30648 /* No need to document $get, since it returns this */
30649 this.$get = ['$injector', function ($injector) {
30650 injector = $injector;
30654 forEach(defaultTypes, function(type, name) {
30655 if (!$types[name]) $types[name] = new Type(type);
30660 this.Param = function Param(id, type, config, location) {
30662 config = unwrapShorthand(config);
30663 type = getType(config, type, location);
30664 var arrayMode = getArrayMode();
30665 type = arrayMode ? type.$asArray(arrayMode, location === "search") : type;
30666 if (type.name === "string" && !arrayMode && location === "path" && config.value === undefined)
30667 config.value = ""; // for 0.2.x; in 0.3.0+ do not automatically default to ""
30668 var isOptional = config.value !== undefined;
30669 var squash = getSquashPolicy(config, isOptional);
30670 var replace = getReplace(config, arrayMode, isOptional, squash);
30672 function unwrapShorthand(config) {
30673 var keys = isObject(config) ? objectKeys(config) : [];
30674 var isShorthand = indexOf(keys, "value") === -1 && indexOf(keys, "type") === -1 &&
30675 indexOf(keys, "squash") === -1 && indexOf(keys, "array") === -1;
30676 if (isShorthand) config = { value: config };
30677 config.$$fn = isInjectable(config.value) ? config.value : function () { return config.value; };
30681 function getType(config, urlType, location) {
30682 if (config.type && urlType) throw new Error("Param '"+id+"' has two type configurations.");
30683 if (urlType) return urlType;
30684 if (!config.type) return (location === "config" ? $types.any : $types.string);
30685 return config.type instanceof Type ? config.type : new Type(config.type);
30688 // array config: param name (param[]) overrides default settings. explicit config overrides param name.
30689 function getArrayMode() {
30690 var arrayDefaults = { array: (location === "search" ? "auto" : false) };
30691 var arrayParamNomenclature = id.match(/\[\]$/) ? { array: true } : {};
30692 return extend(arrayDefaults, arrayParamNomenclature, config).array;
30696 * returns false, true, or the squash value to indicate the "default parameter url squash policy".
30698 function getSquashPolicy(config, isOptional) {
30699 var squash = config.squash;
30700 if (!isOptional || squash === false) return false;
30701 if (!isDefined(squash) || squash == null) return defaultSquashPolicy;
30702 if (squash === true || isString(squash)) return squash;
30703 throw new Error("Invalid squash policy: '" + squash + "'. Valid policies: false, true, or arbitrary string");
30706 function getReplace(config, arrayMode, isOptional, squash) {
30707 var replace, configuredKeys, defaultPolicy = [
30708 { from: "", to: (isOptional || arrayMode ? undefined : "") },
30709 { from: null, to: (isOptional || arrayMode ? undefined : "") }
30711 replace = isArray(config.replace) ? config.replace : [];
30712 if (isString(squash))
30713 replace.push({ from: squash, to: undefined });
30714 configuredKeys = map(replace, function(item) { return item.from; } );
30715 return filter(defaultPolicy, function(item) { return indexOf(configuredKeys, item.from) === -1; }).concat(replace);
30719 * [Internal] Get the default value of a parameter, which may be an injectable function.
30721 function $$getDefaultValue() {
30722 if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
30723 var defaultValue = injector.invoke(config.$$fn);
30724 if (defaultValue !== null && defaultValue !== undefined && !self.type.is(defaultValue))
30725 throw new Error("Default value (" + defaultValue + ") for parameter '" + self.id + "' is not an instance of Type (" + self.type.name + ")");
30726 return defaultValue;
30730 * [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the
30731 * default value, which may be the result of an injectable function.
30733 function $value(value) {
30734 function hasReplaceVal(val) { return function(obj) { return obj.from === val; }; }
30735 function $replace(value) {
30736 var replacement = map(filter(self.replace, hasReplaceVal(value)), function(obj) { return obj.to; });
30737 return replacement.length ? replacement[0] : value;
30739 value = $replace(value);
30740 return !isDefined(value) ? $$getDefaultValue() : self.type.$normalize(value);
30743 function toString() { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}"; }
30748 location: location,
30752 isOptional: isOptional,
30754 dynamic: undefined,
30760 function ParamSet(params) {
30761 extend(this, params || {});
30764 ParamSet.prototype = {
30765 $$new: function() {
30766 return inherit(this, extend(new ParamSet(), { $$parent: this}));
30768 $$keys: function () {
30769 var keys = [], chain = [], parent = this,
30770 ignore = objectKeys(ParamSet.prototype);
30771 while (parent) { chain.push(parent); parent = parent.$$parent; }
30773 forEach(chain, function(paramset) {
30774 forEach(objectKeys(paramset), function(key) {
30775 if (indexOf(keys, key) === -1 && indexOf(ignore, key) === -1) keys.push(key);
30780 $$values: function(paramValues) {
30781 var values = {}, self = this;
30782 forEach(self.$$keys(), function(key) {
30783 values[key] = self[key].value(paramValues && paramValues[key]);
30787 $$equals: function(paramValues1, paramValues2) {
30788 var equal = true, self = this;
30789 forEach(self.$$keys(), function(key) {
30790 var left = paramValues1 && paramValues1[key], right = paramValues2 && paramValues2[key];
30791 if (!self[key].type.equals(left, right)) equal = false;
30795 $$validates: function $$validate(paramValues) {
30796 var keys = this.$$keys(), i, param, rawVal, normalized, encoded;
30797 for (i = 0; i < keys.length; i++) {
30798 param = this[keys[i]];
30799 rawVal = paramValues[keys[i]];
30800 if ((rawVal === undefined || rawVal === null) && param.isOptional)
30801 break; // There was no parameter value, but the param is optional
30802 normalized = param.type.$normalize(rawVal);
30803 if (!param.type.is(normalized))
30804 return false; // The value was not of the correct Type, and could not be decoded to the correct Type
30805 encoded = param.type.encode(normalized);
30806 if (angular.isString(encoded) && !param.type.pattern.exec(encoded))
30807 return false; // The value was of the correct type, but when encoded, did not match the Type's regexp
30811 $$parent: undefined
30814 this.ParamSet = ParamSet;
30817 // Register as a provider so it's available to other providers
30818 angular.module('ui.router.util').provider('$urlMatcherFactory', $UrlMatcherFactory);
30819 angular.module('ui.router.util').run(['$urlMatcherFactory', function($urlMatcherFactory) { }]);
30823 * @name ui.router.router.$urlRouterProvider
30825 * @requires ui.router.util.$urlMatcherFactoryProvider
30826 * @requires $locationProvider
30829 * `$urlRouterProvider` has the responsibility of watching `$location`.
30830 * When `$location` changes it runs through a list of rules one by one until a
30831 * match is found. `$urlRouterProvider` is used behind the scenes anytime you specify
30832 * a url in a state configuration. All urls are compiled into a UrlMatcher object.
30834 * There are several methods on `$urlRouterProvider` that make it useful to use directly
30835 * in your module config.
30837 $UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider'];
30838 function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
30839 var rules = [], otherwise = null, interceptDeferred = false, listener;
30841 // Returns a string that is a prefix of all strings matching the RegExp
30842 function regExpPrefix(re) {
30843 var prefix = /^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(re.source);
30844 return (prefix != null) ? prefix[1].replace(/\\(.)/g, "$1") : '';
30847 // Interpolates matched values into a String.replace()-style pattern
30848 function interpolate(pattern, match) {
30849 return pattern.replace(/\$(\$|\d{1,2})/, function (m, what) {
30850 return match[what === '$' ? 0 : Number(what)];
30856 * @name ui.router.router.$urlRouterProvider#rule
30857 * @methodOf ui.router.router.$urlRouterProvider
30860 * Defines rules that are used by `$urlRouterProvider` to find matches for
30865 * var app = angular.module('app', ['ui.router.router']);
30867 * app.config(function ($urlRouterProvider) {
30868 * // Here's an example of how you might allow case insensitive urls
30869 * $urlRouterProvider.rule(function ($injector, $location) {
30870 * var path = $location.path(),
30871 * normalized = path.toLowerCase();
30873 * if (path !== normalized) {
30874 * return normalized;
30880 * @param {object} rule Handler function that takes `$injector` and `$location`
30881 * services as arguments. You can use them to return a valid path as a string.
30883 * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
30885 this.rule = function (rule) {
30886 if (!isFunction(rule)) throw new Error("'rule' must be a function");
30893 * @name ui.router.router.$urlRouterProvider#otherwise
30894 * @methodOf ui.router.router.$urlRouterProvider
30897 * Defines a path that is used when an invalid route is requested.
30901 * var app = angular.module('app', ['ui.router.router']);
30903 * app.config(function ($urlRouterProvider) {
30904 * // if the path doesn't match any of the urls you configured
30905 * // otherwise will take care of routing the user to the
30907 * $urlRouterProvider.otherwise('/index');
30909 * // Example of using function rule as param
30910 * $urlRouterProvider.otherwise(function ($injector, $location) {
30911 * return '/a/valid/url';
30916 * @param {string|object} rule The url path you want to redirect to or a function
30917 * rule that returns the url path. The function version is passed two params:
30918 * `$injector` and `$location` services, and must return a url string.
30920 * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
30922 this.otherwise = function (rule) {
30923 if (isString(rule)) {
30924 var redirect = rule;
30925 rule = function () { return redirect; };
30927 else if (!isFunction(rule)) throw new Error("'rule' must be a function");
30933 function handleIfMatch($injector, handler, match) {
30934 if (!match) return false;
30935 var result = $injector.invoke(handler, handler, { $match: match });
30936 return isDefined(result) ? result : true;
30941 * @name ui.router.router.$urlRouterProvider#when
30942 * @methodOf ui.router.router.$urlRouterProvider
30945 * Registers a handler for a given url matching. if handle is a string, it is
30946 * treated as a redirect, and is interpolated according to the syntax of match
30947 * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise).
30949 * If the handler is a function, it is injectable. It gets invoked if `$location`
30950 * matches. You have the option of inject the match object as `$match`.
30952 * The handler can return
30954 * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter`
30955 * will continue trying to find another one that matches.
30956 * - **string** which is treated as a redirect and passed to `$location.url()`
30957 * - **void** or any **truthy** value tells `$urlRouter` that the url was handled.
30961 * var app = angular.module('app', ['ui.router.router']);
30963 * app.config(function ($urlRouterProvider) {
30964 * $urlRouterProvider.when($state.url, function ($match, $stateParams) {
30965 * if ($state.$current.navigable !== state ||
30966 * !equalForKeys($match, $stateParams) {
30967 * $state.transitionTo(state, $match, false);
30973 * @param {string|object} what The incoming path that you want to redirect.
30974 * @param {string|object} handler The path you want to redirect your user to.
30976 this.when = function (what, handler) {
30977 var redirect, handlerIsString = isString(handler);
30978 if (isString(what)) what = $urlMatcherFactory.compile(what);
30980 if (!handlerIsString && !isFunction(handler) && !isArray(handler))
30981 throw new Error("invalid 'handler' in when()");
30984 matcher: function (what, handler) {
30985 if (handlerIsString) {
30986 redirect = $urlMatcherFactory.compile(handler);
30987 handler = ['$match', function ($match) { return redirect.format($match); }];
30989 return extend(function ($injector, $location) {
30990 return handleIfMatch($injector, handler, what.exec($location.path(), $location.search()));
30992 prefix: isString(what.prefix) ? what.prefix : ''
30995 regex: function (what, handler) {
30996 if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky");
30998 if (handlerIsString) {
30999 redirect = handler;
31000 handler = ['$match', function ($match) { return interpolate(redirect, $match); }];
31002 return extend(function ($injector, $location) {
31003 return handleIfMatch($injector, handler, what.exec($location.path()));
31005 prefix: regExpPrefix(what)
31010 var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp };
31012 for (var n in check) {
31013 if (check[n]) return this.rule(strategies[n](what, handler));
31016 throw new Error("invalid 'what' in when()");
31021 * @name ui.router.router.$urlRouterProvider#deferIntercept
31022 * @methodOf ui.router.router.$urlRouterProvider
31025 * Disables (or enables) deferring location change interception.
31027 * If you wish to customize the behavior of syncing the URL (for example, if you wish to
31028 * defer a transition but maintain the current URL), call this method at configuration time.
31029 * Then, at run time, call `$urlRouter.listen()` after you have configured your own
31030 * `$locationChangeSuccess` event handler.
31034 * var app = angular.module('app', ['ui.router.router']);
31036 * app.config(function ($urlRouterProvider) {
31038 * // Prevent $urlRouter from automatically intercepting URL changes;
31039 * // this allows you to configure custom behavior in between
31040 * // location changes and route synchronization:
31041 * $urlRouterProvider.deferIntercept();
31043 * }).run(function ($rootScope, $urlRouter, UserService) {
31045 * $rootScope.$on('$locationChangeSuccess', function(e) {
31046 * // UserService is an example service for managing user state
31047 * if (UserService.isLoggedIn()) return;
31049 * // Prevent $urlRouter's default handler from firing
31050 * e.preventDefault();
31052 * UserService.handleLogin().then(function() {
31053 * // Once the user has logged in, sync the current URL
31054 * // to the router:
31055 * $urlRouter.sync();
31059 * // Configures $urlRouter's listener *after* your custom listener
31060 * $urlRouter.listen();
31064 * @param {boolean} defer Indicates whether to defer location change interception. Passing
31065 no parameter is equivalent to `true`.
31067 this.deferIntercept = function (defer) {
31068 if (defer === undefined) defer = true;
31069 interceptDeferred = defer;
31074 * @name ui.router.router.$urlRouter
31076 * @requires $location
31077 * @requires $rootScope
31078 * @requires $injector
31079 * @requires $browser
31085 $get.$inject = ['$location', '$rootScope', '$injector', '$browser'];
31086 function $get( $location, $rootScope, $injector, $browser) {
31088 var baseHref = $browser.baseHref(), location = $location.url(), lastPushedUrl;
31090 function appendBasePath(url, isHtml5, absolute) {
31091 if (baseHref === '/') return url;
31092 if (isHtml5) return baseHref.slice(0, -1) + url;
31093 if (absolute) return baseHref.slice(1) + url;
31097 // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree
31098 function update(evt) {
31099 if (evt && evt.defaultPrevented) return;
31100 var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl;
31101 lastPushedUrl = undefined;
31102 // TODO: Re-implement this in 1.0 for https://github.com/angular-ui/ui-router/issues/1573
31103 //if (ignoreUpdate) return true;
31105 function check(rule) {
31106 var handled = rule($injector, $location);
31108 if (!handled) return false;
31109 if (isString(handled)) $location.replace().url(handled);
31112 var n = rules.length, i;
31114 for (i = 0; i < n; i++) {
31115 if (check(rules[i])) return;
31117 // always check otherwise last to allow dynamic updates to the set of rules
31118 if (otherwise) check(otherwise);
31121 function listen() {
31122 listener = listener || $rootScope.$on('$locationChangeSuccess', update);
31126 if (!interceptDeferred) listen();
31131 * @name ui.router.router.$urlRouter#sync
31132 * @methodOf ui.router.router.$urlRouter
31135 * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`.
31136 * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event,
31137 * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed
31138 * with the transition by calling `$urlRouter.sync()`.
31142 * angular.module('app', ['ui.router'])
31143 * .run(function($rootScope, $urlRouter) {
31144 * $rootScope.$on('$locationChangeSuccess', function(evt) {
31145 * // Halt state change from even starting
31146 * evt.preventDefault();
31147 * // Perform custom logic
31148 * var meetsRequirement = ...
31149 * // Continue with the update and state transition if logic allows
31150 * if (meetsRequirement) $urlRouter.sync();
31159 listen: function() {
31163 update: function(read) {
31165 location = $location.url();
31168 if ($location.url() === location) return;
31170 $location.url(location);
31171 $location.replace();
31174 push: function(urlMatcher, params, options) {
31175 var url = urlMatcher.format(params || {});
31177 // Handle the special hash param, if needed
31178 if (url !== null && params && params['#']) {
31179 url += '#' + params['#'];
31182 $location.url(url);
31183 lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined;
31184 if (options && options.replace) $location.replace();
31189 * @name ui.router.router.$urlRouter#href
31190 * @methodOf ui.router.router.$urlRouter
31193 * A URL generation method that returns the compiled URL for a given
31194 * {@link ui.router.util.type:UrlMatcher `UrlMatcher`}, populated with the provided parameters.
31198 * $bob = $urlRouter.href(new UrlMatcher("/about/:person"), {
31201 * // $bob == "/about/bob";
31204 * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate.
31205 * @param {object=} params An object of parameter values to fill the matcher's required parameters.
31206 * @param {object=} options Options object. The options are:
31208 * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
31210 * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher`
31212 href: function(urlMatcher, params, options) {
31213 if (!urlMatcher.validates(params)) return null;
31215 var isHtml5 = $locationProvider.html5Mode();
31216 if (angular.isObject(isHtml5)) {
31217 isHtml5 = isHtml5.enabled;
31220 var url = urlMatcher.format(params);
31221 options = options || {};
31223 if (!isHtml5 && url !== null) {
31224 url = "#" + $locationProvider.hashPrefix() + url;
31227 // Handle special hash param, if needed
31228 if (url !== null && params && params['#']) {
31229 url += '#' + params['#'];
31232 url = appendBasePath(url, isHtml5, options.absolute);
31234 if (!options.absolute || !url) {
31238 var slash = (!isHtml5 && url ? '/' : ''), port = $location.port();
31239 port = (port === 80 || port === 443 ? '' : ':' + port);
31241 return [$location.protocol(), '://', $location.host(), port, slash, url].join('');
31247 angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);
31251 * @name ui.router.state.$stateProvider
31253 * @requires ui.router.router.$urlRouterProvider
31254 * @requires ui.router.util.$urlMatcherFactoryProvider
31257 * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely
31260 * A state corresponds to a "place" in the application in terms of the overall UI and
31261 * navigation. A state describes (via the controller / template / view properties) what
31262 * the UI looks like and does at that place.
31264 * States often have things in common, and the primary way of factoring out these
31265 * commonalities in this model is via the state hierarchy, i.e. parent/child states aka
31268 * The `$stateProvider` provides interfaces to declare these states for your app.
31270 $StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider'];
31271 function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
31273 var root, states = {}, $state, queue = {}, abstractKey = 'abstract';
31275 // Builds state properties from definition passed to registerState()
31276 var stateBuilder = {
31278 // Derive parent state from a hierarchical name only if 'parent' is not explicitly defined.
31279 // state.children = [];
31280 // if (parent) parent.children.push(state);
31281 parent: function(state) {
31282 if (isDefined(state.parent) && state.parent) return findState(state.parent);
31283 // regex matches any valid composite state name
31284 // would match "contact.list" but not "contacts"
31285 var compositeName = /^(.+)\.[^.]+$/.exec(state.name);
31286 return compositeName ? findState(compositeName[1]) : root;
31289 // inherit 'data' from parent and override by own values (if any)
31290 data: function(state) {
31291 if (state.parent && state.parent.data) {
31292 state.data = state.self.data = extend({}, state.parent.data, state.data);
31297 // Build a URLMatcher if necessary, either via a relative or absolute URL
31298 url: function(state) {
31299 var url = state.url, config = { params: state.params || {} };
31301 if (isString(url)) {
31302 if (url.charAt(0) == '^') return $urlMatcherFactory.compile(url.substring(1), config);
31303 return (state.parent.navigable || root).url.concat(url, config);
31306 if (!url || $urlMatcherFactory.isMatcher(url)) return url;
31307 throw new Error("Invalid url '" + url + "' in state '" + state + "'");
31310 // Keep track of the closest ancestor state that has a URL (i.e. is navigable)
31311 navigable: function(state) {
31312 return state.url ? state : (state.parent ? state.parent.navigable : null);
31315 // Own parameters for this state. state.url.params is already built at this point. Create and add non-url params
31316 ownParams: function(state) {
31317 var params = state.url && state.url.params || new $$UMFP.ParamSet();
31318 forEach(state.params || {}, function(config, id) {
31319 if (!params[id]) params[id] = new $$UMFP.Param(id, null, config, "config");
31324 // Derive parameters for this state and ensure they're a super-set of parent's parameters
31325 params: function(state) {
31326 return state.parent && state.parent.params ? extend(state.parent.params.$$new(), state.ownParams) : new $$UMFP.ParamSet();
31329 // If there is no explicit multi-view configuration, make one up so we don't have
31330 // to handle both cases in the view directive later. Note that having an explicit
31331 // 'views' property will mean the default unnamed view properties are ignored. This
31332 // is also a good time to resolve view names to absolute names, so everything is a
31333 // straight lookup at link time.
31334 views: function(state) {
31337 forEach(isDefined(state.views) ? state.views : { '': state }, function (view, name) {
31338 if (name.indexOf('@') < 0) name += '@' + state.parent.name;
31339 views[name] = view;
31344 // Keep a full path from the root down to this state as this is needed for state activation.
31345 path: function(state) {
31346 return state.parent ? state.parent.path.concat(state) : []; // exclude root from path
31349 // Speed up $state.contains() as it's used a lot
31350 includes: function(state) {
31351 var includes = state.parent ? extend({}, state.parent.includes) : {};
31352 includes[state.name] = true;
31359 function isRelative(stateName) {
31360 return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0;
31363 function findState(stateOrName, base) {
31364 if (!stateOrName) return undefined;
31366 var isStr = isString(stateOrName),
31367 name = isStr ? stateOrName : stateOrName.name,
31368 path = isRelative(name);
31371 if (!base) throw new Error("No reference point given for path '" + name + "'");
31372 base = findState(base);
31374 var rel = name.split("."), i = 0, pathLength = rel.length, current = base;
31376 for (; i < pathLength; i++) {
31377 if (rel[i] === "" && i === 0) {
31381 if (rel[i] === "^") {
31382 if (!current.parent) throw new Error("Path '" + name + "' not valid for state '" + base.name + "'");
31383 current = current.parent;
31388 rel = rel.slice(i).join(".");
31389 name = current.name + (current.name && rel ? "." : "") + rel;
31391 var state = states[name];
31393 if (state && (isStr || (!isStr && (state === stateOrName || state.self === stateOrName)))) {
31399 function queueState(parentName, state) {
31400 if (!queue[parentName]) {
31401 queue[parentName] = [];
31403 queue[parentName].push(state);
31406 function flushQueuedChildren(parentName) {
31407 var queued = queue[parentName] || [];
31408 while(queued.length) {
31409 registerState(queued.shift());
31413 function registerState(state) {
31414 // Wrap a new object around the state so we can store our private details easily.
31415 state = inherit(state, {
31417 resolve: state.resolve || {},
31418 toString: function() { return this.name; }
31421 var name = state.name;
31422 if (!isString(name) || name.indexOf('@') >= 0) throw new Error("State must have a valid name");
31423 if (states.hasOwnProperty(name)) throw new Error("State '" + name + "'' is already defined");
31426 var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.'))
31427 : (isString(state.parent)) ? state.parent
31428 : (isObject(state.parent) && isString(state.parent.name)) ? state.parent.name
31431 // If parent is not registered yet, add state to queue and register later
31432 if (parentName && !states[parentName]) {
31433 return queueState(parentName, state.self);
31436 for (var key in stateBuilder) {
31437 if (isFunction(stateBuilder[key])) state[key] = stateBuilder[key](state, stateBuilder.$delegates[key]);
31439 states[name] = state;
31441 // Register the state in the global state list and with $urlRouter if necessary.
31442 if (!state[abstractKey] && state.url) {
31443 $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) {
31444 if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) {
31445 $state.transitionTo(state, $match, { inherit: true, location: false });
31450 // Register any queued children
31451 flushQueuedChildren(name);
31456 // Checks text to see if it looks like a glob.
31457 function isGlob (text) {
31458 return text.indexOf('*') > -1;
31461 // Returns true if glob matches current $state name.
31462 function doesStateMatchGlob (glob) {
31463 var globSegments = glob.split('.'),
31464 segments = $state.$current.name.split('.');
31466 //match single stars
31467 for (var i = 0, l = globSegments.length; i < l; i++) {
31468 if (globSegments[i] === '*') {
31473 //match greedy starts
31474 if (globSegments[0] === '**') {
31475 segments = segments.slice(indexOf(segments, globSegments[1]));
31476 segments.unshift('**');
31478 //match greedy ends
31479 if (globSegments[globSegments.length - 1] === '**') {
31480 segments.splice(indexOf(segments, globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE);
31481 segments.push('**');
31484 if (globSegments.length != segments.length) {
31488 return segments.join('') === globSegments.join('');
31492 // Implicit root state that is always active
31493 root = registerState({
31499 root.navigable = null;
31504 * @name ui.router.state.$stateProvider#decorator
31505 * @methodOf ui.router.state.$stateProvider
31508 * Allows you to extend (carefully) or override (at your own peril) the
31509 * `stateBuilder` object used internally by `$stateProvider`. This can be used
31510 * to add custom functionality to ui-router, for example inferring templateUrl
31511 * based on the state name.
31513 * When passing only a name, it returns the current (original or decorated) builder
31514 * function that matches `name`.
31516 * The builder functions that can be decorated are listed below. Though not all
31517 * necessarily have a good use case for decoration, that is up to you to decide.
31519 * In addition, users can attach custom decorators, which will generate new
31520 * properties within the state's internal definition. There is currently no clear
31521 * use-case for this beyond accessing internal states (i.e. $state.$current),
31522 * however, expect this to become increasingly relevant as we introduce additional
31523 * meta-programming features.
31525 * **Warning**: Decorators should not be interdependent because the order of
31526 * execution of the builder functions in non-deterministic. Builder functions
31527 * should only be dependent on the state definition object and super function.
31530 * Existing builder functions and current return values:
31532 * - **parent** `{object}` - returns the parent state object.
31533 * - **data** `{object}` - returns state data, including any inherited data that is not
31534 * overridden by own values (if any).
31535 * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher}
31537 * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is
31539 * - **params** `{object}` - returns an array of state params that are ensured to
31540 * be a super-set of parent's params.
31541 * - **views** `{object}` - returns a views object where each key is an absolute view
31542 * name (i.e. "viewName@stateName") and each value is the config object
31543 * (template, controller) for the view. Even when you don't use the views object
31544 * explicitly on a state config, one is still created for you internally.
31545 * So by decorating this builder function you have access to decorating template
31546 * and controller properties.
31547 * - **ownParams** `{object}` - returns an array of params that belong to the state,
31548 * not including any params defined by ancestor states.
31549 * - **path** `{string}` - returns the full path from the root down to this state.
31550 * Needed for state activation.
31551 * - **includes** `{object}` - returns an object that includes every state that
31552 * would pass a `$state.includes()` test.
31556 * // Override the internal 'views' builder with a function that takes the state
31557 * // definition, and a reference to the internal function being overridden:
31558 * $stateProvider.decorator('views', function (state, parent) {
31560 * views = parent(state);
31562 * angular.forEach(views, function (config, name) {
31563 * var autoName = (state.name + '.' + name).replace('.', '/');
31564 * config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';
31565 * result[name] = config;
31570 * $stateProvider.state('home', {
31572 * 'contact.list': { controller: 'ListController' },
31573 * 'contact.item': { controller: 'ItemController' }
31579 * $state.go('home');
31580 * // Auto-populates list and item views with /partials/home/contact/list.html,
31581 * // and /partials/home/contact/item.html, respectively.
31584 * @param {string} name The name of the builder function to decorate.
31585 * @param {object} func A function that is responsible for decorating the original
31586 * builder function. The function receives two parameters:
31588 * - `{object}` - state - The state config object.
31589 * - `{object}` - super - The original builder function.
31591 * @return {object} $stateProvider - $stateProvider instance
31593 this.decorator = decorator;
31594 function decorator(name, func) {
31595 /*jshint validthis: true */
31596 if (isString(name) && !isDefined(func)) {
31597 return stateBuilder[name];
31599 if (!isFunction(func) || !isString(name)) {
31602 if (stateBuilder[name] && !stateBuilder.$delegates[name]) {
31603 stateBuilder.$delegates[name] = stateBuilder[name];
31605 stateBuilder[name] = func;
31611 * @name ui.router.state.$stateProvider#state
31612 * @methodOf ui.router.state.$stateProvider
31615 * Registers a state configuration under a given state name. The stateConfig object
31616 * has the following acceptable properties.
31618 * @param {string} name A unique state name, e.g. "home", "about", "contacts".
31619 * To create a parent/child state use a dot, e.g. "about.sales", "home.newest".
31620 * @param {object} stateConfig State configuration object.
31621 * @param {string|function=} stateConfig.template
31622 * <a id='template'></a>
31623 * html template as a string or a function that returns
31624 * an html template as a string which should be used by the uiView directives. This property
31625 * takes precedence over templateUrl.
31627 * If `template` is a function, it will be called with the following parameters:
31629 * - {array.<object>} - state parameters extracted from the current $location.path() by
31630 * applying the current state
31633 * "<h1>inline template definition</h1>" +
31634 * "<div ui-view></div>"</pre>
31635 * <pre>template: function(params) {
31636 * return "<h1>generated template</h1>"; }</pre>
31639 * @param {string|function=} stateConfig.templateUrl
31640 * <a id='templateUrl'></a>
31642 * path or function that returns a path to an html
31643 * template that should be used by uiView.
31645 * If `templateUrl` is a function, it will be called with the following parameters:
31647 * - {array.<object>} - state parameters extracted from the current $location.path() by
31648 * applying the current state
31650 * <pre>templateUrl: "home.html"</pre>
31651 * <pre>templateUrl: function(params) {
31652 * return myTemplates[params.pageId]; }</pre>
31654 * @param {function=} stateConfig.templateProvider
31655 * <a id='templateProvider'></a>
31656 * Provider function that returns HTML content string.
31657 * <pre> templateProvider:
31658 * function(MyTemplateService, params) {
31659 * return MyTemplateService.getTemplate(params.pageId);
31662 * @param {string|function=} stateConfig.controller
31663 * <a id='controller'></a>
31665 * Controller fn that should be associated with newly
31666 * related scope or the name of a registered controller if passed as a string.
31667 * Optionally, the ControllerAs may be declared here.
31668 * <pre>controller: "MyRegisteredController"</pre>
31670 * "MyRegisteredController as fooCtrl"}</pre>
31671 * <pre>controller: function($scope, MyService) {
31672 * $scope.data = MyService.getData(); }</pre>
31674 * @param {function=} stateConfig.controllerProvider
31675 * <a id='controllerProvider'></a>
31677 * Injectable provider function that returns the actual controller or string.
31678 * <pre>controllerProvider:
31679 * function(MyResolveData) {
31680 * if (MyResolveData.foo)
31682 * else if (MyResolveData.bar)
31683 * return "BarCtrl";
31684 * else return function($scope) {
31685 * $scope.baz = "Qux";
31689 * @param {string=} stateConfig.controllerAs
31690 * <a id='controllerAs'></a>
31692 * A controller alias name. If present the controller will be
31693 * published to scope under the controllerAs name.
31694 * <pre>controllerAs: "myCtrl"</pre>
31696 * @param {string|object=} stateConfig.parent
31697 * <a id='parent'></a>
31698 * Optionally specifies the parent state of this state.
31700 * <pre>parent: 'parentState'</pre>
31701 * <pre>parent: parentState // JS variable</pre>
31703 * @param {object=} stateConfig.resolve
31704 * <a id='resolve'></a>
31706 * An optional map<string, function> of dependencies which
31707 * should be injected into the controller. If any of these dependencies are promises,
31708 * the router will wait for them all to be resolved before the controller is instantiated.
31709 * If all the promises are resolved successfully, the $stateChangeSuccess event is fired
31710 * and the values of the resolved promises are injected into any controllers that reference them.
31711 * If any of the promises are rejected the $stateChangeError event is fired.
31713 * The map object is:
31715 * - key - {string}: name of dependency to be injected into controller
31716 * - factory - {string|function}: If string then it is alias for service. Otherwise if function,
31717 * it is injected and return value it treated as dependency. If result is a promise, it is
31718 * resolved before its value is injected into controller.
31722 * function($http, $stateParams) {
31723 * return $http.get("/api/foos/"+stateParams.fooID);
31727 * @param {string=} stateConfig.url
31730 * A url fragment with optional parameters. When a state is navigated or
31731 * transitioned to, the `$stateParams` service will be populated with any
31732 * parameters that were passed.
31734 * (See {@link ui.router.util.type:UrlMatcher UrlMatcher} `UrlMatcher`} for
31735 * more details on acceptable patterns )
31738 * <pre>url: "/home"
31739 * url: "/users/:userid"
31740 * url: "/books/{bookid:[a-zA-Z_-]}"
31741 * url: "/books/{categoryid:int}"
31742 * url: "/books/{publishername:string}/{categoryid:int}"
31743 * url: "/messages?before&after"
31744 * url: "/messages?{before:date}&{after:date}"
31745 * url: "/messages/:mailboxid?{before:date}&{after:date}"
31748 * @param {object=} stateConfig.views
31749 * <a id='views'></a>
31750 * an optional map<string, object> which defined multiple views, or targets views
31751 * manually/explicitly.
31755 * Targets three named `ui-view`s in the parent state's template
31758 * controller: "headerCtrl",
31759 * templateUrl: "header.html"
31761 * controller: "bodyCtrl",
31762 * templateUrl: "body.html"
31764 * controller: "footCtrl",
31765 * templateUrl: "footer.html"
31769 * Targets named `ui-view="header"` from grandparent state 'top''s template, and named `ui-view="body" from parent state's template.
31772 * controller: "msgHeaderCtrl",
31773 * templateUrl: "msgHeader.html"
31775 * controller: "messagesCtrl",
31776 * templateUrl: "messages.html"
31780 * @param {boolean=} [stateConfig.abstract=false]
31781 * <a id='abstract'></a>
31782 * An abstract state will never be directly activated,
31783 * but can provide inherited properties to its common children states.
31784 * <pre>abstract: true</pre>
31786 * @param {function=} stateConfig.onEnter
31787 * <a id='onEnter'></a>
31789 * Callback function for when a state is entered. Good way
31790 * to trigger an action or dispatch an event, such as opening a dialog.
31791 * If minifying your scripts, make sure to explictly annotate this function,
31792 * because it won't be automatically annotated by your build tools.
31794 * <pre>onEnter: function(MyService, $stateParams) {
31795 * MyService.foo($stateParams.myParam);
31798 * @param {function=} stateConfig.onExit
31799 * <a id='onExit'></a>
31801 * Callback function for when a state is exited. Good way to
31802 * trigger an action or dispatch an event, such as opening a dialog.
31803 * If minifying your scripts, make sure to explictly annotate this function,
31804 * because it won't be automatically annotated by your build tools.
31806 * <pre>onExit: function(MyService, $stateParams) {
31807 * MyService.cleanup($stateParams.myParam);
31810 * @param {boolean=} [stateConfig.reloadOnSearch=true]
31811 * <a id='reloadOnSearch'></a>
31813 * If `false`, will not retrigger the same state
31814 * just because a search/query parameter has changed (via $location.search() or $location.hash()).
31815 * Useful for when you'd like to modify $location.search() without triggering a reload.
31816 * <pre>reloadOnSearch: false</pre>
31818 * @param {object=} stateConfig.data
31819 * <a id='data'></a>
31821 * Arbitrary data object, useful for custom configuration. The parent state's `data` is
31822 * prototypally inherited. In other words, adding a data property to a state adds it to
31823 * the entire subtree via prototypal inheritance.
31826 * requiredRole: 'foo'
31829 * @param {object=} stateConfig.params
31830 * <a id='params'></a>
31832 * A map which optionally configures parameters declared in the `url`, or
31833 * defines additional non-url parameters. For each parameter being
31834 * configured, add a configuration object keyed to the name of the parameter.
31836 * Each parameter configuration object may contain the following properties:
31838 * - ** value ** - {object|function=}: specifies the default value for this
31839 * parameter. This implicitly sets this parameter as optional.
31841 * When UI-Router routes to a state and no value is
31842 * specified for this parameter in the URL or transition, the
31843 * default value will be used instead. If `value` is a function,
31844 * it will be injected and invoked, and the return value used.
31846 * *Note*: `undefined` is treated as "no default value" while `null`
31847 * is treated as "the default value is `null`".
31849 * *Shorthand*: If you only need to configure the default value of the
31850 * parameter, you may use a shorthand syntax. In the **`params`**
31851 * map, instead mapping the param name to a full parameter configuration
31852 * object, simply set map it to the default parameter value, e.g.:
31854 * <pre>// define a parameter's default value
31856 * param1: { value: "defaultValue" }
31858 * // shorthand default values
31860 * param1: "defaultValue",
31861 * param2: "param2Default"
31864 * - ** array ** - {boolean=}: *(default: false)* If true, the param value will be
31865 * treated as an array of values. If you specified a Type, the value will be
31866 * treated as an array of the specified Type. Note: query parameter values
31867 * default to a special `"auto"` mode.
31869 * For query parameters in `"auto"` mode, if multiple values for a single parameter
31870 * are present in the URL (e.g.: `/foo?bar=1&bar=2&bar=3`) then the values
31871 * are mapped to an array (e.g.: `{ foo: [ '1', '2', '3' ] }`). However, if
31872 * only one value is present (e.g.: `/foo?bar=1`) then the value is treated as single
31873 * value (e.g.: `{ foo: '1' }`).
31876 * param1: { array: true }
31879 * - ** squash ** - {bool|string=}: `squash` configures how a default parameter value is represented in the URL when
31880 * the current parameter value is the same as the default value. If `squash` is not set, it uses the
31881 * configured default squash policy.
31882 * (See {@link ui.router.util.$urlMatcherFactory#methods_defaultSquashPolicy `defaultSquashPolicy()`})
31884 * There are three squash settings:
31886 * - false: The parameter's default value is not squashed. It is encoded and included in the URL
31887 * - true: The parameter's default value is omitted from the URL. If the parameter is preceeded and followed
31888 * by slashes in the state's `url` declaration, then one of those slashes are omitted.
31889 * This can allow for cleaner looking URLs.
31890 * - `"<arbitrary string>"`: The parameter's default value is replaced with an arbitrary placeholder of your choice.
31894 * value: "defaultId",
31897 * // squash "defaultValue" to "~"
31900 * value: "defaultValue",
31908 * // Some state name examples
31910 * // stateName can be a single top-level name (must be unique).
31911 * $stateProvider.state("home", {});
31913 * // Or it can be a nested state name. This state is a child of the
31914 * // above "home" state.
31915 * $stateProvider.state("home.newest", {});
31917 * // Nest states as deeply as needed.
31918 * $stateProvider.state("home.newest.abc.xyz.inception", {});
31920 * // state() returns $stateProvider, so you can chain state declarations.
31922 * .state("home", {})
31923 * .state("about", {})
31924 * .state("contacts", {});
31928 this.state = state;
31929 function state(name, definition) {
31930 /*jshint validthis: true */
31931 if (isObject(name)) definition = name;
31932 else definition.name = name;
31933 registerState(definition);
31939 * @name ui.router.state.$state
31941 * @requires $rootScope
31943 * @requires ui.router.state.$view
31944 * @requires $injector
31945 * @requires ui.router.util.$resolve
31946 * @requires ui.router.state.$stateParams
31947 * @requires ui.router.router.$urlRouter
31949 * @property {object} params A param object, e.g. {sectionId: section.id)}, that
31950 * you'd like to test against the current active state.
31951 * @property {object} current A reference to the state's config object. However
31952 * you passed it in. Useful for accessing custom data.
31953 * @property {object} transition Currently pending transition. A promise that'll
31954 * resolve or reject.
31957 * `$state` service is responsible for representing states as well as transitioning
31958 * between them. It also provides interfaces to ask for current state or even states
31959 * you're coming from.
31962 $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$urlRouter', '$location', '$urlMatcherFactory'];
31963 function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $urlRouter, $location, $urlMatcherFactory) {
31965 var TransitionSuperseded = $q.reject(new Error('transition superseded'));
31966 var TransitionPrevented = $q.reject(new Error('transition prevented'));
31967 var TransitionAborted = $q.reject(new Error('transition aborted'));
31968 var TransitionFailed = $q.reject(new Error('transition failed'));
31970 // Handles the case where a state which is the target of a transition is not found, and the user
31971 // can optionally retry or defer the transition
31972 function handleRedirect(redirect, state, params, options) {
31975 * @name ui.router.state.$state#$stateNotFound
31976 * @eventOf ui.router.state.$state
31977 * @eventType broadcast on root scope
31979 * Fired when a requested state **cannot be found** using the provided state name during transition.
31980 * The event is broadcast allowing any handlers a single chance to deal with the error (usually by
31981 * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler,
31982 * you can see its three properties in the example. You can use `event.preventDefault()` to abort the
31983 * transition and the promise returned from `go` will be rejected with a `'transition aborted'` value.
31985 * @param {Object} event Event object.
31986 * @param {Object} unfoundState Unfound State information. Contains: `to, toParams, options` properties.
31987 * @param {State} fromState Current state object.
31988 * @param {Object} fromParams Current state params.
31993 * // somewhere, assume lazy.state has not been defined
31994 * $state.go("lazy.state", {a:1, b:2}, {inherit:false});
31996 * // somewhere else
31997 * $scope.$on('$stateNotFound',
31998 * function(event, unfoundState, fromState, fromParams){
31999 * console.log(unfoundState.to); // "lazy.state"
32000 * console.log(unfoundState.toParams); // {a:1, b:2}
32001 * console.log(unfoundState.options); // {inherit:false} + default options
32005 var evt = $rootScope.$broadcast('$stateNotFound', redirect, state, params);
32007 if (evt.defaultPrevented) {
32008 $urlRouter.update();
32009 return TransitionAborted;
32016 // Allow the handler to return a promise to defer state lookup retry
32017 if (options.$retry) {
32018 $urlRouter.update();
32019 return TransitionFailed;
32021 var retryTransition = $state.transition = $q.when(evt.retry);
32023 retryTransition.then(function() {
32024 if (retryTransition !== $state.transition) return TransitionSuperseded;
32025 redirect.options.$retry = true;
32026 return $state.transitionTo(redirect.to, redirect.toParams, redirect.options);
32028 return TransitionAborted;
32030 $urlRouter.update();
32032 return retryTransition;
32035 root.locals = { resolve: null, globals: { $stateParams: {} } };
32039 current: root.self,
32046 * @name ui.router.state.$state#reload
32047 * @methodOf ui.router.state.$state
32050 * A method that force reloads the current state. All resolves are re-resolved,
32051 * controllers reinstantiated, and events re-fired.
32055 * var app angular.module('app', ['ui.router']);
32057 * app.controller('ctrl', function ($scope, $state) {
32058 * $scope.reload = function(){
32064 * `reload()` is just an alias for:
32066 * $state.transitionTo($state.current, $stateParams, {
32067 * reload: true, inherit: false, notify: true
32071 * @param {string=|object=} state - A state name or a state object, which is the root of the resolves to be re-resolved.
32074 * //assuming app application consists of 3 states: 'contacts', 'contacts.detail', 'contacts.detail.item'
32075 * //and current state is 'contacts.detail.item'
32076 * var app angular.module('app', ['ui.router']);
32078 * app.controller('ctrl', function ($scope, $state) {
32079 * $scope.reload = function(){
32080 * //will reload 'contact.detail' and 'contact.detail.item' states
32081 * $state.reload('contact.detail');
32086 * `reload()` is just an alias for:
32088 * $state.transitionTo($state.current, $stateParams, {
32089 * reload: true, inherit: false, notify: true
32093 * @returns {promise} A promise representing the state of the new transition. See
32094 * {@link ui.router.state.$state#methods_go $state.go}.
32096 $state.reload = function reload(state) {
32097 return $state.transitionTo($state.current, $stateParams, { reload: state || true, inherit: false, notify: true});
32102 * @name ui.router.state.$state#go
32103 * @methodOf ui.router.state.$state
32106 * Convenience method for transitioning to a new state. `$state.go` calls
32107 * `$state.transitionTo` internally but automatically sets options to
32108 * `{ location: true, inherit: true, relative: $state.$current, notify: true }`.
32109 * This allows you to easily use an absolute or relative to path and specify
32110 * only the parameters you'd like to update (while letting unspecified parameters
32111 * inherit from the currently active ancestor states).
32115 * var app = angular.module('app', ['ui.router']);
32117 * app.controller('ctrl', function ($scope, $state) {
32118 * $scope.changeState = function () {
32119 * $state.go('contact.detail');
32123 * <img src='../ngdoc_assets/StateGoExamples.png'/>
32125 * @param {string} to Absolute state name or relative state path. Some examples:
32127 * - `$state.go('contact.detail')` - will go to the `contact.detail` state
32128 * - `$state.go('^')` - will go to a parent state
32129 * - `$state.go('^.sibling')` - will go to a sibling state
32130 * - `$state.go('.child.grandchild')` - will go to grandchild state
32132 * @param {object=} params A map of the parameters that will be sent to the state,
32133 * will populate $stateParams. Any parameters that are not specified will be inherited from currently
32134 * defined parameters. This allows, for example, going to a sibling state that shares parameters
32135 * specified in a parent state. Parameter inheritance only works between common ancestor states, I.e.
32136 * transitioning to a sibling will get you the parameters for all parents, transitioning to a child
32137 * will get you all current parameters, etc.
32138 * @param {object=} options Options object. The options are:
32140 * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
32141 * will not. If string, must be `"replace"`, which will update url and also replace last history record.
32142 * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
32143 * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
32144 * defines which state to be relative from.
32145 * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
32146 * - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params
32147 * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
32148 * use this when you want to force a reload when *everything* is the same, including search params.
32150 * @returns {promise} A promise representing the state of the new transition.
32152 * Possible success values:
32156 * <br/>Possible rejection values:
32158 * - 'transition superseded' - when a newer transition has been started after this one
32159 * - 'transition prevented' - when `event.preventDefault()` has been called in a `$stateChangeStart` listener
32160 * - 'transition aborted' - when `event.preventDefault()` has been called in a `$stateNotFound` listener or
32161 * when a `$stateNotFound` `event.retry` promise errors.
32162 * - 'transition failed' - when a state has been unsuccessfully found after 2 tries.
32163 * - *resolve error* - when an error has occurred with a `resolve`
32166 $state.go = function go(to, params, options) {
32167 return $state.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options));
32172 * @name ui.router.state.$state#transitionTo
32173 * @methodOf ui.router.state.$state
32176 * Low-level method for transitioning to a new state. {@link ui.router.state.$state#methods_go $state.go}
32177 * uses `transitionTo` internally. `$state.go` is recommended in most situations.
32181 * var app = angular.module('app', ['ui.router']);
32183 * app.controller('ctrl', function ($scope, $state) {
32184 * $scope.changeState = function () {
32185 * $state.transitionTo('contact.detail');
32190 * @param {string} to State name.
32191 * @param {object=} toParams A map of the parameters that will be sent to the state,
32192 * will populate $stateParams.
32193 * @param {object=} options Options object. The options are:
32195 * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
32196 * will not. If string, must be `"replace"`, which will update url and also replace last history record.
32197 * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url.
32198 * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'),
32199 * defines which state to be relative from.
32200 * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
32201 * - **`reload`** (v0.2.5) - {boolean=false|string=|object=}, If `true` will force transition even if the state or params
32202 * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
32203 * use this when you want to force a reload when *everything* is the same, including search params.
32204 * if String, then will reload the state with the name given in reload, and any children.
32205 * if Object, then a stateObj is expected, will reload the state found in stateObj, and any children.
32207 * @returns {promise} A promise representing the state of the new transition. See
32208 * {@link ui.router.state.$state#methods_go $state.go}.
32210 $state.transitionTo = function transitionTo(to, toParams, options) {
32211 toParams = toParams || {};
32213 location: true, inherit: false, relative: null, notify: true, reload: false, $retry: false
32216 var from = $state.$current, fromParams = $state.params, fromPath = from.path;
32217 var evt, toState = findState(to, options.relative);
32219 // Store the hash param for later (since it will be stripped out by various methods)
32220 var hash = toParams['#'];
32222 if (!isDefined(toState)) {
32223 var redirect = { to: to, toParams: toParams, options: options };
32224 var redirectResult = handleRedirect(redirect, from.self, fromParams, options);
32226 if (redirectResult) {
32227 return redirectResult;
32230 // Always retry once if the $stateNotFound was not prevented
32231 // (handles either redirect changed or state lazy-definition)
32233 toParams = redirect.toParams;
32234 options = redirect.options;
32235 toState = findState(to, options.relative);
32237 if (!isDefined(toState)) {
32238 if (!options.relative) throw new Error("No such state '" + to + "'");
32239 throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'");
32242 if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'");
32243 if (options.inherit) toParams = inheritParams($stateParams, toParams || {}, $state.$current, toState);
32244 if (!toState.params.$$validates(toParams)) return TransitionFailed;
32246 toParams = toState.params.$$values(toParams);
32249 var toPath = to.path;
32251 // Starting from the root of the path, keep all levels that haven't changed
32252 var keep = 0, state = toPath[keep], locals = root.locals, toLocals = [];
32254 if (!options.reload) {
32255 while (state && state === fromPath[keep] && state.ownParams.$$equals(toParams, fromParams)) {
32256 locals = toLocals[keep] = state.locals;
32258 state = toPath[keep];
32260 } else if (isString(options.reload) || isObject(options.reload)) {
32261 if (isObject(options.reload) && !options.reload.name) {
32262 throw new Error('Invalid reload state object');
32265 var reloadState = options.reload === true ? fromPath[0] : findState(options.reload);
32266 if (options.reload && !reloadState) {
32267 throw new Error("No such reload state '" + (isString(options.reload) ? options.reload : options.reload.name) + "'");
32270 while (state && state === fromPath[keep] && state !== reloadState) {
32271 locals = toLocals[keep] = state.locals;
32273 state = toPath[keep];
32277 // If we're going to the same state and all locals are kept, we've got nothing to do.
32278 // But clear 'transition', as we still want to cancel any other pending transitions.
32279 // TODO: We may not want to bump 'transition' if we're called from a location change
32280 // that we've initiated ourselves, because we might accidentally abort a legitimate
32281 // transition initiated from code?
32282 if (shouldSkipReload(to, toParams, from, fromParams, locals, options)) {
32283 if (hash) toParams['#'] = hash;
32284 $state.params = toParams;
32285 copy($state.params, $stateParams);
32286 if (options.location && to.navigable && to.navigable.url) {
32287 $urlRouter.push(to.navigable.url, toParams, {
32288 $$avoidResync: true, replace: options.location === 'replace'
32290 $urlRouter.update(true);
32292 $state.transition = null;
32293 return $q.when($state.current);
32296 // Filter parameters before we pass them to event handlers etc.
32297 toParams = filterByKeys(to.params.$$keys(), toParams || {});
32299 // Broadcast start event and cancel the transition if requested
32300 if (options.notify) {
32303 * @name ui.router.state.$state#$stateChangeStart
32304 * @eventOf ui.router.state.$state
32305 * @eventType broadcast on root scope
32307 * Fired when the state transition **begins**. You can use `event.preventDefault()`
32308 * to prevent the transition from happening and then the transition promise will be
32309 * rejected with a `'transition prevented'` value.
32311 * @param {Object} event Event object.
32312 * @param {State} toState The state being transitioned to.
32313 * @param {Object} toParams The params supplied to the `toState`.
32314 * @param {State} fromState The current state, pre-transition.
32315 * @param {Object} fromParams The params supplied to the `fromState`.
32320 * $rootScope.$on('$stateChangeStart',
32321 * function(event, toState, toParams, fromState, fromParams){
32322 * event.preventDefault();
32323 * // transitionTo() promise will be rejected with
32324 * // a 'transition prevented' error
32328 if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams).defaultPrevented) {
32329 $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams);
32330 $urlRouter.update();
32331 return TransitionPrevented;
32335 // Resolve locals for the remaining states, but don't update any global state just
32336 // yet -- if anything fails to resolve the current state needs to remain untouched.
32337 // We also set up an inheritance chain for the locals here. This allows the view directive
32338 // to quickly look up the correct definition for each view in the current state. Even
32339 // though we create the locals object itself outside resolveState(), it is initially
32340 // empty and gets filled asynchronously. We need to keep track of the promise for the
32341 // (fully resolved) current locals, and pass this down the chain.
32342 var resolved = $q.when(locals);
32344 for (var l = keep; l < toPath.length; l++, state = toPath[l]) {
32345 locals = toLocals[l] = inherit(locals);
32346 resolved = resolveState(state, toParams, state === to, resolved, locals, options);
32349 // Once everything is resolved, we are ready to perform the actual transition
32350 // and return a promise for the new state. We also keep track of what the
32351 // current promise is, so that we can detect overlapping transitions and
32352 // keep only the outcome of the last transition.
32353 var transition = $state.transition = resolved.then(function () {
32354 var l, entering, exiting;
32356 if ($state.transition !== transition) return TransitionSuperseded;
32358 // Exit 'from' states not kept
32359 for (l = fromPath.length - 1; l >= keep; l--) {
32360 exiting = fromPath[l];
32361 if (exiting.self.onExit) {
32362 $injector.invoke(exiting.self.onExit, exiting.self, exiting.locals.globals);
32364 exiting.locals = null;
32367 // Enter 'to' states not kept
32368 for (l = keep; l < toPath.length; l++) {
32369 entering = toPath[l];
32370 entering.locals = toLocals[l];
32371 if (entering.self.onEnter) {
32372 $injector.invoke(entering.self.onEnter, entering.self, entering.locals.globals);
32376 // Re-add the saved hash before we start returning things
32377 if (hash) toParams['#'] = hash;
32379 // Run it again, to catch any transitions in callbacks
32380 if ($state.transition !== transition) return TransitionSuperseded;
32382 // Update globals in $state
32383 $state.$current = to;
32384 $state.current = to.self;
32385 $state.params = toParams;
32386 copy($state.params, $stateParams);
32387 $state.transition = null;
32389 if (options.location && to.navigable) {
32390 $urlRouter.push(to.navigable.url, to.navigable.locals.globals.$stateParams, {
32391 $$avoidResync: true, replace: options.location === 'replace'
32395 if (options.notify) {
32398 * @name ui.router.state.$state#$stateChangeSuccess
32399 * @eventOf ui.router.state.$state
32400 * @eventType broadcast on root scope
32402 * Fired once the state transition is **complete**.
32404 * @param {Object} event Event object.
32405 * @param {State} toState The state being transitioned to.
32406 * @param {Object} toParams The params supplied to the `toState`.
32407 * @param {State} fromState The current state, pre-transition.
32408 * @param {Object} fromParams The params supplied to the `fromState`.
32410 $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams);
32412 $urlRouter.update(true);
32414 return $state.current;
32415 }, function (error) {
32416 if ($state.transition !== transition) return TransitionSuperseded;
32418 $state.transition = null;
32421 * @name ui.router.state.$state#$stateChangeError
32422 * @eventOf ui.router.state.$state
32423 * @eventType broadcast on root scope
32425 * Fired when an **error occurs** during transition. It's important to note that if you
32426 * have any errors in your resolve functions (javascript errors, non-existent services, etc)
32427 * they will not throw traditionally. You must listen for this $stateChangeError event to
32428 * catch **ALL** errors.
32430 * @param {Object} event Event object.
32431 * @param {State} toState The state being transitioned to.
32432 * @param {Object} toParams The params supplied to the `toState`.
32433 * @param {State} fromState The current state, pre-transition.
32434 * @param {Object} fromParams The params supplied to the `fromState`.
32435 * @param {Error} error The resolve error object.
32437 evt = $rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error);
32439 if (!evt.defaultPrevented) {
32440 $urlRouter.update();
32443 return $q.reject(error);
32451 * @name ui.router.state.$state#is
32452 * @methodOf ui.router.state.$state
32455 * Similar to {@link ui.router.state.$state#methods_includes $state.includes},
32456 * but only checks for the full state name. If params is supplied then it will be
32457 * tested for strict equality against the current active params object, so all params
32458 * must match with none missing and no extras.
32462 * $state.$current.name = 'contacts.details.item';
32465 * $state.is('contact.details.item'); // returns true
32466 * $state.is(contactDetailItemStateObject); // returns true
32468 * // relative name (. and ^), typically from a template
32469 * // E.g. from the 'contacts.details' template
32470 * <div ng-class="{highlighted: $state.is('.item')}">Item</div>
32473 * @param {string|object} stateOrName The state name (absolute or relative) or state object you'd like to check.
32474 * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like
32475 * to test against the current active state.
32476 * @param {object=} options An options object. The options are:
32478 * - **`relative`** - {string|object} - If `stateOrName` is a relative state name and `options.relative` is set, .is will
32479 * test relative to `options.relative` state (or name).
32481 * @returns {boolean} Returns true if it is the state.
32483 $state.is = function is(stateOrName, params, options) {
32484 options = extend({ relative: $state.$current }, options || {});
32485 var state = findState(stateOrName, options.relative);
32487 if (!isDefined(state)) { return undefined; }
32488 if ($state.$current !== state) { return false; }
32489 return params ? equalForKeys(state.params.$$values(params), $stateParams) : true;
32494 * @name ui.router.state.$state#includes
32495 * @methodOf ui.router.state.$state
32498 * A method to determine if the current active state is equal to or is the child of the
32499 * state stateName. If any params are passed then they will be tested for a match as well.
32500 * Not all the parameters need to be passed, just the ones you'd like to test for equality.
32503 * Partial and relative names
32505 * $state.$current.name = 'contacts.details.item';
32507 * // Using partial names
32508 * $state.includes("contacts"); // returns true
32509 * $state.includes("contacts.details"); // returns true
32510 * $state.includes("contacts.details.item"); // returns true
32511 * $state.includes("contacts.list"); // returns false
32512 * $state.includes("about"); // returns false
32514 * // Using relative names (. and ^), typically from a template
32515 * // E.g. from the 'contacts.details' template
32516 * <div ng-class="{highlighted: $state.includes('.item')}">Item</div>
32519 * Basic globbing patterns
32521 * $state.$current.name = 'contacts.details.item.url';
32523 * $state.includes("*.details.*.*"); // returns true
32524 * $state.includes("*.details.**"); // returns true
32525 * $state.includes("**.item.**"); // returns true
32526 * $state.includes("*.details.item.url"); // returns true
32527 * $state.includes("*.details.*.url"); // returns true
32528 * $state.includes("*.details.*"); // returns false
32529 * $state.includes("item.**"); // returns false
32532 * @param {string} stateOrName A partial name, relative name, or glob pattern
32533 * to be searched for within the current state name.
32534 * @param {object=} params A param object, e.g. `{sectionId: section.id}`,
32535 * that you'd like to test against the current active state.
32536 * @param {object=} options An options object. The options are:
32538 * - **`relative`** - {string|object=} - If `stateOrName` is a relative state reference and `options.relative` is set,
32539 * .includes will test relative to `options.relative` state (or name).
32541 * @returns {boolean} Returns true if it does include the state
32543 $state.includes = function includes(stateOrName, params, options) {
32544 options = extend({ relative: $state.$current }, options || {});
32545 if (isString(stateOrName) && isGlob(stateOrName)) {
32546 if (!doesStateMatchGlob(stateOrName)) {
32549 stateOrName = $state.$current.name;
32552 var state = findState(stateOrName, options.relative);
32553 if (!isDefined(state)) { return undefined; }
32554 if (!isDefined($state.$current.includes[state.name])) { return false; }
32555 return params ? equalForKeys(state.params.$$values(params), $stateParams, objectKeys(params)) : true;
32561 * @name ui.router.state.$state#href
32562 * @methodOf ui.router.state.$state
32565 * A url generation method that returns the compiled url for the given state populated with the given params.
32569 * expect($state.href("about.person", { person: "bob" })).toEqual("/about/bob");
32572 * @param {string|object} stateOrName The state name or state object you'd like to generate a url from.
32573 * @param {object=} params An object of parameter values to fill the state's required parameters.
32574 * @param {object=} options Options object. The options are:
32576 * - **`lossy`** - {boolean=true} - If true, and if there is no url associated with the state provided in the
32577 * first parameter, then the constructed href url will be built from the first navigable ancestor (aka
32578 * ancestor with a valid url).
32579 * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
32580 * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
32581 * defines which state to be relative from.
32582 * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
32584 * @returns {string} compiled state url
32586 $state.href = function href(stateOrName, params, options) {
32591 relative: $state.$current
32594 var state = findState(stateOrName, options.relative);
32596 if (!isDefined(state)) return null;
32597 if (options.inherit) params = inheritParams($stateParams, params || {}, $state.$current, state);
32599 var nav = (state && options.lossy) ? state.navigable : state;
32601 if (!nav || nav.url === undefined || nav.url === null) {
32604 return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys().concat('#'), params || {}), {
32605 absolute: options.absolute
32611 * @name ui.router.state.$state#get
32612 * @methodOf ui.router.state.$state
32615 * Returns the state configuration object for any specific state or all states.
32617 * @param {string|object=} stateOrName (absolute or relative) If provided, will only get the config for
32618 * the requested state. If not provided, returns an array of ALL state configs.
32619 * @param {string|object=} context When stateOrName is a relative state reference, the state will be retrieved relative to context.
32620 * @returns {Object|Array} State configuration object or array of all objects.
32622 $state.get = function (stateOrName, context) {
32623 if (arguments.length === 0) return map(objectKeys(states), function(name) { return states[name].self; });
32624 var state = findState(stateOrName, context || $state.$current);
32625 return (state && state.self) ? state.self : null;
32628 function resolveState(state, params, paramsAreFiltered, inherited, dst, options) {
32629 // Make a restricted $stateParams with only the parameters that apply to this state if
32630 // necessary. In addition to being available to the controller and onEnter/onExit callbacks,
32631 // we also need $stateParams to be available for any $injector calls we make during the
32632 // dependency resolution process.
32633 var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params);
32634 var locals = { $stateParams: $stateParams };
32636 // Resolve 'global' dependencies for the state, i.e. those not specific to a view.
32637 // We're also including $stateParams in this; that way the parameters are restricted
32638 // to the set that should be visible to the state, and are independent of when we update
32639 // the global $state and $stateParams values.
32640 dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state);
32641 var promises = [dst.resolve.then(function (globals) {
32642 dst.globals = globals;
32644 if (inherited) promises.push(inherited);
32646 function resolveViews() {
32647 var viewsPromises = [];
32649 // Resolve template and dependencies for all views.
32650 forEach(state.views, function (view, name) {
32651 var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {});
32652 injectables.$template = [ function () {
32653 return $view.load(name, { view: view, locals: dst.globals, params: $stateParams, notify: options.notify }) || '';
32656 viewsPromises.push($resolve.resolve(injectables, dst.globals, dst.resolve, state).then(function (result) {
32657 // References to the controller (only instantiated at link time)
32658 if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) {
32659 var injectLocals = angular.extend({}, injectables, dst.globals);
32660 result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals);
32662 result.$$controller = view.controller;
32664 // Provide access to the state itself for internal use
32665 result.$$state = state;
32666 result.$$controllerAs = view.controllerAs;
32667 dst[name] = result;
32671 return $q.all(viewsPromises).then(function(){
32672 return dst.globals;
32676 // Wait for all the promises and then return the activation object
32677 return $q.all(promises).then(resolveViews).then(function (values) {
32685 function shouldSkipReload(to, toParams, from, fromParams, locals, options) {
32686 // Return true if there are no differences in non-search (path/object) params, false if there are differences
32687 function nonSearchParamsEqual(fromAndToState, fromParams, toParams) {
32688 // Identify whether all the parameters that differ between `fromParams` and `toParams` were search params.
32689 function notSearchParam(key) {
32690 return fromAndToState.params[key].location != "search";
32692 var nonQueryParamKeys = fromAndToState.params.$$keys().filter(notSearchParam);
32693 var nonQueryParams = pick.apply({}, [fromAndToState.params].concat(nonQueryParamKeys));
32694 var nonQueryParamSet = new $$UMFP.ParamSet(nonQueryParams);
32695 return nonQueryParamSet.$$equals(fromParams, toParams);
32698 // If reload was not explicitly requested
32699 // and we're transitioning to the same state we're already in
32700 // and the locals didn't change
32701 // or they changed in a way that doesn't merit reloading
32702 // (reloadOnParams:false, or reloadOnSearch.false and only search params changed)
32703 // Then return true.
32704 if (!options.reload && to === from &&
32705 (locals === from.locals || (to.self.reloadOnSearch === false && nonSearchParamsEqual(from, fromParams, toParams)))) {
32711 angular.module('ui.router.state')
32712 .value('$stateParams', {})
32713 .provider('$state', $StateProvider);
32716 $ViewProvider.$inject = [];
32717 function $ViewProvider() {
32722 * @name ui.router.state.$view
32724 * @requires ui.router.util.$templateFactory
32725 * @requires $rootScope
32730 $get.$inject = ['$rootScope', '$templateFactory'];
32731 function $get( $rootScope, $templateFactory) {
32733 // $view.load('full.viewName', { template: ..., controller: ..., resolve: ..., async: false, params: ... })
32736 * @name ui.router.state.$view#load
32737 * @methodOf ui.router.state.$view
32741 * @param {string} name name
32742 * @param {object} options option object.
32744 load: function load(name, options) {
32745 var result, defaults = {
32746 template: null, controller: null, view: null, locals: null, notify: true, async: true, params: {}
32748 options = extend(defaults, options);
32750 if (options.view) {
32751 result = $templateFactory.fromConfig(options.view, options.params, options.locals);
32753 if (result && options.notify) {
32756 * @name ui.router.state.$state#$viewContentLoading
32757 * @eventOf ui.router.state.$view
32758 * @eventType broadcast on root scope
32761 * Fired once the view **begins loading**, *before* the DOM is rendered.
32763 * @param {Object} event Event object.
32764 * @param {Object} viewConfig The view config properties (template, controller, etc).
32769 * $scope.$on('$viewContentLoading',
32770 * function(event, viewConfig){
32771 * // Access to all the view config properties.
32772 * // and one special property 'targetView'
32773 * // viewConfig.targetView
32777 $rootScope.$broadcast('$viewContentLoading', options);
32785 angular.module('ui.router.state').provider('$view', $ViewProvider);
32789 * @name ui.router.state.$uiViewScrollProvider
32792 * Provider that returns the {@link ui.router.state.$uiViewScroll} service function.
32794 function $ViewScrollProvider() {
32796 var useAnchorScroll = false;
32800 * @name ui.router.state.$uiViewScrollProvider#useAnchorScroll
32801 * @methodOf ui.router.state.$uiViewScrollProvider
32804 * Reverts back to using the core [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) service for
32805 * scrolling based on the url anchor.
32807 this.useAnchorScroll = function () {
32808 useAnchorScroll = true;
32813 * @name ui.router.state.$uiViewScroll
32815 * @requires $anchorScroll
32816 * @requires $timeout
32819 * When called with a jqLite element, it scrolls the element into view (after a
32820 * `$timeout` so the DOM has time to refresh).
32822 * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor,
32823 * this can be enabled by calling {@link ui.router.state.$uiViewScrollProvider#methods_useAnchorScroll `$uiViewScrollProvider.useAnchorScroll()`}.
32825 this.$get = ['$anchorScroll', '$timeout', function ($anchorScroll, $timeout) {
32826 if (useAnchorScroll) {
32827 return $anchorScroll;
32830 return function ($element) {
32831 return $timeout(function () {
32832 $element[0].scrollIntoView();
32838 angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider);
32842 * @name ui.router.state.directive:ui-view
32844 * @requires ui.router.state.$state
32845 * @requires $compile
32846 * @requires $controller
32847 * @requires $injector
32848 * @requires ui.router.state.$uiViewScroll
32849 * @requires $document
32854 * The ui-view directive tells $state where to place your templates.
32856 * @param {string=} name A view name. The name should be unique amongst the other views in the
32857 * same state. You can have views of the same name that live in different states.
32859 * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window
32860 * when a view is populated. By default, $anchorScroll is overridden by ui-router's custom scroll
32861 * service, {@link ui.router.state.$uiViewScroll}. This custom service let's you
32862 * scroll ui-view elements into view when they are populated during a state activation.
32864 * *Note: To revert back to old [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll)
32865 * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.*
32867 * @param {string=} onload Expression to evaluate whenever the view updates.
32870 * A view can be unnamed or named.
32873 * <div ui-view></div>
32876 * <div ui-view="viewName"></div>
32879 * You can only have one unnamed view within any template (or root html). If you are only using a
32880 * single view and it is unnamed then you can populate it like so:
32882 * <div ui-view></div>
32883 * $stateProvider.state("home", {
32884 * template: "<h1>HELLO!</h1>"
32888 * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#views `views`}
32889 * config property, by name, in this case an empty name:
32891 * $stateProvider.state("home", {
32894 * template: "<h1>HELLO!</h1>"
32900 * But typically you'll only use the views property if you name your view or have more than one view
32901 * in the same template. There's not really a compelling reason to name a view if its the only one,
32902 * but you could if you wanted, like so:
32904 * <div ui-view="main"></div>
32907 * $stateProvider.state("home", {
32910 * template: "<h1>HELLO!</h1>"
32916 * Really though, you'll use views to set up multiple views:
32918 * <div ui-view></div>
32919 * <div ui-view="chart"></div>
32920 * <div ui-view="data"></div>
32924 * $stateProvider.state("home", {
32927 * template: "<h1>HELLO!</h1>"
32930 * template: "<chart_thing/>"
32933 * template: "<data_thing/>"
32939 * Examples for `autoscroll`:
32942 * <!-- If autoscroll present with no expression,
32943 * then scroll ui-view into view -->
32944 * <ui-view autoscroll/>
32946 * <!-- If autoscroll present with valid expression,
32947 * then scroll ui-view into view if expression evaluates to true -->
32948 * <ui-view autoscroll='true'/>
32949 * <ui-view autoscroll='false'/>
32950 * <ui-view autoscroll='scopeVariable'/>
32953 $ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll', '$interpolate'];
32954 function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate) {
32956 function getService() {
32957 return ($injector.has) ? function(service) {
32958 return $injector.has(service) ? $injector.get(service) : null;
32959 } : function(service) {
32961 return $injector.get(service);
32968 var service = getService(),
32969 $animator = service('$animator'),
32970 $animate = service('$animate');
32972 // Returns a set of DOM manipulation functions based on which Angular version
32974 function getRenderer(attrs, scope) {
32975 var statics = function() {
32977 enter: function (element, target, cb) { target.after(element); cb(); },
32978 leave: function (element, cb) { element.remove(); cb(); }
32984 enter: function(element, target, cb) {
32985 var promise = $animate.enter(element, null, target, cb);
32986 if (promise && promise.then) promise.then(cb);
32988 leave: function(element, cb) {
32989 var promise = $animate.leave(element, cb);
32990 if (promise && promise.then) promise.then(cb);
32996 var animate = $animator && $animator(scope, attrs);
32999 enter: function(element, target, cb) {animate.enter(element, null, target); cb(); },
33000 leave: function(element, cb) { animate.leave(element); cb(); }
33011 transclude: 'element',
33012 compile: function (tElement, tAttrs, $transclude) {
33013 return function (scope, $element, attrs) {
33014 var previousEl, currentEl, currentScope, latestLocals,
33015 onloadExp = attrs.onload || '',
33016 autoScrollExp = attrs.autoscroll,
33017 renderer = getRenderer(attrs, scope);
33019 scope.$on('$stateChangeSuccess', function() {
33022 scope.$on('$viewContentLoading', function() {
33028 function cleanupLastView() {
33030 previousEl.remove();
33034 if (currentScope) {
33035 currentScope.$destroy();
33036 currentScope = null;
33040 renderer.leave(currentEl, function() {
33044 previousEl = currentEl;
33049 function updateView(firstTime) {
33051 name = getUiViewName(scope, attrs, $element, $interpolate),
33052 previousLocals = name && $state.$current && $state.$current.locals[name];
33054 if (!firstTime && previousLocals === latestLocals) return; // nothing to do
33055 newScope = scope.$new();
33056 latestLocals = $state.$current.locals[name];
33058 var clone = $transclude(newScope, function(clone) {
33059 renderer.enter(clone, $element, function onUiViewEnter() {
33061 currentScope.$emit('$viewContentAnimationEnded');
33064 if (angular.isDefined(autoScrollExp) && !autoScrollExp || scope.$eval(autoScrollExp)) {
33065 $uiViewScroll(clone);
33072 currentScope = newScope;
33075 * @name ui.router.state.directive:ui-view#$viewContentLoaded
33076 * @eventOf ui.router.state.directive:ui-view
33077 * @eventType emits on ui-view directive scope
33079 * Fired once the view is **loaded**, *after* the DOM is rendered.
33081 * @param {Object} event Event object.
33083 currentScope.$emit('$viewContentLoaded');
33084 currentScope.$eval(onloadExp);
33093 $ViewDirectiveFill.$inject = ['$compile', '$controller', '$state', '$interpolate'];
33094 function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate) {
33098 compile: function (tElement) {
33099 var initial = tElement.html();
33100 return function (scope, $element, attrs) {
33101 var current = $state.$current,
33102 name = getUiViewName(scope, attrs, $element, $interpolate),
33103 locals = current && current.locals[name];
33109 $element.data('$uiView', { name: name, state: locals.$$state });
33110 $element.html(locals.$template ? locals.$template : initial);
33112 var link = $compile($element.contents());
33114 if (locals.$$controller) {
33115 locals.$scope = scope;
33116 locals.$element = $element;
33117 var controller = $controller(locals.$$controller, locals);
33118 if (locals.$$controllerAs) {
33119 scope[locals.$$controllerAs] = controller;
33121 $element.data('$ngControllerController', controller);
33122 $element.children().data('$ngControllerController', controller);
33132 * Shared ui-view code for both directives:
33133 * Given scope, element, and its attributes, return the view's name
33135 function getUiViewName(scope, attrs, element, $interpolate) {
33136 var name = $interpolate(attrs.uiView || attrs.name || '')(scope);
33137 var inherited = element.inheritedData('$uiView');
33138 return name.indexOf('@') >= 0 ? name : (name + '@' + (inherited ? inherited.state.name : ''));
33141 angular.module('ui.router.state').directive('uiView', $ViewDirective);
33142 angular.module('ui.router.state').directive('uiView', $ViewDirectiveFill);
33144 function parseStateRef(ref, current) {
33145 var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed;
33146 if (preparsed) ref = current + '(' + preparsed[1] + ')';
33147 parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
33148 if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'");
33149 return { state: parsed[1], paramExpr: parsed[3] || null };
33152 function stateContext(el) {
33153 var stateData = el.parent().inheritedData('$uiView');
33155 if (stateData && stateData.state && stateData.state.name) {
33156 return stateData.state;
33162 * @name ui.router.state.directive:ui-sref
33164 * @requires ui.router.state.$state
33165 * @requires $timeout
33170 * A directive that binds a link (`<a>` tag) to a state. If the state has an associated
33171 * URL, the directive will automatically generate & update the `href` attribute via
33172 * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking
33173 * the link will trigger a state transition with optional parameters.
33175 * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
33176 * handled natively by the browser.
33178 * You can also use relative state paths within ui-sref, just like the relative
33179 * paths passed to `$state.go()`. You just need to be aware that the path is relative
33180 * to the state that the link lives in, in other words the state that loaded the
33181 * template containing the link.
33183 * You can specify options to pass to {@link ui.router.state.$state#go $state.go()}
33184 * using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`,
33188 * Here's an example of how you'd use ui-sref and how it would compile. If you have the
33189 * following template:
33191 * <a ui-sref="home">Home</a> | <a ui-sref="about">About</a> | <a ui-sref="{page: 2}">Next page</a>
33194 * <li ng-repeat="contact in contacts">
33195 * <a ui-sref="contacts.detail({ id: contact.id })">{{ contact.name }}</a>
33200 * Then the compiled html would be (assuming Html5Mode is off and current state is contacts):
33202 * <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>
33205 * <li ng-repeat="contact in contacts">
33206 * <a href="#/contacts/1" ui-sref="contacts.detail({ id: contact.id })">Joe</a>
33208 * <li ng-repeat="contact in contacts">
33209 * <a href="#/contacts/2" ui-sref="contacts.detail({ id: contact.id })">Alice</a>
33211 * <li ng-repeat="contact in contacts">
33212 * <a href="#/contacts/3" ui-sref="contacts.detail({ id: contact.id })">Bob</a>
33216 * <a ui-sref="home" ui-sref-opts="{reload: true}">Home</a>
33219 * @param {string} ui-sref 'stateName' can be any valid absolute or relative state
33220 * @param {Object} ui-sref-opts options to pass to {@link ui.router.state.$state#go $state.go()}
33222 $StateRefDirective.$inject = ['$state', '$timeout'];
33223 function $StateRefDirective($state, $timeout) {
33224 var allowedOptions = ['location', 'inherit', 'reload', 'absolute'];
33228 require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
33229 link: function(scope, element, attrs, uiSrefActive) {
33230 var ref = parseStateRef(attrs.uiSref, $state.current.name);
33231 var params = null, url = null, base = stateContext(element) || $state.$current;
33232 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
33233 var hrefKind = Object.prototype.toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
33234 'xlink:href' : 'href';
33235 var newHref = null, isAnchor = element.prop("tagName").toUpperCase() === "A";
33236 var isForm = element[0].nodeName === "FORM";
33237 var attr = isForm ? "action" : hrefKind, nav = true;
33239 var options = { relative: base, inherit: true };
33240 var optionsOverride = scope.$eval(attrs.uiSrefOpts) || {};
33242 angular.forEach(allowedOptions, function(option) {
33243 if (option in optionsOverride) {
33244 options[option] = optionsOverride[option];
33248 var update = function(newVal) {
33249 if (newVal) params = angular.copy(newVal);
33252 newHref = $state.href(ref.state, params, options);
33254 var activeDirective = uiSrefActive[1] || uiSrefActive[0];
33255 if (activeDirective) {
33256 activeDirective.$$addStateInfo(ref.state, params);
33258 if (newHref === null) {
33262 attrs.$set(attr, newHref);
33265 if (ref.paramExpr) {
33266 scope.$watch(ref.paramExpr, function(newVal, oldVal) {
33267 if (newVal !== params) update(newVal);
33269 params = angular.copy(scope.$eval(ref.paramExpr));
33273 if (isForm) return;
33275 element.bind("click", function(e) {
33276 var button = e.which || e.button;
33277 if ( !(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || element.attr('target')) ) {
33278 // HACK: This is to allow ng-clicks to be processed before the transition is initiated:
33279 var transition = $timeout(function() {
33280 $state.go(ref.state, params, options);
33282 e.preventDefault();
33284 // if the state has no URL, ignore one preventDefault from the <a> directive.
33285 var ignorePreventDefaultCount = isAnchor && !newHref ? 1: 0;
33286 e.preventDefault = function() {
33287 if (ignorePreventDefaultCount-- <= 0)
33288 $timeout.cancel(transition);
33298 * @name ui.router.state.directive:ui-sref-active
33300 * @requires ui.router.state.$state
33301 * @requires ui.router.state.$stateParams
33302 * @requires $interpolate
33307 * A directive working alongside ui-sref to add classes to an element when the
33308 * related ui-sref directive's state is active, and removing them when it is inactive.
33309 * The primary use-case is to simplify the special appearance of navigation menus
33310 * relying on `ui-sref`, by having the "active" state's menu button appear different,
33311 * distinguishing it from the inactive menu items.
33313 * ui-sref-active can live on the same element as ui-sref or on a parent element. The first
33314 * ui-sref-active found at the same level or above the ui-sref will be used.
33316 * Will activate when the ui-sref's target state or any child state is active. If you
33317 * need to activate only when the ui-sref target state is active and *not* any of
33318 * it's children, then you will use
33319 * {@link ui.router.state.directive:ui-sref-active-eq ui-sref-active-eq}
33322 * Given the following template:
33325 * <li ui-sref-active="active" class="item">
33326 * <a href ui-sref="app.user({user: 'bilbobaggins'})">@bilbobaggins</a>
33332 * When the app state is "app.user" (or any children states), and contains the state parameter "user" with value "bilbobaggins",
33333 * the resulting HTML will appear as (note the 'active' class):
33336 * <li ui-sref-active="active" class="item active">
33337 * <a ui-sref="app.user({user: 'bilbobaggins'})" href="/users/bilbobaggins">@bilbobaggins</a>
33342 * The class name is interpolated **once** during the directives link time (any further changes to the
33343 * interpolated value are ignored).
33345 * Multiple classes may be specified in a space-separated format:
33348 * <li ui-sref-active='class1 class2 class3'>
33349 * <a ui-sref="app.user">link</a>
33357 * @name ui.router.state.directive:ui-sref-active-eq
33359 * @requires ui.router.state.$state
33360 * @requires ui.router.state.$stateParams
33361 * @requires $interpolate
33366 * The same as {@link ui.router.state.directive:ui-sref-active ui-sref-active} but will only activate
33367 * when the exact target state used in the `ui-sref` is active; no child states.
33370 $StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
33371 function $StateRefActiveDirective($state, $stateParams, $interpolate) {
33374 controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
33375 var states = [], activeClass;
33377 // There probably isn't much point in $observing this
33378 // uiSrefActive and uiSrefActiveEq share the same directive object with some
33379 // slight difference in logic routing
33380 activeClass = $interpolate($attrs.uiSrefActiveEq || $attrs.uiSrefActive || '', false)($scope);
33382 // Allow uiSref to communicate with uiSrefActive[Equals]
33383 this.$$addStateInfo = function (newState, newParams) {
33384 var state = $state.get(newState, stateContext($element));
33387 state: state || { name: newState },
33394 $scope.$on('$stateChangeSuccess', update);
33396 // Update route state
33397 function update() {
33399 $element.addClass(activeClass);
33401 $element.removeClass(activeClass);
33405 function anyMatch() {
33406 for (var i = 0; i < states.length; i++) {
33407 if (isMatch(states[i].state, states[i].params)) {
33414 function isMatch(state, params) {
33415 if (typeof $attrs.uiSrefActiveEq !== 'undefined') {
33416 return $state.is(state.name, params);
33418 return $state.includes(state.name, params);
33425 angular.module('ui.router.state')
33426 .directive('uiSref', $StateRefDirective)
33427 .directive('uiSrefActive', $StateRefActiveDirective)
33428 .directive('uiSrefActiveEq', $StateRefActiveDirective);
33432 * @name ui.router.state.filter:isState
33434 * @requires ui.router.state.$state
33437 * Translates to {@link ui.router.state.$state#methods_is $state.is("stateName")}.
33439 $IsStateFilter.$inject = ['$state'];
33440 function $IsStateFilter($state) {
33441 var isFilter = function (state) {
33442 return $state.is(state);
33444 isFilter.$stateful = true;
33450 * @name ui.router.state.filter:includedByState
33452 * @requires ui.router.state.$state
33455 * Translates to {@link ui.router.state.$state#methods_includes $state.includes('fullOrPartialStateName')}.
33457 $IncludedByStateFilter.$inject = ['$state'];
33458 function $IncludedByStateFilter($state) {
33459 var includesFilter = function (state) {
33460 return $state.includes(state);
33462 includesFilter.$stateful = true;
33463 return includesFilter;
33466 angular.module('ui.router.state')
33467 .filter('isState', $IsStateFilter)
33468 .filter('includedByState', $IncludedByStateFilter);
33469 })(window, window.angular);
33473 /***/ function(module, exports, __webpack_require__) {
33475 __webpack_require__(5);
33476 module.exports = 'ngResource';
33481 /***/ function(module, exports) {
33484 * @license AngularJS v1.4.8
33485 * (c) 2010-2015 Google, Inc. http://angularjs.org
33488 (function(window, angular, undefined) {'use strict';
33490 var $resourceMinErr = angular.$$minErr('$resource');
33492 // Helper functions and regex to lookup a dotted path on an object
33493 // stopping at undefined/null. The path must be composed of ASCII
33494 // identifiers (just like $parse)
33495 var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/;
33497 function isValidDottedPath(path) {
33498 return (path != null && path !== '' && path !== 'hasOwnProperty' &&
33499 MEMBER_NAME_REGEX.test('.' + path));
33502 function lookupDottedPath(obj, path) {
33503 if (!isValidDottedPath(path)) {
33504 throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path);
33506 var keys = path.split('.');
33507 for (var i = 0, ii = keys.length; i < ii && angular.isDefined(obj); i++) {
33509 obj = (obj !== null) ? obj[key] : undefined;
33515 * Create a shallow copy of an object and clear other fields from the destination
33517 function shallowClearAndCopy(src, dst) {
33520 angular.forEach(dst, function(value, key) {
33524 for (var key in src) {
33525 if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
33526 dst[key] = src[key];
33540 * The `ngResource` module provides interaction support with RESTful services
33541 * via the $resource service.
33544 * <div doc-module-components="ngResource"></div>
33546 * See {@link ngResource.$resource `$resource`} for usage.
33555 * A factory which creates a resource object that lets you interact with
33556 * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
33558 * The returned resource object has action methods which provide high-level behaviors without
33559 * the need to interact with the low level {@link ng.$http $http} service.
33561 * Requires the {@link ngResource `ngResource`} module to be installed.
33563 * By default, trailing slashes will be stripped from the calculated URLs,
33564 * which can pose problems with server backends that do not expect that
33565 * behavior. This can be disabled by configuring the `$resourceProvider` like
33569 app.config(['$resourceProvider', function($resourceProvider) {
33570 // Don't strip trailing slashes from calculated URLs
33571 $resourceProvider.defaults.stripTrailingSlashes = false;
33575 * @param {string} url A parameterized URL template with parameters prefixed by `:` as in
33576 * `/user/:username`. If you are using a URL with a port number (e.g.
33577 * `http://example.com:8080/api`), it will be respected.
33579 * If you are using a url with a suffix, just add the suffix, like this:
33580 * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')`
33581 * or even `$resource('http://example.com/resource/:resource_id.:format')`
33582 * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be
33583 * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you
33584 * can escape it with `/\.`.
33586 * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
33587 * `actions` methods. If any of the parameter value is a function, it will be executed every time
33588 * when a param value needs to be obtained for a request (unless the param was overridden).
33590 * Each key value in the parameter object is first bound to url template if present and then any
33591 * excess keys are appended to the url search query after the `?`.
33593 * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
33594 * URL `/path/greet?salutation=Hello`.
33596 * If the parameter value is prefixed with `@` then the value for that parameter will be extracted
33597 * from the corresponding property on the `data` object (provided when calling an action method). For
33598 * example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of `someParam`
33599 * will be `data.someProp`.
33601 * @param {Object.<Object>=} actions Hash with declaration of custom actions that should extend
33602 * the default set of resource actions. The declaration should be created in the format of {@link
33603 * ng.$http#usage $http.config}:
33605 * {action1: {method:?, params:?, isArray:?, headers:?, ...},
33606 * action2: {method:?, params:?, isArray:?, headers:?, ...},
33611 * - **`action`** – {string} – The name of action. This name becomes the name of the method on
33612 * your resource object.
33613 * - **`method`** – {string} – Case insensitive HTTP method (e.g. `GET`, `POST`, `PUT`,
33614 * `DELETE`, `JSONP`, etc).
33615 * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of
33616 * the parameter value is a function, it will be executed every time when a param value needs to
33617 * be obtained for a request (unless the param was overridden).
33618 * - **`url`** – {string} – action specific `url` override. The url templating is supported just
33619 * like for the resource-level urls.
33620 * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array,
33621 * see `returns` section.
33622 * - **`transformRequest`** –
33623 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
33624 * transform function or an array of such functions. The transform function takes the http
33625 * request body and headers and returns its transformed (typically serialized) version.
33626 * By default, transformRequest will contain one function that checks if the request data is
33627 * an object and serializes to using `angular.toJson`. To prevent this behavior, set
33628 * `transformRequest` to an empty array: `transformRequest: []`
33629 * - **`transformResponse`** –
33630 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
33631 * transform function or an array of such functions. The transform function takes the http
33632 * response body and headers and returns its transformed (typically deserialized) version.
33633 * By default, transformResponse will contain one function that checks if the response looks like
33634 * a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior, set
33635 * `transformResponse` to an empty array: `transformResponse: []`
33636 * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
33637 * GET request, otherwise if a cache instance built with
33638 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
33640 * - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that
33641 * should abort the request when resolved.
33642 * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the
33644 * [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5)
33645 * for more information.
33646 * - **`responseType`** - `{string}` - see
33647 * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType).
33648 * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
33649 * `response` and `responseError`. Both `response` and `responseError` interceptors get called
33650 * with `http response` object. See {@link ng.$http $http interceptors}.
33652 * @param {Object} options Hash with custom settings that should extend the
33653 * default `$resourceProvider` behavior. The only supported option is
33657 * - **`stripTrailingSlashes`** – {boolean} – If true then the trailing
33658 * slashes from any calculated URL will be stripped. (Defaults to true.)
33660 * @returns {Object} A resource "class" object with methods for the default set of resource actions
33661 * optionally extended with custom `actions`. The default set contains these actions:
33663 * { 'get': {method:'GET'},
33664 * 'save': {method:'POST'},
33665 * 'query': {method:'GET', isArray:true},
33666 * 'remove': {method:'DELETE'},
33667 * 'delete': {method:'DELETE'} };
33670 * Calling these methods invoke an {@link ng.$http} with the specified http method,
33671 * destination and parameters. When the data is returned from the server then the object is an
33672 * instance of the resource class. The actions `save`, `remove` and `delete` are available on it
33673 * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
33674 * read, update, delete) on server-side data like this:
33676 * var User = $resource('/user/:userId', {userId:'@id'});
33677 * var user = User.get({userId:123}, function() {
33683 * It is important to realize that invoking a $resource object method immediately returns an
33684 * empty reference (object or array depending on `isArray`). Once the data is returned from the
33685 * server the existing reference is populated with the actual data. This is a useful trick since
33686 * usually the resource is assigned to a model which is then rendered by the view. Having an empty
33687 * object results in no rendering, once the data arrives from the server then the object is
33688 * populated with the data and the view automatically re-renders itself showing the new data. This
33689 * means that in most cases one never has to write a callback function for the action methods.
33691 * The action methods on the class object or instance object can be invoked with the following
33694 * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
33695 * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
33696 * - non-GET instance actions: `instance.$action([parameters], [success], [error])`
33699 * Success callback is called with (value, responseHeaders) arguments, where the value is
33700 * the populated resource instance or collection object. The error callback is called
33701 * with (httpResponse) argument.
33703 * Class actions return empty instance (with additional properties below).
33704 * Instance actions return promise of the action.
33706 * The Resource instances and collection have these additional properties:
33708 * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this
33709 * instance or collection.
33711 * On success, the promise is resolved with the same resource instance or collection object,
33712 * updated with data from server. This makes it easy to use in
33713 * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view
33714 * rendering until the resource(s) are loaded.
33716 * On failure, the promise is resolved with the {@link ng.$http http response} object, without
33717 * the `resource` property.
33719 * If an interceptor object was provided, the promise will instead be resolved with the value
33720 * returned by the interceptor.
33722 * - `$resolved`: `true` after first server interaction is completed (either with success or
33723 * rejection), `false` before that. Knowing if the Resource has been resolved is useful in
33728 * # Credit card resource
33731 // Define CreditCard class
33732 var CreditCard = $resource('/user/:userId/card/:cardId',
33733 {userId:123, cardId:'@id'}, {
33734 charge: {method:'POST', params:{charge:true}}
33737 // We can retrieve a collection from the server
33738 var cards = CreditCard.query(function() {
33739 // GET: /user/123/card
33740 // server returns: [ {id:456, number:'1234', name:'Smith'} ];
33742 var card = cards[0];
33743 // each item is an instance of CreditCard
33744 expect(card instanceof CreditCard).toEqual(true);
33745 card.name = "J. Smith";
33746 // non GET methods are mapped onto the instances
33748 // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
33749 // server returns: {id:456, number:'1234', name: 'J. Smith'};
33751 // our custom method is mapped as well.
33752 card.$charge({amount:9.99});
33753 // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
33756 // we can create an instance as well
33757 var newCard = new CreditCard({number:'0123'});
33758 newCard.name = "Mike Smith";
33760 // POST: /user/123/card {number:'0123', name:'Mike Smith'}
33761 // server returns: {id:789, number:'0123', name: 'Mike Smith'};
33762 expect(newCard.id).toEqual(789);
33765 * The object returned from this function execution is a resource "class" which has "static" method
33766 * for each action in the definition.
33768 * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and
33770 * When the data is returned from the server then the object is an instance of the resource type and
33771 * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
33772 * operations (create, read, update, delete) on server-side data.
33775 var User = $resource('/user/:userId', {userId:'@id'});
33776 User.get({userId:123}, function(user) {
33782 * It's worth noting that the success callback for `get`, `query` and other methods gets passed
33783 * in the response that came from the server as well as $http header getter function, so one
33784 * could rewrite the above example and get access to http headers as:
33787 var User = $resource('/user/:userId', {userId:'@id'});
33788 User.get({userId:123}, function(u, getResponseHeaders){
33790 u.$save(function(u, putResponseHeaders) {
33791 //u => saved user object
33792 //putResponseHeaders => $http header getter
33797 * You can also access the raw `$http` promise via the `$promise` property on the object returned
33800 var User = $resource('/user/:userId', {userId:'@id'});
33801 User.get({userId:123})
33802 .$promise.then(function(user) {
33803 $scope.user = user;
33807 * # Creating a custom 'PUT' request
33808 * In this example we create a custom method on our resource to make a PUT request
33810 * var app = angular.module('app', ['ngResource', 'ngRoute']);
33812 * // Some APIs expect a PUT request in the format URL/object/ID
33813 * // Here we are creating an 'update' method
33814 * app.factory('Notes', ['$resource', function($resource) {
33815 * return $resource('/notes/:id', null,
33817 * 'update': { method:'PUT' }
33821 * // In our controller we get the ID from the URL using ngRoute and $routeParams
33822 * // We pass in $routeParams and our Notes factory along with $scope
33823 * app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes',
33824 function($scope, $routeParams, Notes) {
33825 * // First get a note object from the factory
33826 * var note = Notes.get({ id:$routeParams.id });
33829 * // Now call update passing in the ID first then the object you are updating
33830 * Notes.update({ id:$id }, note);
33832 * // This will PUT /notes/ID with the note object in the request payload
33836 angular.module('ngResource', ['ng']).
33837 provider('$resource', function() {
33838 var PROTOCOL_AND_DOMAIN_REGEX = /^https?:\/\/[^\/]*/;
33839 var provider = this;
33842 // Strip slashes by default
33843 stripTrailingSlashes: true,
33845 // Default actions configuration
33847 'get': {method: 'GET'},
33848 'save': {method: 'POST'},
33849 'query': {method: 'GET', isArray: true},
33850 'remove': {method: 'DELETE'},
33851 'delete': {method: 'DELETE'}
33855 this.$get = ['$http', '$q', function($http, $q) {
33857 var noop = angular.noop,
33858 forEach = angular.forEach,
33859 extend = angular.extend,
33860 copy = angular.copy,
33861 isFunction = angular.isFunction;
33864 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
33865 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set
33866 * (pchar) allowed in path segments:
33868 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
33869 * pct-encoded = "%" HEXDIG HEXDIG
33870 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
33871 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
33872 * / "*" / "+" / "," / ";" / "="
33874 function encodeUriSegment(val) {
33875 return encodeUriQuery(val, true).
33876 replace(/%26/gi, '&').
33877 replace(/%3D/gi, '=').
33878 replace(/%2B/gi, '+');
33883 * This method is intended for encoding *key* or *value* parts of query component. We need a
33884 * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't
33885 * have to be encoded per http://tools.ietf.org/html/rfc3986:
33886 * query = *( pchar / "/" / "?" )
33887 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
33888 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
33889 * pct-encoded = "%" HEXDIG HEXDIG
33890 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
33891 * / "*" / "+" / "," / ";" / "="
33893 function encodeUriQuery(val, pctEncodeSpaces) {
33894 return encodeURIComponent(val).
33895 replace(/%40/gi, '@').
33896 replace(/%3A/gi, ':').
33897 replace(/%24/g, '$').
33898 replace(/%2C/gi, ',').
33899 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
33902 function Route(template, defaults) {
33903 this.template = template;
33904 this.defaults = extend({}, provider.defaults, defaults);
33905 this.urlParams = {};
33908 Route.prototype = {
33909 setUrlParams: function(config, params, actionUrl) {
33911 url = actionUrl || self.template,
33914 protocolAndDomain = '';
33916 var urlParams = self.urlParams = {};
33917 forEach(url.split(/\W/), function(param) {
33918 if (param === 'hasOwnProperty') {
33919 throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name.");
33921 if (!(new RegExp("^\\d+$").test(param)) && param &&
33922 (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) {
33923 urlParams[param] = true;
33926 url = url.replace(/\\:/g, ':');
33927 url = url.replace(PROTOCOL_AND_DOMAIN_REGEX, function(match) {
33928 protocolAndDomain = match;
33932 params = params || {};
33933 forEach(self.urlParams, function(_, urlParam) {
33934 val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
33935 if (angular.isDefined(val) && val !== null) {
33936 encodedVal = encodeUriSegment(val);
33937 url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), function(match, p1) {
33938 return encodedVal + p1;
33941 url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match,
33942 leadingSlashes, tail) {
33943 if (tail.charAt(0) == '/') {
33946 return leadingSlashes + tail;
33952 // strip trailing slashes and set the url (unless this behavior is specifically disabled)
33953 if (self.defaults.stripTrailingSlashes) {
33954 url = url.replace(/\/+$/, '') || '/';
33957 // then replace collapse `/.` if found in the last URL path segment before the query
33958 // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x`
33959 url = url.replace(/\/\.(?=\w+($|\?))/, '.');
33960 // replace escaped `/\.` with `/.`
33961 config.url = protocolAndDomain + url.replace(/\/\\\./, '/.');
33964 // set params - delegate param encoding to $http
33965 forEach(params, function(value, key) {
33966 if (!self.urlParams[key]) {
33967 config.params = config.params || {};
33968 config.params[key] = value;
33975 function resourceFactory(url, paramDefaults, actions, options) {
33976 var route = new Route(url, options);
33978 actions = extend({}, provider.defaults.actions, actions);
33980 function extractParams(data, actionParams) {
33982 actionParams = extend({}, paramDefaults, actionParams);
33983 forEach(actionParams, function(value, key) {
33984 if (isFunction(value)) { value = value(); }
33985 ids[key] = value && value.charAt && value.charAt(0) == '@' ?
33986 lookupDottedPath(data, value.substr(1)) : value;
33991 function defaultResponseInterceptor(response) {
33992 return response.resource;
33995 function Resource(value) {
33996 shallowClearAndCopy(value || {}, this);
33999 Resource.prototype.toJSON = function() {
34000 var data = extend({}, this);
34001 delete data.$promise;
34002 delete data.$resolved;
34006 forEach(actions, function(action, name) {
34007 var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method);
34009 Resource[name] = function(a1, a2, a3, a4) {
34010 var params = {}, data, success, error;
34012 /* jshint -W086 */ /* (purposefully fall through case statements) */
34013 switch (arguments.length) {
34020 if (isFunction(a2)) {
34021 if (isFunction(a1)) {
34037 if (isFunction(a1)) success = a1;
34038 else if (hasBody) data = a1;
34043 throw $resourceMinErr('badargs',
34044 "Expected up to 4 arguments [params, data, success, error], got {0} arguments",
34047 /* jshint +W086 */ /* (purposefully fall through case statements) */
34049 var isInstanceCall = this instanceof Resource;
34050 var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data));
34051 var httpConfig = {};
34052 var responseInterceptor = action.interceptor && action.interceptor.response ||
34053 defaultResponseInterceptor;
34054 var responseErrorInterceptor = action.interceptor && action.interceptor.responseError ||
34057 forEach(action, function(value, key) {
34060 httpConfig[key] = copy(value);
34064 case 'interceptor':
34067 httpConfig[key] = value;
34072 if (hasBody) httpConfig.data = data;
34073 route.setUrlParams(httpConfig,
34074 extend({}, extractParams(data, action.params || {}), params),
34077 var promise = $http(httpConfig).then(function(response) {
34078 var data = response.data,
34079 promise = value.$promise;
34082 // Need to convert action.isArray to boolean in case it is undefined
34084 if (angular.isArray(data) !== (!!action.isArray)) {
34085 throw $resourceMinErr('badcfg',
34086 'Error in resource configuration for action `{0}`. Expected response to ' +
34087 'contain an {1} but got an {2} (Request: {3} {4})', name, action.isArray ? 'array' : 'object',
34088 angular.isArray(data) ? 'array' : 'object', httpConfig.method, httpConfig.url);
34091 if (action.isArray) {
34093 forEach(data, function(item) {
34094 if (typeof item === "object") {
34095 value.push(new Resource(item));
34097 // Valid JSON values may be string literals, and these should not be converted
34098 // into objects. These items will not have access to the Resource prototype
34099 // methods, but unfortunately there
34104 shallowClearAndCopy(data, value);
34105 value.$promise = promise;
34109 value.$resolved = true;
34111 response.resource = value;
34114 }, function(response) {
34115 value.$resolved = true;
34117 (error || noop)(response);
34119 return $q.reject(response);
34122 promise = promise.then(
34123 function(response) {
34124 var value = responseInterceptor(response);
34125 (success || noop)(value, response.headers);
34128 responseErrorInterceptor);
34130 if (!isInstanceCall) {
34131 // we are creating instance / collection
34132 // - set the initial promise
34133 // - return the instance / collection
34134 value.$promise = promise;
34135 value.$resolved = false;
34145 Resource.prototype['$' + name] = function(params, success, error) {
34146 if (isFunction(params)) {
34147 error = success; success = params; params = {};
34149 var result = Resource[name].call(this, params, this, success, error);
34150 return result.$promise || result;
34154 Resource.bind = function(additionalParamDefaults) {
34155 return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
34161 return resourceFactory;
34166 })(window, window.angular);
34171 /***/ function(module, exports, __webpack_require__) {
34173 __webpack_require__(7);
34175 module.exports = 'ui.bootstrap';
34180 /***/ function(module, exports) {
34183 * angular-ui-bootstrap
34184 * http://angular-ui.github.io/bootstrap/
34186 * Version: 1.0.0 - 2016-01-08
34189 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"]);
34190 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"]);
34191 angular.module('ui.bootstrap.collapse', [])
34193 .directive('uibCollapse', ['$animate', '$injector', function($animate, $injector) {
34194 var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null;
34196 link: function(scope, element, attrs) {
34197 if (!scope.$eval(attrs.uibCollapse)) {
34198 element.addClass('in')
34199 .addClass('collapse')
34200 .css({height: 'auto'});
34203 function expand() {
34204 element.removeClass('collapse')
34205 .addClass('collapsing')
34206 .attr('aria-expanded', true)
34207 .attr('aria-hidden', false);
34210 $animateCss(element, {
34213 to: { height: element[0].scrollHeight + 'px' }
34214 }).start()['finally'](expandDone);
34216 $animate.addClass(element, 'in', {
34217 to: { height: element[0].scrollHeight + 'px' }
34218 }).then(expandDone);
34222 function expandDone() {
34223 element.removeClass('collapsing')
34224 .addClass('collapse')
34225 .css({height: 'auto'});
34228 function collapse() {
34229 if (!element.hasClass('collapse') && !element.hasClass('in')) {
34230 return collapseDone();
34234 // IMPORTANT: The height must be set before adding "collapsing" class.
34235 // Otherwise, the browser attempts to animate from height 0 (in
34236 // collapsing class) to the given height here.
34237 .css({height: element[0].scrollHeight + 'px'})
34238 // initially all panel collapse have the collapse class, this removal
34239 // prevents the animation from jumping to collapsed state
34240 .removeClass('collapse')
34241 .addClass('collapsing')
34242 .attr('aria-expanded', false)
34243 .attr('aria-hidden', true);
34246 $animateCss(element, {
34249 }).start()['finally'](collapseDone);
34251 $animate.removeClass(element, 'in', {
34253 }).then(collapseDone);
34257 function collapseDone() {
34258 element.css({height: '0'}); // Required so that collapse works when animation is disabled
34259 element.removeClass('collapsing')
34260 .addClass('collapse');
34263 scope.$watch(attrs.uibCollapse, function(shouldCollapse) {
34264 if (shouldCollapse) {
34274 angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
34276 .constant('uibAccordionConfig', {
34280 .controller('UibAccordionController', ['$scope', '$attrs', 'uibAccordionConfig', function($scope, $attrs, accordionConfig) {
34281 // This array keeps track of the accordion groups
34284 // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
34285 this.closeOthers = function(openGroup) {
34286 var closeOthers = angular.isDefined($attrs.closeOthers) ?
34287 $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
34289 angular.forEach(this.groups, function(group) {
34290 if (group !== openGroup) {
34291 group.isOpen = false;
34297 // This is called from the accordion-group directive to add itself to the accordion
34298 this.addGroup = function(groupScope) {
34300 this.groups.push(groupScope);
34302 groupScope.$on('$destroy', function(event) {
34303 that.removeGroup(groupScope);
34307 // This is called from the accordion-group directive when to remove itself
34308 this.removeGroup = function(group) {
34309 var index = this.groups.indexOf(group);
34310 if (index !== -1) {
34311 this.groups.splice(index, 1);
34316 // The accordion directive simply sets up the directive controller
34317 // and adds an accordion CSS class to itself element.
34318 .directive('uibAccordion', function() {
34320 controller: 'UibAccordionController',
34321 controllerAs: 'accordion',
34323 templateUrl: function(element, attrs) {
34324 return attrs.templateUrl || 'uib/template/accordion/accordion.html';
34329 // The accordion-group directive indicates a block of html that will expand and collapse in an accordion
34330 .directive('uibAccordionGroup', function() {
34332 require: '^uibAccordion', // We need this directive to be inside an accordion
34333 transclude: true, // It transcludes the contents of the directive into the template
34334 replace: true, // The element containing the directive will be replaced with the template
34335 templateUrl: function(element, attrs) {
34336 return attrs.templateUrl || 'uib/template/accordion/accordion-group.html';
34339 heading: '@', // Interpolate the heading attribute onto this scope
34343 controller: function() {
34344 this.setHeading = function(element) {
34345 this.heading = element;
34348 link: function(scope, element, attrs, accordionCtrl) {
34349 accordionCtrl.addGroup(scope);
34351 scope.openClass = attrs.openClass || 'panel-open';
34352 scope.panelClass = attrs.panelClass || 'panel-default';
34353 scope.$watch('isOpen', function(value) {
34354 element.toggleClass(scope.openClass, !!value);
34356 accordionCtrl.closeOthers(scope);
34360 scope.toggleOpen = function($event) {
34361 if (!scope.isDisabled) {
34362 if (!$event || $event.which === 32) {
34363 scope.isOpen = !scope.isOpen;
34371 // Use accordion-heading below an accordion-group to provide a heading containing HTML
34372 .directive('uibAccordionHeading', function() {
34374 transclude: true, // Grab the contents to be used as the heading
34375 template: '', // In effect remove this element!
34377 require: '^uibAccordionGroup',
34378 link: function(scope, element, attrs, accordionGroupCtrl, transclude) {
34379 // Pass the heading to the accordion-group controller
34380 // so that it can be transcluded into the right place in the template
34381 // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
34382 accordionGroupCtrl.setHeading(transclude(scope, angular.noop));
34387 // Use in the accordion-group template to indicate where you want the heading to be transcluded
34388 // You must provide the property on the accordion-group controller that will hold the transcluded element
34389 .directive('uibAccordionTransclude', function() {
34391 require: '^uibAccordionGroup',
34392 link: function(scope, element, attrs, controller) {
34393 scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) {
34395 element.find('span').html('');
34396 element.find('span').append(heading);
34403 angular.module('ui.bootstrap.alert', [])
34405 .controller('UibAlertController', ['$scope', '$attrs', '$interpolate', '$timeout', function($scope, $attrs, $interpolate, $timeout) {
34406 $scope.closeable = !!$attrs.close;
34408 var dismissOnTimeout = angular.isDefined($attrs.dismissOnTimeout) ?
34409 $interpolate($attrs.dismissOnTimeout)($scope.$parent) : null;
34411 if (dismissOnTimeout) {
34412 $timeout(function() {
34414 }, parseInt(dismissOnTimeout, 10));
34418 .directive('uibAlert', function() {
34420 controller: 'UibAlertController',
34421 controllerAs: 'alert',
34422 templateUrl: function(element, attrs) {
34423 return attrs.templateUrl || 'uib/template/alert/alert.html';
34434 angular.module('ui.bootstrap.buttons', [])
34436 .constant('uibButtonConfig', {
34437 activeClass: 'active',
34438 toggleEvent: 'click'
34441 .controller('UibButtonsController', ['uibButtonConfig', function(buttonConfig) {
34442 this.activeClass = buttonConfig.activeClass || 'active';
34443 this.toggleEvent = buttonConfig.toggleEvent || 'click';
34446 .directive('uibBtnRadio', ['$parse', function($parse) {
34448 require: ['uibBtnRadio', 'ngModel'],
34449 controller: 'UibButtonsController',
34450 controllerAs: 'buttons',
34451 link: function(scope, element, attrs, ctrls) {
34452 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
34453 var uncheckableExpr = $parse(attrs.uibUncheckable);
34455 element.find('input').css({display: 'none'});
34458 ngModelCtrl.$render = function() {
34459 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.uibBtnRadio)));
34463 element.on(buttonsCtrl.toggleEvent, function() {
34464 if (attrs.disabled) {
34468 var isActive = element.hasClass(buttonsCtrl.activeClass);
34470 if (!isActive || angular.isDefined(attrs.uncheckable)) {
34471 scope.$apply(function() {
34472 ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.uibBtnRadio));
34473 ngModelCtrl.$render();
34478 if (attrs.uibUncheckable) {
34479 scope.$watch(uncheckableExpr, function(uncheckable) {
34480 attrs.$set('uncheckable', uncheckable ? '' : null);
34487 .directive('uibBtnCheckbox', function() {
34489 require: ['uibBtnCheckbox', 'ngModel'],
34490 controller: 'UibButtonsController',
34491 controllerAs: 'button',
34492 link: function(scope, element, attrs, ctrls) {
34493 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
34495 element.find('input').css({display: 'none'});
34497 function getTrueValue() {
34498 return getCheckboxValue(attrs.btnCheckboxTrue, true);
34501 function getFalseValue() {
34502 return getCheckboxValue(attrs.btnCheckboxFalse, false);
34505 function getCheckboxValue(attribute, defaultValue) {
34506 return angular.isDefined(attribute) ? scope.$eval(attribute) : defaultValue;
34510 ngModelCtrl.$render = function() {
34511 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
34515 element.on(buttonsCtrl.toggleEvent, function() {
34516 if (attrs.disabled) {
34520 scope.$apply(function() {
34521 ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
34522 ngModelCtrl.$render();
34529 angular.module('ui.bootstrap.carousel', [])
34531 .controller('UibCarouselController', ['$scope', '$element', '$interval', '$timeout', '$animate', function($scope, $element, $interval, $timeout, $animate) {
34533 slides = self.slides = $scope.slides = [],
34534 SLIDE_DIRECTION = 'uib-slideDirection',
34536 currentInterval, isPlaying, bufferedTransitions = [];
34537 self.currentSlide = null;
34539 var destroyed = false;
34541 self.addSlide = function(slide, element) {
34542 slide.$element = element;
34543 slides.push(slide);
34544 //if this is the first slide or the slide is set to active, select it
34545 if (slides.length === 1 || slide.active) {
34546 if ($scope.$currentTransition) {
34547 $scope.$currentTransition = null;
34550 self.select(slides[slides.length - 1]);
34551 if (slides.length === 1) {
34555 slide.active = false;
34559 self.getCurrentIndex = function() {
34560 if (self.currentSlide && angular.isDefined(self.currentSlide.index)) {
34561 return +self.currentSlide.index;
34563 return currentIndex;
34566 self.next = $scope.next = function() {
34567 var newIndex = (self.getCurrentIndex() + 1) % slides.length;
34569 if (newIndex === 0 && $scope.noWrap()) {
34574 return self.select(getSlideByIndex(newIndex), 'next');
34577 self.prev = $scope.prev = function() {
34578 var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1;
34580 if ($scope.noWrap() && newIndex === slides.length - 1) {
34585 return self.select(getSlideByIndex(newIndex), 'prev');
34588 self.removeSlide = function(slide) {
34589 if (angular.isDefined(slide.index)) {
34590 slides.sort(function(a, b) {
34591 return +a.index > +b.index;
34595 var bufferedIndex = bufferedTransitions.indexOf(slide);
34596 if (bufferedIndex !== -1) {
34597 bufferedTransitions.splice(bufferedIndex, 1);
34599 //get the index of the slide inside the carousel
34600 var index = slides.indexOf(slide);
34601 slides.splice(index, 1);
34602 $timeout(function() {
34603 if (slides.length > 0 && slide.active) {
34604 if (index >= slides.length) {
34605 self.select(slides[index - 1]);
34607 self.select(slides[index]);
34609 } else if (currentIndex > index) {
34614 //clean the currentSlide when no more slide
34615 if (slides.length === 0) {
34616 self.currentSlide = null;
34617 clearBufferedTransitions();
34621 /* direction: "prev" or "next" */
34622 self.select = $scope.select = function(nextSlide, direction) {
34623 var nextIndex = $scope.indexOfSlide(nextSlide);
34624 //Decide direction if it's not given
34625 if (direction === undefined) {
34626 direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
34628 //Prevent this user-triggered transition from occurring if there is already one in progress
34629 if (nextSlide && nextSlide !== self.currentSlide && !$scope.$currentTransition) {
34630 goNext(nextSlide, nextIndex, direction);
34631 } else if (nextSlide && nextSlide !== self.currentSlide && $scope.$currentTransition) {
34632 bufferedTransitions.push(nextSlide);
34636 /* Allow outside people to call indexOf on slides array */
34637 $scope.indexOfSlide = function(slide) {
34638 return angular.isDefined(slide.index) ? +slide.index : slides.indexOf(slide);
34641 $scope.isActive = function(slide) {
34642 return self.currentSlide === slide;
34645 $scope.pause = function() {
34646 if (!$scope.noPause) {
34652 $scope.play = function() {
34659 $scope.$on('$destroy', function() {
34664 $scope.$watch('noTransition', function(noTransition) {
34665 $animate.enabled($element, !noTransition);
34668 $scope.$watch('interval', restartTimer);
34670 $scope.$watchCollection('slides', resetTransition);
34672 function clearBufferedTransitions() {
34673 while (bufferedTransitions.length) {
34674 bufferedTransitions.shift();
34678 function getSlideByIndex(index) {
34679 if (angular.isUndefined(slides[index].index)) {
34680 return slides[index];
34682 for (var i = 0, l = slides.length; i < l; ++i) {
34683 if (slides[i].index === index) {
34689 function goNext(slide, index, direction) {
34690 if (destroyed) { return; }
34692 angular.extend(slide, {direction: direction, active: true});
34693 angular.extend(self.currentSlide || {}, {direction: direction, active: false});
34694 if ($animate.enabled($element) && !$scope.$currentTransition &&
34695 slide.$element && self.slides.length > 1) {
34696 slide.$element.data(SLIDE_DIRECTION, slide.direction);
34697 if (self.currentSlide && self.currentSlide.$element) {
34698 self.currentSlide.$element.data(SLIDE_DIRECTION, slide.direction);
34701 $scope.$currentTransition = true;
34702 $animate.on('addClass', slide.$element, function(element, phase) {
34703 if (phase === 'close') {
34704 $scope.$currentTransition = null;
34705 $animate.off('addClass', element);
34706 if (bufferedTransitions.length) {
34707 var nextSlide = bufferedTransitions.pop();
34708 var nextIndex = $scope.indexOfSlide(nextSlide);
34709 var nextDirection = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
34710 clearBufferedTransitions();
34712 goNext(nextSlide, nextIndex, nextDirection);
34718 self.currentSlide = slide;
34719 currentIndex = index;
34721 //every time you change slides, reset the timer
34725 function resetTimer() {
34726 if (currentInterval) {
34727 $interval.cancel(currentInterval);
34728 currentInterval = null;
34732 function resetTransition(slides) {
34733 if (!slides.length) {
34734 $scope.$currentTransition = null;
34735 clearBufferedTransitions();
34739 function restartTimer() {
34741 var interval = +$scope.interval;
34742 if (!isNaN(interval) && interval > 0) {
34743 currentInterval = $interval(timerFn, interval);
34747 function timerFn() {
34748 var interval = +$scope.interval;
34749 if (isPlaying && !isNaN(interval) && interval > 0 && slides.length) {
34757 .directive('uibCarousel', function() {
34761 controller: 'UibCarouselController',
34762 controllerAs: 'carousel',
34763 templateUrl: function(element, attrs) {
34764 return attrs.templateUrl || 'uib/template/carousel/carousel.html';
34775 .directive('uibSlide', function() {
34777 require: '^uibCarousel',
34780 templateUrl: function(element, attrs) {
34781 return attrs.templateUrl || 'uib/template/carousel/slide.html';
34788 link: function (scope, element, attrs, carouselCtrl) {
34789 carouselCtrl.addSlide(scope, element);
34790 //when the scope is destroyed then remove the slide from the current slides array
34791 scope.$on('$destroy', function() {
34792 carouselCtrl.removeSlide(scope);
34795 scope.$watch('active', function(active) {
34797 carouselCtrl.select(scope);
34804 .animation('.item', ['$animateCss',
34805 function($animateCss) {
34806 var SLIDE_DIRECTION = 'uib-slideDirection';
34808 function removeClass(element, className, callback) {
34809 element.removeClass(className);
34816 beforeAddClass: function(element, className, done) {
34817 if (className === 'active') {
34818 var stopped = false;
34819 var direction = element.data(SLIDE_DIRECTION);
34820 var directionClass = direction === 'next' ? 'left' : 'right';
34821 var removeClassFn = removeClass.bind(this, element,
34822 directionClass + ' ' + direction, done);
34823 element.addClass(direction);
34825 $animateCss(element, {addClass: directionClass})
34827 .done(removeClassFn);
34829 return function() {
34835 beforeRemoveClass: function (element, className, done) {
34836 if (className === 'active') {
34837 var stopped = false;
34838 var direction = element.data(SLIDE_DIRECTION);
34839 var directionClass = direction === 'next' ? 'left' : 'right';
34840 var removeClassFn = removeClass.bind(this, element, directionClass, done);
34842 $animateCss(element, {addClass: directionClass})
34844 .done(removeClassFn);
34846 return function() {
34855 angular.module('ui.bootstrap.dateparser', [])
34857 .service('uibDateParser', ['$log', '$locale', 'orderByFilter', function($log, $locale, orderByFilter) {
34858 // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js
34859 var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
34862 var formatCodeToRegex;
34864 this.init = function() {
34865 localeId = $locale.id;
34869 formatCodeToRegex = [
34873 apply: function(value) { this.year = +value; }
34878 apply: function(value) { this.year = +value + 2000; }
34883 apply: function(value) { this.year = +value; }
34887 regex: '0?[1-9]|1[0-2]',
34888 apply: function(value) { this.month = value - 1; }
34892 regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
34893 apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); }
34897 regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
34898 apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); }
34902 regex: '0[1-9]|1[0-2]',
34903 apply: function(value) { this.month = value - 1; }
34907 regex: '[1-9]|1[0-2]',
34908 apply: function(value) { this.month = value - 1; }
34912 regex: '[0-2]?[0-9]{1}|3[0-1]{1}',
34913 apply: function(value) { this.date = +value; }
34917 regex: '[0-2][0-9]{1}|3[0-1]{1}',
34918 apply: function(value) { this.date = +value; }
34922 regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
34923 apply: function(value) { this.date = +value; }
34927 regex: $locale.DATETIME_FORMATS.DAY.join('|')
34931 regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|')
34935 regex: '(?:0|1)[0-9]|2[0-3]',
34936 apply: function(value) { this.hours = +value; }
34940 regex: '0[0-9]|1[0-2]',
34941 apply: function(value) { this.hours = +value; }
34945 regex: '1?[0-9]|2[0-3]',
34946 apply: function(value) { this.hours = +value; }
34950 regex: '[0-9]|1[0-2]',
34951 apply: function(value) { this.hours = +value; }
34955 regex: '[0-5][0-9]',
34956 apply: function(value) { this.minutes = +value; }
34960 regex: '[0-9]|[1-5][0-9]',
34961 apply: function(value) { this.minutes = +value; }
34965 regex: '[0-9][0-9][0-9]',
34966 apply: function(value) { this.milliseconds = +value; }
34970 regex: '[0-5][0-9]',
34971 apply: function(value) { this.seconds = +value; }
34975 regex: '[0-9]|[1-5][0-9]',
34976 apply: function(value) { this.seconds = +value; }
34980 regex: $locale.DATETIME_FORMATS.AMPMS.join('|'),
34981 apply: function(value) {
34982 if (this.hours === 12) {
34986 if (value === 'PM') {
34993 regex: '[+-]\\d{4}',
34994 apply: function(value) {
34995 var matches = value.match(/([+-])(\d{2})(\d{2})/),
34997 hours = matches[2],
34998 minutes = matches[3];
34999 this.hours += toInt(sign + hours);
35000 this.minutes += toInt(sign + minutes);
35005 regex: '[0-4][0-9]|5[0-3]'
35009 regex: '[0-9]|[1-4][0-9]|5[0-3]'
35013 regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\s/g, '\\s')
35017 regex: $locale.DATETIME_FORMATS.ERAS.join('|')
35021 regex: $locale.DATETIME_FORMATS.ERAS.join('|')
35025 regex: $locale.DATETIME_FORMATS.ERAS.join('|')
35032 function createParser(format) {
35033 var map = [], regex = format.split('');
35035 // check for literal values
35036 var quoteIndex = format.indexOf('\'');
35037 if (quoteIndex > -1) {
35038 var inLiteral = false;
35039 format = format.split('');
35040 for (var i = quoteIndex; i < format.length; i++) {
35042 if (format[i] === '\'') {
35043 if (i + 1 < format.length && format[i+1] === '\'') { // escaped single quote
35046 } else { // end of literal
35053 if (format[i] === '\'') { // start of literal
35061 format = format.join('');
35064 angular.forEach(formatCodeToRegex, function(data) {
35065 var index = format.indexOf(data.key);
35068 format = format.split('');
35070 regex[index] = '(' + data.regex + ')';
35071 format[index] = '$'; // Custom symbol to define consumed part of format
35072 for (var i = index + 1, n = index + data.key.length; i < n; i++) {
35076 format = format.join('');
35081 matcher: data.regex
35087 regex: new RegExp('^' + regex.join('') + '$'),
35088 map: orderByFilter(map, 'index')
35092 this.parse = function(input, format, baseDate) {
35093 if (!angular.isString(input) || !format) {
35097 format = $locale.DATETIME_FORMATS[format] || format;
35098 format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&');
35100 if ($locale.id !== localeId) {
35104 if (!this.parsers[format]) {
35105 this.parsers[format] = createParser(format);
35108 var parser = this.parsers[format],
35109 regex = parser.regex,
35111 results = input.match(regex),
35113 if (results && results.length) {
35115 if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) {
35117 year: baseDate.getFullYear(),
35118 month: baseDate.getMonth(),
35119 date: baseDate.getDate(),
35120 hours: baseDate.getHours(),
35121 minutes: baseDate.getMinutes(),
35122 seconds: baseDate.getSeconds(),
35123 milliseconds: baseDate.getMilliseconds()
35127 $log.warn('dateparser:', 'baseDate is not a valid date');
35129 fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 };
35132 for (var i = 1, n = results.length; i < n; i++) {
35133 var mapper = map[i - 1];
35134 if (mapper.matcher === 'Z') {
35138 if (mapper.apply) {
35139 mapper.apply.call(fields, results[i]);
35143 var datesetter = tzOffset ? Date.prototype.setUTCFullYear :
35144 Date.prototype.setFullYear;
35145 var timesetter = tzOffset ? Date.prototype.setUTCHours :
35146 Date.prototype.setHours;
35148 if (isValid(fields.year, fields.month, fields.date)) {
35149 if (angular.isDate(baseDate) && !isNaN(baseDate.getTime()) && !tzOffset) {
35150 dt = new Date(baseDate);
35151 datesetter.call(dt, fields.year, fields.month, fields.date);
35152 timesetter.call(dt, fields.hours, fields.minutes,
35153 fields.seconds, fields.milliseconds);
35156 datesetter.call(dt, fields.year, fields.month, fields.date);
35157 timesetter.call(dt, fields.hours || 0, fields.minutes || 0,
35158 fields.seconds || 0, fields.milliseconds || 0);
35166 // Check if date is valid for specific month (and year for February).
35167 // Month: 0 = Jan, 1 = Feb, etc
35168 function isValid(year, month, date) {
35173 if (month === 1 && date > 28) {
35174 return date === 29 && (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0);
35177 if (month === 3 || month === 5 || month === 8 || month === 10) {
35184 function toInt(str) {
35185 return parseInt(str, 10);
35188 this.toTimezone = toTimezone;
35189 this.fromTimezone = fromTimezone;
35190 this.timezoneToOffset = timezoneToOffset;
35191 this.addDateMinutes = addDateMinutes;
35192 this.convertTimezoneToLocal = convertTimezoneToLocal;
35194 function toTimezone(date, timezone) {
35195 return date && timezone ? convertTimezoneToLocal(date, timezone) : date;
35198 function fromTimezone(date, timezone) {
35199 return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date;
35202 //https://github.com/angular/angular.js/blob/4daafd3dbe6a80d578f5a31df1bb99c77559543e/src/Angular.js#L1207
35203 function timezoneToOffset(timezone, fallback) {
35204 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
35205 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
35208 function addDateMinutes(date, minutes) {
35209 date = new Date(date.getTime());
35210 date.setMinutes(date.getMinutes() + minutes);
35214 function convertTimezoneToLocal(date, timezone, reverse) {
35215 reverse = reverse ? -1 : 1;
35216 var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
35217 return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
35221 // Avoiding use of ng-class as it creates a lot of watchers when a class is to be applied to
35222 // at most one element.
35223 angular.module('ui.bootstrap.isClass', [])
35224 .directive('uibIsClass', [
35226 function ($animate) {
35227 // 11111111 22222222
35228 var ON_REGEXP = /^\s*([\s\S]+?)\s+on\s+([\s\S]+?)\s*$/;
35229 // 11111111 22222222
35230 var IS_REGEXP = /^\s*([\s\S]+?)\s+for\s+([\s\S]+?)\s*$/;
35232 var dataPerTracked = {};
35236 compile: function (tElement, tAttrs) {
35237 var linkedScopes = [];
35238 var instances = [];
35239 var expToData = {};
35240 var lastActivated = null;
35241 var onExpMatches = tAttrs.uibIsClass.match(ON_REGEXP);
35242 var onExp = onExpMatches[2];
35243 var expsStr = onExpMatches[1];
35244 var exps = expsStr.split(',');
35248 function linkFn(scope, element, attrs) {
35249 linkedScopes.push(scope);
35255 exps.forEach(function (exp, k) {
35256 addForExp(exp, scope);
35259 scope.$on('$destroy', removeScope);
35262 function addForExp(exp, scope) {
35263 var matches = exp.match(IS_REGEXP);
35264 var clazz = scope.$eval(matches[1]);
35265 var compareWithExp = matches[2];
35266 var data = expToData[exp];
35268 var watchFn = function (compareWithVal) {
35269 var newActivated = null;
35270 instances.some(function (instance) {
35271 var thisVal = instance.scope.$eval(onExp);
35272 if (thisVal === compareWithVal) {
35273 newActivated = instance;
35277 if (data.lastActivated !== newActivated) {
35278 if (data.lastActivated) {
35279 $animate.removeClass(data.lastActivated.element, clazz);
35281 if (newActivated) {
35282 $animate.addClass(newActivated.element, clazz);
35284 data.lastActivated = newActivated;
35287 expToData[exp] = data = {
35288 lastActivated: null,
35291 compareWithExp: compareWithExp,
35292 watcher: scope.$watch(compareWithExp, watchFn)
35295 data.watchFn(scope.$eval(compareWithExp));
35298 function removeScope(e) {
35299 var removedScope = e.targetScope;
35300 var index = linkedScopes.indexOf(removedScope);
35301 linkedScopes.splice(index, 1);
35302 instances.splice(index, 1);
35303 if (linkedScopes.length) {
35304 var newWatchScope = linkedScopes[0];
35305 angular.forEach(expToData, function (data) {
35306 if (data.scope === removedScope) {
35307 data.watcher = newWatchScope.$watch(data.compareWithExp, data.watchFn);
35308 data.scope = newWatchScope;
35319 angular.module('ui.bootstrap.position', [])
35322 * A set of utility methods for working with the DOM.
35323 * It is meant to be used where we need to absolute-position elements in
35324 * relation to another element (this is the case for tooltips, popovers,
35325 * typeahead suggestions etc.).
35327 .factory('$uibPosition', ['$document', '$window', function($document, $window) {
35329 * Used by scrollbarWidth() function to cache scrollbar's width.
35330 * Do not access this variable directly, use scrollbarWidth() instead.
35332 var SCROLLBAR_WIDTH;
35333 var OVERFLOW_REGEX = {
35334 normal: /(auto|scroll)/,
35335 hidden: /(auto|scroll|hidden)/
35337 var PLACEMENT_REGEX = {
35338 auto: /\s?auto?\s?/i,
35339 primary: /^(top|bottom|left|right)$/,
35340 secondary: /^(top|bottom|left|right|center)$/,
35341 vertical: /^(top|bottom)$/
35347 * Provides a raw DOM element from a jQuery/jQLite element.
35349 * @param {element} elem - The element to convert.
35351 * @returns {element} A HTML element.
35353 getRawNode: function(elem) {
35354 return elem[0] || elem;
35358 * Provides a parsed number for a style property. Strips
35359 * units and casts invalid numbers to 0.
35361 * @param {string} value - The style value to parse.
35363 * @returns {number} A valid number.
35365 parseStyle: function(value) {
35366 value = parseFloat(value);
35367 return isFinite(value) ? value : 0;
35371 * Provides the closest positioned ancestor.
35373 * @param {element} element - The element to get the offest parent for.
35375 * @returns {element} The closest positioned ancestor.
35377 offsetParent: function(elem) {
35378 elem = this.getRawNode(elem);
35380 var offsetParent = elem.offsetParent || $document[0].documentElement;
35382 function isStaticPositioned(el) {
35383 return ($window.getComputedStyle(el).position || 'static') === 'static';
35386 while (offsetParent && offsetParent !== $document[0].documentElement && isStaticPositioned(offsetParent)) {
35387 offsetParent = offsetParent.offsetParent;
35390 return offsetParent || $document[0].documentElement;
35394 * Provides the scrollbar width, concept from TWBS measureScrollbar()
35395 * function in https://github.com/twbs/bootstrap/blob/master/js/modal.js
35397 * @returns {number} The width of the browser scollbar.
35399 scrollbarWidth: function() {
35400 if (angular.isUndefined(SCROLLBAR_WIDTH)) {
35401 var scrollElem = angular.element('<div style="position: absolute; top: -9999px; width: 50px; height: 50px; overflow: scroll;"></div>');
35402 $document.find('body').append(scrollElem);
35403 SCROLLBAR_WIDTH = scrollElem[0].offsetWidth - scrollElem[0].clientWidth;
35404 SCROLLBAR_WIDTH = isFinite(SCROLLBAR_WIDTH) ? SCROLLBAR_WIDTH : 0;
35405 scrollElem.remove();
35408 return SCROLLBAR_WIDTH;
35412 * Provides the closest scrollable ancestor.
35413 * A port of the jQuery UI scrollParent method:
35414 * https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js
35416 * @param {element} elem - The element to find the scroll parent of.
35417 * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
35418 * default is false.
35420 * @returns {element} A HTML element.
35422 scrollParent: function(elem, includeHidden) {
35423 elem = this.getRawNode(elem);
35425 var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
35426 var documentEl = $document[0].documentElement;
35427 var elemStyle = $window.getComputedStyle(elem);
35428 var excludeStatic = elemStyle.position === 'absolute';
35429 var scrollParent = elem.parentElement || documentEl;
35431 if (scrollParent === documentEl || elemStyle.position === 'fixed') {
35435 while (scrollParent.parentElement && scrollParent !== documentEl) {
35436 var spStyle = $window.getComputedStyle(scrollParent);
35437 if (excludeStatic && spStyle.position !== 'static') {
35438 excludeStatic = false;
35441 if (!excludeStatic && overflowRegex.test(spStyle.overflow + spStyle.overflowY + spStyle.overflowX)) {
35444 scrollParent = scrollParent.parentElement;
35447 return scrollParent;
35451 * Provides read-only equivalent of jQuery's position function:
35452 * http://api.jquery.com/position/ - distance to closest positioned
35453 * ancestor. Does not account for margins by default like jQuery position.
35455 * @param {element} elem - The element to caclulate the position on.
35456 * @param {boolean=} [includeMargins=false] - Should margins be accounted
35457 * for, default is false.
35459 * @returns {object} An object with the following properties:
35461 * <li>**width**: the width of the element</li>
35462 * <li>**height**: the height of the element</li>
35463 * <li>**top**: distance to top edge of offset parent</li>
35464 * <li>**left**: distance to left edge of offset parent</li>
35467 position: function(elem, includeMagins) {
35468 elem = this.getRawNode(elem);
35470 var elemOffset = this.offset(elem);
35471 if (includeMagins) {
35472 var elemStyle = $window.getComputedStyle(elem);
35473 elemOffset.top -= this.parseStyle(elemStyle.marginTop);
35474 elemOffset.left -= this.parseStyle(elemStyle.marginLeft);
35476 var parent = this.offsetParent(elem);
35477 var parentOffset = {top: 0, left: 0};
35479 if (parent !== $document[0].documentElement) {
35480 parentOffset = this.offset(parent);
35481 parentOffset.top += parent.clientTop - parent.scrollTop;
35482 parentOffset.left += parent.clientLeft - parent.scrollLeft;
35486 width: Math.round(angular.isNumber(elemOffset.width) ? elemOffset.width : elem.offsetWidth),
35487 height: Math.round(angular.isNumber(elemOffset.height) ? elemOffset.height : elem.offsetHeight),
35488 top: Math.round(elemOffset.top - parentOffset.top),
35489 left: Math.round(elemOffset.left - parentOffset.left)
35494 * Provides read-only equivalent of jQuery's offset function:
35495 * http://api.jquery.com/offset/ - distance to viewport. Does
35496 * not account for borders, margins, or padding on the body
35499 * @param {element} elem - The element to calculate the offset on.
35501 * @returns {object} An object with the following properties:
35503 * <li>**width**: the width of the element</li>
35504 * <li>**height**: the height of the element</li>
35505 * <li>**top**: distance to top edge of viewport</li>
35506 * <li>**right**: distance to bottom edge of viewport</li>
35509 offset: function(elem) {
35510 elem = this.getRawNode(elem);
35512 var elemBCR = elem.getBoundingClientRect();
35514 width: Math.round(angular.isNumber(elemBCR.width) ? elemBCR.width : elem.offsetWidth),
35515 height: Math.round(angular.isNumber(elemBCR.height) ? elemBCR.height : elem.offsetHeight),
35516 top: Math.round(elemBCR.top + ($window.pageYOffset || $document[0].documentElement.scrollTop)),
35517 left: Math.round(elemBCR.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft))
35522 * Provides offset distance to the closest scrollable ancestor
35523 * or viewport. Accounts for border and scrollbar width.
35525 * Right and bottom dimensions represent the distance to the
35526 * respective edge of the viewport element. If the element
35527 * edge extends beyond the viewport, a negative value will be
35530 * @param {element} elem - The element to get the viewport offset for.
35531 * @param {boolean=} [useDocument=false] - Should the viewport be the document element instead
35532 * of the first scrollable element, default is false.
35533 * @param {boolean=} [includePadding=true] - Should the padding on the offset parent element
35534 * be accounted for, default is true.
35536 * @returns {object} An object with the following properties:
35538 * <li>**top**: distance to the top content edge of viewport element</li>
35539 * <li>**bottom**: distance to the bottom content edge of viewport element</li>
35540 * <li>**left**: distance to the left content edge of viewport element</li>
35541 * <li>**right**: distance to the right content edge of viewport element</li>
35544 viewportOffset: function(elem, useDocument, includePadding) {
35545 elem = this.getRawNode(elem);
35546 includePadding = includePadding !== false ? true : false;
35548 var elemBCR = elem.getBoundingClientRect();
35549 var offsetBCR = {top: 0, left: 0, bottom: 0, right: 0};
35551 var offsetParent = useDocument ? $document[0].documentElement : this.scrollParent(elem);
35552 var offsetParentBCR = offsetParent.getBoundingClientRect();
35554 offsetBCR.top = offsetParentBCR.top + offsetParent.clientTop;
35555 offsetBCR.left = offsetParentBCR.left + offsetParent.clientLeft;
35556 if (offsetParent === $document[0].documentElement) {
35557 offsetBCR.top += $window.pageYOffset;
35558 offsetBCR.left += $window.pageXOffset;
35560 offsetBCR.bottom = offsetBCR.top + offsetParent.clientHeight;
35561 offsetBCR.right = offsetBCR.left + offsetParent.clientWidth;
35563 if (includePadding) {
35564 var offsetParentStyle = $window.getComputedStyle(offsetParent);
35565 offsetBCR.top += this.parseStyle(offsetParentStyle.paddingTop);
35566 offsetBCR.bottom -= this.parseStyle(offsetParentStyle.paddingBottom);
35567 offsetBCR.left += this.parseStyle(offsetParentStyle.paddingLeft);
35568 offsetBCR.right -= this.parseStyle(offsetParentStyle.paddingRight);
35572 top: Math.round(elemBCR.top - offsetBCR.top),
35573 bottom: Math.round(offsetBCR.bottom - elemBCR.bottom),
35574 left: Math.round(elemBCR.left - offsetBCR.left),
35575 right: Math.round(offsetBCR.right - elemBCR.right)
35580 * Provides an array of placement values parsed from a placement string.
35581 * Along with the 'auto' indicator, supported placement strings are:
35583 * <li>top: element on top, horizontally centered on host element.</li>
35584 * <li>top-left: element on top, left edge aligned with host element left edge.</li>
35585 * <li>top-right: element on top, lerightft edge aligned with host element right edge.</li>
35586 * <li>bottom: element on bottom, horizontally centered on host element.</li>
35587 * <li>bottom-left: element on bottom, left edge aligned with host element left edge.</li>
35588 * <li>bottom-right: element on bottom, right edge aligned with host element right edge.</li>
35589 * <li>left: element on left, vertically centered on host element.</li>
35590 * <li>left-top: element on left, top edge aligned with host element top edge.</li>
35591 * <li>left-bottom: element on left, bottom edge aligned with host element bottom edge.</li>
35592 * <li>right: element on right, vertically centered on host element.</li>
35593 * <li>right-top: element on right, top edge aligned with host element top edge.</li>
35594 * <li>right-bottom: element on right, bottom edge aligned with host element bottom edge.</li>
35596 * A placement string with an 'auto' indicator is expected to be
35597 * space separated from the placement, i.e: 'auto bottom-left' If
35598 * the primary and secondary placement values do not match 'top,
35599 * bottom, left, right' then 'top' will be the primary placement and
35600 * 'center' will be the secondary placement. If 'auto' is passed, true
35601 * will be returned as the 3rd value of the array.
35603 * @param {string} placement - The placement string to parse.
35605 * @returns {array} An array with the following values
35607 * <li>**[0]**: The primary placement.</li>
35608 * <li>**[1]**: The secondary placement.</li>
35609 * <li>**[2]**: If auto is passed: true, else undefined.</li>
35612 parsePlacement: function(placement) {
35613 var autoPlace = PLACEMENT_REGEX.auto.test(placement);
35615 placement = placement.replace(PLACEMENT_REGEX.auto, '');
35618 placement = placement.split('-');
35620 placement[0] = placement[0] || 'top';
35621 if (!PLACEMENT_REGEX.primary.test(placement[0])) {
35622 placement[0] = 'top';
35625 placement[1] = placement[1] || 'center';
35626 if (!PLACEMENT_REGEX.secondary.test(placement[1])) {
35627 placement[1] = 'center';
35631 placement[2] = true;
35633 placement[2] = false;
35640 * Provides coordinates for an element to be positioned relative to
35641 * another element. Passing 'auto' as part of the placement parameter
35642 * will enable smart placement - where the element fits. i.e:
35643 * 'auto left-top' will check to see if there is enough space to the left
35644 * of the hostElem to fit the targetElem, if not place right (same for secondary
35645 * top placement). Available space is calculated using the viewportOffset
35648 * @param {element} hostElem - The element to position against.
35649 * @param {element} targetElem - The element to position.
35650 * @param {string=} [placement=top] - The placement for the targetElem,
35651 * default is 'top'. 'center' is assumed as secondary placement for
35652 * 'top', 'left', 'right', and 'bottom' placements. Available placements are:
35655 * <li>top-right</li>
35656 * <li>top-left</li>
35658 * <li>bottom-left</li>
35659 * <li>bottom-right</li>
35661 * <li>left-top</li>
35662 * <li>left-bottom</li>
35664 * <li>right-top</li>
35665 * <li>right-bottom</li>
35667 * @param {boolean=} [appendToBody=false] - Should the top and left values returned
35668 * be calculated from the body element, default is false.
35670 * @returns {object} An object with the following properties:
35672 * <li>**top**: Value for targetElem top.</li>
35673 * <li>**left**: Value for targetElem left.</li>
35674 * <li>**placement**: The resolved placement.</li>
35677 positionElements: function(hostElem, targetElem, placement, appendToBody) {
35678 hostElem = this.getRawNode(hostElem);
35679 targetElem = this.getRawNode(targetElem);
35681 // need to read from prop to support tests.
35682 var targetWidth = angular.isDefined(targetElem.offsetWidth) ? targetElem.offsetWidth : targetElem.prop('offsetWidth');
35683 var targetHeight = angular.isDefined(targetElem.offsetHeight) ? targetElem.offsetHeight : targetElem.prop('offsetHeight');
35685 placement = this.parsePlacement(placement);
35687 var hostElemPos = appendToBody ? this.offset(hostElem) : this.position(hostElem);
35688 var targetElemPos = {top: 0, left: 0, placement: ''};
35690 if (placement[2]) {
35691 var viewportOffset = this.viewportOffset(hostElem);
35693 var targetElemStyle = $window.getComputedStyle(targetElem);
35694 var adjustedSize = {
35695 width: targetWidth + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginLeft) + this.parseStyle(targetElemStyle.marginRight))),
35696 height: targetHeight + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginTop) + this.parseStyle(targetElemStyle.marginBottom)))
35699 placement[0] = placement[0] === 'top' && adjustedSize.height > viewportOffset.top && adjustedSize.height <= viewportOffset.bottom ? 'bottom' :
35700 placement[0] === 'bottom' && adjustedSize.height > viewportOffset.bottom && adjustedSize.height <= viewportOffset.top ? 'top' :
35701 placement[0] === 'left' && adjustedSize.width > viewportOffset.left && adjustedSize.width <= viewportOffset.right ? 'right' :
35702 placement[0] === 'right' && adjustedSize.width > viewportOffset.right && adjustedSize.width <= viewportOffset.left ? 'left' :
35705 placement[1] = placement[1] === 'top' && adjustedSize.height - hostElemPos.height > viewportOffset.bottom && adjustedSize.height - hostElemPos.height <= viewportOffset.top ? 'bottom' :
35706 placement[1] === 'bottom' && adjustedSize.height - hostElemPos.height > viewportOffset.top && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom ? 'top' :
35707 placement[1] === 'left' && adjustedSize.width - hostElemPos.width > viewportOffset.right && adjustedSize.width - hostElemPos.width <= viewportOffset.left ? 'right' :
35708 placement[1] === 'right' && adjustedSize.width - hostElemPos.width > viewportOffset.left && adjustedSize.width - hostElemPos.width <= viewportOffset.right ? 'left' :
35711 if (placement[1] === 'center') {
35712 if (PLACEMENT_REGEX.vertical.test(placement[0])) {
35713 var xOverflow = hostElemPos.width / 2 - targetWidth / 2;
35714 if (viewportOffset.left + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.right) {
35715 placement[1] = 'left';
35716 } else if (viewportOffset.right + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.left) {
35717 placement[1] = 'right';
35720 var yOverflow = hostElemPos.height / 2 - adjustedSize.height / 2;
35721 if (viewportOffset.top + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom) {
35722 placement[1] = 'top';
35723 } else if (viewportOffset.bottom + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.top) {
35724 placement[1] = 'bottom';
35730 switch (placement[0]) {
35732 targetElemPos.top = hostElemPos.top - targetHeight;
35735 targetElemPos.top = hostElemPos.top + hostElemPos.height;
35738 targetElemPos.left = hostElemPos.left - targetWidth;
35741 targetElemPos.left = hostElemPos.left + hostElemPos.width;
35745 switch (placement[1]) {
35747 targetElemPos.top = hostElemPos.top;
35750 targetElemPos.top = hostElemPos.top + hostElemPos.height - targetHeight;
35753 targetElemPos.left = hostElemPos.left;
35756 targetElemPos.left = hostElemPos.left + hostElemPos.width - targetWidth;
35759 if (PLACEMENT_REGEX.vertical.test(placement[0])) {
35760 targetElemPos.left = hostElemPos.left + hostElemPos.width / 2 - targetWidth / 2;
35762 targetElemPos.top = hostElemPos.top + hostElemPos.height / 2 - targetHeight / 2;
35767 targetElemPos.top = Math.round(targetElemPos.top);
35768 targetElemPos.left = Math.round(targetElemPos.left);
35769 targetElemPos.placement = placement[1] === 'center' ? placement[0] : placement[0] + '-' + placement[1];
35771 return targetElemPos;
35775 * Provides a way for positioning tooltip & dropdown
35776 * arrows when using placement options beyond the standard
35777 * left, right, top, or bottom.
35779 * @param {element} elem - The tooltip/dropdown element.
35780 * @param {string} placement - The placement for the elem.
35782 positionArrow: function(elem, placement) {
35783 elem = this.getRawNode(elem);
35785 var isTooltip = true;
35787 var innerElem = elem.querySelector('.tooltip-inner');
35790 innerElem = elem.querySelector('.popover-inner');
35796 var arrowElem = isTooltip ? elem.querySelector('.tooltip-arrow') : elem.querySelector('.arrow');
35801 placement = this.parsePlacement(placement);
35802 if (placement[1] === 'center') {
35803 // no adjustment necessary - just reset styles
35804 angular.element(arrowElem).css({top: '', bottom: '', right: '', left: '', margin: ''});
35808 var borderProp = 'border-' + placement[0] + '-width';
35809 var borderWidth = $window.getComputedStyle(arrowElem)[borderProp];
35811 var borderRadiusProp = 'border-';
35812 if (PLACEMENT_REGEX.vertical.test(placement[0])) {
35813 borderRadiusProp += placement[0] + '-' + placement[1];
35815 borderRadiusProp += placement[1] + '-' + placement[0];
35817 borderRadiusProp += '-radius';
35818 var borderRadius = $window.getComputedStyle(isTooltip ? innerElem : elem)[borderRadiusProp];
35828 switch (placement[0]) {
35830 arrowCss.bottom = isTooltip ? '0' : '-' + borderWidth;
35833 arrowCss.top = isTooltip ? '0' : '-' + borderWidth;
35836 arrowCss.right = isTooltip ? '0' : '-' + borderWidth;
35839 arrowCss.left = isTooltip ? '0' : '-' + borderWidth;
35843 arrowCss[placement[1]] = borderRadius;
35845 angular.element(arrowElem).css(arrowCss);
35850 angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.isClass', 'ui.bootstrap.position'])
35852 .value('$datepickerSuppressError', false)
35854 .constant('uibDatepickerConfig', {
35856 formatMonth: 'MMMM',
35857 formatYear: 'yyyy',
35858 formatDayHeader: 'EEE',
35859 formatDayTitle: 'MMMM yyyy',
35860 formatMonthTitle: 'yyyy',
35861 datepickerMode: 'day',
35870 shortcutPropagation: false,
35874 .controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerSuppressError', 'uibDateParser',
35875 function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError, dateParser) {
35877 ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl;
35878 ngModelOptions = {};
35881 this.modes = ['day', 'month', 'year'];
35883 // Interpolated configuration attributes
35884 angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle'], function(key) {
35885 self[key] = angular.isDefined($attrs[key]) ? $interpolate($attrs[key])($scope.$parent) : datepickerConfig[key];
35888 // Evaled configuration attributes
35889 angular.forEach(['showWeeks', 'startingDay', 'yearRows', 'yearColumns', 'shortcutPropagation'], function(key) {
35890 self[key] = angular.isDefined($attrs[key]) ? $scope.$parent.$eval($attrs[key]) : datepickerConfig[key];
35893 // Watchable date attributes
35894 angular.forEach(['minDate', 'maxDate'], function(key) {
35896 $scope.$parent.$watch($attrs[key], function(value) {
35897 self[key] = value ? angular.isDate(value) ? dateParser.fromTimezone(new Date(value), ngModelOptions.timezone) : new Date(dateFilter(value, 'medium')) : null;
35898 self.refreshView();
35901 self[key] = datepickerConfig[key] ? dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) : null;
35905 angular.forEach(['minMode', 'maxMode'], function(key) {
35907 $scope.$parent.$watch($attrs[key], function(value) {
35908 self[key] = $scope[key] = angular.isDefined(value) ? value : $attrs[key];
35909 if (key === 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key]) ||
35910 key === 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key])) {
35911 $scope.datepickerMode = self[key];
35915 self[key] = $scope[key] = datepickerConfig[key] || null;
35919 $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode;
35920 $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
35922 if (angular.isDefined($attrs.initDate)) {
35923 this.activeDate = dateParser.fromTimezone($scope.$parent.$eval($attrs.initDate), ngModelOptions.timezone) || new Date();
35924 $scope.$parent.$watch($attrs.initDate, function(initDate) {
35925 if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
35926 self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone);
35927 self.refreshView();
35931 this.activeDate = new Date();
35934 $scope.disabled = angular.isDefined($attrs.disabled) || false;
35935 if (angular.isDefined($attrs.ngDisabled)) {
35936 $scope.$parent.$watch($attrs.ngDisabled, function(disabled) {
35937 $scope.disabled = disabled;
35938 self.refreshView();
35942 $scope.isActive = function(dateObject) {
35943 if (self.compare(dateObject.date, self.activeDate) === 0) {
35944 $scope.activeDateId = dateObject.uid;
35950 this.init = function(ngModelCtrl_) {
35951 ngModelCtrl = ngModelCtrl_;
35952 ngModelOptions = ngModelCtrl_.$options || datepickerConfig.ngModelOptions;
35954 if (ngModelCtrl.$modelValue) {
35955 this.activeDate = ngModelCtrl.$modelValue;
35958 ngModelCtrl.$render = function() {
35963 this.render = function() {
35964 if (ngModelCtrl.$viewValue) {
35965 var date = new Date(ngModelCtrl.$viewValue),
35966 isValid = !isNaN(date);
35969 this.activeDate = dateParser.fromTimezone(date, ngModelOptions.timezone);
35970 } else if (!$datepickerSuppressError) {
35971 $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.');
35974 this.refreshView();
35977 this.refreshView = function() {
35978 if (this.element) {
35979 $scope.selectedDt = null;
35980 this._refreshView();
35981 if ($scope.activeDt) {
35982 $scope.activeDateId = $scope.activeDt.uid;
35985 var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
35986 date = dateParser.fromTimezone(date, ngModelOptions.timezone);
35987 ngModelCtrl.$setValidity('dateDisabled', !date ||
35988 this.element && !this.isDisabled(date));
35992 this.createDateObject = function(date, format) {
35993 var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
35994 model = dateParser.fromTimezone(model, ngModelOptions.timezone);
35997 label: dateFilter(date, format),
35998 selected: model && this.compare(date, model) === 0,
35999 disabled: this.isDisabled(date),
36000 current: this.compare(date, new Date()) === 0,
36001 customClass: this.customClass(date) || null
36004 if (model && this.compare(date, model) === 0) {
36005 $scope.selectedDt = dt;
36008 if (self.activeDate && this.compare(dt.date, self.activeDate) === 0) {
36009 $scope.activeDt = dt;
36015 this.isDisabled = function(date) {
36016 return $scope.disabled ||
36017 this.minDate && this.compare(date, this.minDate) < 0 ||
36018 this.maxDate && this.compare(date, this.maxDate) > 0 ||
36019 $attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode});
36022 this.customClass = function(date) {
36023 return $scope.customClass({date: date, mode: $scope.datepickerMode});
36026 // Split array into smaller arrays
36027 this.split = function(arr, size) {
36029 while (arr.length > 0) {
36030 arrays.push(arr.splice(0, size));
36035 $scope.select = function(date) {
36036 if ($scope.datepickerMode === self.minMode) {
36037 var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.timezone) : new Date(0, 0, 0, 0, 0, 0, 0);
36038 dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
36039 dt = dateParser.toTimezone(dt, ngModelOptions.timezone);
36040 ngModelCtrl.$setViewValue(dt);
36041 ngModelCtrl.$render();
36043 self.activeDate = date;
36044 $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) - 1];
36048 $scope.move = function(direction) {
36049 var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
36050 month = self.activeDate.getMonth() + direction * (self.step.months || 0);
36051 self.activeDate.setFullYear(year, month, 1);
36052 self.refreshView();
36055 $scope.toggleMode = function(direction) {
36056 direction = direction || 1;
36058 if ($scope.datepickerMode === self.maxMode && direction === 1 ||
36059 $scope.datepickerMode === self.minMode && direction === -1) {
36063 $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) + direction];
36066 // Key event mapper
36067 $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' };
36069 var focusElement = function() {
36070 self.element[0].focus();
36073 // Listen for focus requests from popup directive
36074 $scope.$on('uib:datepicker.focus', focusElement);
36076 $scope.keydown = function(evt) {
36077 var key = $scope.keys[evt.which];
36079 if (!key || evt.shiftKey || evt.altKey || $scope.disabled) {
36083 evt.preventDefault();
36084 if (!self.shortcutPropagation) {
36085 evt.stopPropagation();
36088 if (key === 'enter' || key === 'space') {
36089 if (self.isDisabled(self.activeDate)) {
36090 return; // do nothing
36092 $scope.select(self.activeDate);
36093 } else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
36094 $scope.toggleMode(key === 'up' ? 1 : -1);
36096 self.handleKeyDown(key, evt);
36097 self.refreshView();
36102 .controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
36103 var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
36105 this.step = { months: 1 };
36106 this.element = $element;
36107 function getDaysInMonth(year, month) {
36108 return month === 1 && year % 4 === 0 &&
36109 (year % 100 !== 0 || year % 400 === 0) ? 29 : DAYS_IN_MONTH[month];
36112 this.init = function(ctrl) {
36113 angular.extend(ctrl, this);
36114 scope.showWeeks = ctrl.showWeeks;
36115 ctrl.refreshView();
36118 this.getDates = function(startDate, n) {
36119 var dates = new Array(n), current = new Date(startDate), i = 0, date;
36121 date = new Date(current);
36123 current.setDate(current.getDate() + 1);
36128 this._refreshView = function() {
36129 var year = this.activeDate.getFullYear(),
36130 month = this.activeDate.getMonth(),
36131 firstDayOfMonth = new Date(this.activeDate);
36133 firstDayOfMonth.setFullYear(year, month, 1);
36135 var difference = this.startingDay - firstDayOfMonth.getDay(),
36136 numDisplayedFromPreviousMonth = difference > 0 ?
36137 7 - difference : - difference,
36138 firstDate = new Date(firstDayOfMonth);
36140 if (numDisplayedFromPreviousMonth > 0) {
36141 firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
36144 // 42 is the number of days on a six-week calendar
36145 var days = this.getDates(firstDate, 42);
36146 for (var i = 0; i < 42; i ++) {
36147 days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), {
36148 secondary: days[i].getMonth() !== month,
36149 uid: scope.uniqueId + '-' + i
36153 scope.labels = new Array(7);
36154 for (var j = 0; j < 7; j++) {
36155 scope.labels[j] = {
36156 abbr: dateFilter(days[j].date, this.formatDayHeader),
36157 full: dateFilter(days[j].date, 'EEEE')
36161 scope.title = dateFilter(this.activeDate, this.formatDayTitle);
36162 scope.rows = this.split(days, 7);
36164 if (scope.showWeeks) {
36165 scope.weekNumbers = [];
36166 var thursdayIndex = (4 + 7 - this.startingDay) % 7,
36167 numWeeks = scope.rows.length;
36168 for (var curWeek = 0; curWeek < numWeeks; curWeek++) {
36169 scope.weekNumbers.push(
36170 getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date));
36175 this.compare = function(date1, date2) {
36176 var _date1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate());
36177 var _date2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
36178 _date1.setFullYear(date1.getFullYear());
36179 _date2.setFullYear(date2.getFullYear());
36180 return _date1 - _date2;
36183 function getISO8601WeekNumber(date) {
36184 var checkDate = new Date(date);
36185 checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
36186 var time = checkDate.getTime();
36187 checkDate.setMonth(0); // Compare with Jan 1
36188 checkDate.setDate(1);
36189 return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
36192 this.handleKeyDown = function(key, evt) {
36193 var date = this.activeDate.getDate();
36195 if (key === 'left') {
36197 } else if (key === 'up') {
36199 } else if (key === 'right') {
36201 } else if (key === 'down') {
36203 } else if (key === 'pageup' || key === 'pagedown') {
36204 var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1);
36205 this.activeDate.setMonth(month, 1);
36206 date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date);
36207 } else if (key === 'home') {
36209 } else if (key === 'end') {
36210 date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth());
36212 this.activeDate.setDate(date);
36216 .controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
36217 this.step = { years: 1 };
36218 this.element = $element;
36220 this.init = function(ctrl) {
36221 angular.extend(ctrl, this);
36222 ctrl.refreshView();
36225 this._refreshView = function() {
36226 var months = new Array(12),
36227 year = this.activeDate.getFullYear(),
36230 for (var i = 0; i < 12; i++) {
36231 date = new Date(this.activeDate);
36232 date.setFullYear(year, i, 1);
36233 months[i] = angular.extend(this.createDateObject(date, this.formatMonth), {
36234 uid: scope.uniqueId + '-' + i
36238 scope.title = dateFilter(this.activeDate, this.formatMonthTitle);
36239 scope.rows = this.split(months, 3);
36242 this.compare = function(date1, date2) {
36243 var _date1 = new Date(date1.getFullYear(), date1.getMonth());
36244 var _date2 = new Date(date2.getFullYear(), date2.getMonth());
36245 _date1.setFullYear(date1.getFullYear());
36246 _date2.setFullYear(date2.getFullYear());
36247 return _date1 - _date2;
36250 this.handleKeyDown = function(key, evt) {
36251 var date = this.activeDate.getMonth();
36253 if (key === 'left') {
36255 } else if (key === 'up') {
36257 } else if (key === 'right') {
36259 } else if (key === 'down') {
36261 } else if (key === 'pageup' || key === 'pagedown') {
36262 var year = this.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1);
36263 this.activeDate.setFullYear(year);
36264 } else if (key === 'home') {
36266 } else if (key === 'end') {
36269 this.activeDate.setMonth(date);
36273 .controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
36274 var columns, range;
36275 this.element = $element;
36277 function getStartingYear(year) {
36278 return parseInt((year - 1) / range, 10) * range + 1;
36281 this.yearpickerInit = function() {
36282 columns = this.yearColumns;
36283 range = this.yearRows * columns;
36284 this.step = { years: range };
36287 this._refreshView = function() {
36288 var years = new Array(range), date;
36290 for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) {
36291 date = new Date(this.activeDate);
36292 date.setFullYear(start + i, 0, 1);
36293 years[i] = angular.extend(this.createDateObject(date, this.formatYear), {
36294 uid: scope.uniqueId + '-' + i
36298 scope.title = [years[0].label, years[range - 1].label].join(' - ');
36299 scope.rows = this.split(years, columns);
36300 scope.columns = columns;
36303 this.compare = function(date1, date2) {
36304 return date1.getFullYear() - date2.getFullYear();
36307 this.handleKeyDown = function(key, evt) {
36308 var date = this.activeDate.getFullYear();
36310 if (key === 'left') {
36312 } else if (key === 'up') {
36313 date = date - columns;
36314 } else if (key === 'right') {
36316 } else if (key === 'down') {
36317 date = date + columns;
36318 } else if (key === 'pageup' || key === 'pagedown') {
36319 date += (key === 'pageup' ? - 1 : 1) * range;
36320 } else if (key === 'home') {
36321 date = getStartingYear(this.activeDate.getFullYear());
36322 } else if (key === 'end') {
36323 date = getStartingYear(this.activeDate.getFullYear()) + range - 1;
36325 this.activeDate.setFullYear(date);
36329 .directive('uibDatepicker', function() {
36332 templateUrl: function(element, attrs) {
36333 return attrs.templateUrl || 'uib/template/datepicker/datepicker.html';
36336 datepickerMode: '=?',
36339 shortcutPropagation: '&?'
36341 require: ['uibDatepicker', '^ngModel'],
36342 controller: 'UibDatepickerController',
36343 controllerAs: 'datepicker',
36344 link: function(scope, element, attrs, ctrls) {
36345 var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
36347 datepickerCtrl.init(ngModelCtrl);
36352 .directive('uibDaypicker', function() {
36355 templateUrl: function(element, attrs) {
36356 return attrs.templateUrl || 'uib/template/datepicker/day.html';
36358 require: ['^uibDatepicker', 'uibDaypicker'],
36359 controller: 'UibDaypickerController',
36360 link: function(scope, element, attrs, ctrls) {
36361 var datepickerCtrl = ctrls[0],
36362 daypickerCtrl = ctrls[1];
36364 daypickerCtrl.init(datepickerCtrl);
36369 .directive('uibMonthpicker', function() {
36372 templateUrl: function(element, attrs) {
36373 return attrs.templateUrl || 'uib/template/datepicker/month.html';
36375 require: ['^uibDatepicker', 'uibMonthpicker'],
36376 controller: 'UibMonthpickerController',
36377 link: function(scope, element, attrs, ctrls) {
36378 var datepickerCtrl = ctrls[0],
36379 monthpickerCtrl = ctrls[1];
36381 monthpickerCtrl.init(datepickerCtrl);
36386 .directive('uibYearpicker', function() {
36389 templateUrl: function(element, attrs) {
36390 return attrs.templateUrl || 'uib/template/datepicker/year.html';
36392 require: ['^uibDatepicker', 'uibYearpicker'],
36393 controller: 'UibYearpickerController',
36394 link: function(scope, element, attrs, ctrls) {
36395 var ctrl = ctrls[0];
36396 angular.extend(ctrl, ctrls[1]);
36397 ctrl.yearpickerInit();
36399 ctrl.refreshView();
36404 .constant('uibDatepickerPopupConfig', {
36405 datepickerPopup: 'yyyy-MM-dd',
36406 datepickerPopupTemplateUrl: 'uib/template/datepicker/popup.html',
36407 datepickerTemplateUrl: 'uib/template/datepicker/datepicker.html',
36409 date: 'yyyy-MM-dd',
36410 'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',
36413 currentText: 'Today',
36414 clearText: 'Clear',
36416 closeOnDateSelection: true,
36417 appendToBody: false,
36418 showButtonBar: true,
36420 altInputFormats: []
36423 .controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig',
36424 function(scope, element, attrs, $compile, $parse, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig) {
36427 isHtml5DateInput = false;
36428 var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
36429 datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl,
36430 ngModel, ngModelOptions, $popup, altInputFormats;
36432 scope.watchData = {};
36434 this.init = function(_ngModel_) {
36435 ngModel = _ngModel_;
36436 ngModelOptions = _ngModel_.$options || datepickerConfig.ngModelOptions;
36437 closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection;
36438 appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody;
36439 onOpenFocus = angular.isDefined(attrs.onOpenFocus) ? scope.$parent.$eval(attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus;
36440 datepickerPopupTemplateUrl = angular.isDefined(attrs.datepickerPopupTemplateUrl) ? attrs.datepickerPopupTemplateUrl : datepickerPopupConfig.datepickerPopupTemplateUrl;
36441 datepickerTemplateUrl = angular.isDefined(attrs.datepickerTemplateUrl) ? attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl;
36442 altInputFormats = angular.isDefined(attrs.altInputFormats) ? scope.$parent.$eval(attrs.altInputFormats) : datepickerPopupConfig.altInputFormats;
36444 scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar;
36446 if (datepickerPopupConfig.html5Types[attrs.type]) {
36447 dateFormat = datepickerPopupConfig.html5Types[attrs.type];
36448 isHtml5DateInput = true;
36450 dateFormat = attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup;
36451 attrs.$observe('uibDatepickerPopup', function(value, oldValue) {
36452 var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
36453 // Invalidate the $modelValue to ensure that formatters re-run
36454 // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
36455 if (newDateFormat !== dateFormat) {
36456 dateFormat = newDateFormat;
36457 ngModel.$modelValue = null;
36460 throw new Error('uibDatepickerPopup must have a date format specified.');
36467 throw new Error('uibDatepickerPopup must have a date format specified.');
36470 if (isHtml5DateInput && attrs.uibDatepickerPopup) {
36471 throw new Error('HTML5 date input types do not support custom formats.');
36474 // popup element used to display calendar
36475 popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>');
36476 scope.ngModelOptions = angular.copy(ngModelOptions);
36477 scope.ngModelOptions.timezone = null;
36479 'ng-model': 'date',
36480 'ng-model-options': 'ngModelOptions',
36481 'ng-change': 'dateSelection(date)',
36482 'template-url': datepickerPopupTemplateUrl
36485 // datepicker element
36486 datepickerEl = angular.element(popupEl.children()[0]);
36487 datepickerEl.attr('template-url', datepickerTemplateUrl);
36489 if (isHtml5DateInput) {
36490 if (attrs.type === 'month') {
36491 datepickerEl.attr('datepicker-mode', '"month"');
36492 datepickerEl.attr('min-mode', 'month');
36496 if (attrs.datepickerOptions) {
36497 var options = scope.$parent.$eval(attrs.datepickerOptions);
36498 if (options && options.initDate) {
36499 scope.initDate = dateParser.fromTimezone(options.initDate, ngModelOptions.timezone);
36500 datepickerEl.attr('init-date', 'initDate');
36501 delete options.initDate;
36503 angular.forEach(options, function(value, option) {
36504 datepickerEl.attr(cameltoDash(option), value);
36508 angular.forEach(['minMode', 'maxMode'], function(key) {
36510 scope.$parent.$watch(function() { return attrs[key]; }, function(value) {
36511 scope.watchData[key] = value;
36513 datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
36517 angular.forEach(['datepickerMode', 'shortcutPropagation'], function(key) {
36519 var getAttribute = $parse(attrs[key]);
36522 return getAttribute(scope.$parent);
36526 datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
36528 // Propagate changes from datepicker to outside
36529 if (key === 'datepickerMode') {
36530 var setAttribute = getAttribute.assign;
36531 propConfig.set = function(v) {
36532 setAttribute(scope.$parent, v);
36536 Object.defineProperty(scope.watchData, key, propConfig);
36540 angular.forEach(['minDate', 'maxDate', 'initDate'], function(key) {
36542 var getAttribute = $parse(attrs[key]);
36544 scope.$parent.$watch(getAttribute, function(value) {
36545 if (key === 'minDate' || key === 'maxDate') {
36546 cache[key] = angular.isDate(value) ? dateParser.fromTimezone(new Date(value), ngModelOptions.timezone) : new Date(dateFilter(value, 'medium'));
36549 scope.watchData[key] = cache[key] || dateParser.fromTimezone(new Date(value), ngModelOptions.timezone);
36552 datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
36556 if (attrs.dateDisabled) {
36557 datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })');
36560 angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle', 'showWeeks', 'startingDay', 'yearRows', 'yearColumns'], function(key) {
36561 if (angular.isDefined(attrs[key])) {
36562 datepickerEl.attr(cameltoDash(key), attrs[key]);
36566 if (attrs.customClass) {
36567 datepickerEl.attr('custom-class', 'customClass({ date: date, mode: mode })');
36570 if (!isHtml5DateInput) {
36571 // Internal API to maintain the correct ng-invalid-[key] class
36572 ngModel.$$parserName = 'date';
36573 ngModel.$validators.date = validator;
36574 ngModel.$parsers.unshift(parseDate);
36575 ngModel.$formatters.push(function(value) {
36576 if (ngModel.$isEmpty(value)) {
36577 scope.date = value;
36580 scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);
36581 return dateFilter(scope.date, dateFormat);
36584 ngModel.$formatters.push(function(value) {
36585 scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);
36590 // Detect changes in the view from the text box
36591 ngModel.$viewChangeListeners.push(function() {
36592 scope.date = parseDateString(ngModel.$viewValue);
36595 element.bind('keydown', inputKeydownBind);
36597 $popup = $compile(popupEl)(scope);
36598 // Prevent jQuery cache memory leak (template is now redundant after linking)
36601 if (appendToBody) {
36602 $document.find('body').append($popup);
36604 element.after($popup);
36607 scope.$on('$destroy', function() {
36608 if (scope.isOpen === true) {
36609 if (!$rootScope.$$phase) {
36610 scope.$apply(function() {
36611 scope.isOpen = false;
36617 element.unbind('keydown', inputKeydownBind);
36618 $document.unbind('click', documentClickBind);
36622 scope.getText = function(key) {
36623 return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
36626 scope.isDisabled = function(date) {
36627 if (date === 'today') {
36631 return scope.watchData.minDate && scope.compare(date, cache.minDate) < 0 ||
36632 scope.watchData.maxDate && scope.compare(date, cache.maxDate) > 0;
36635 scope.compare = function(date1, date2) {
36636 return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
36640 scope.dateSelection = function(dt) {
36641 if (angular.isDefined(dt)) {
36644 var date = scope.date ? dateFilter(scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function
36646 ngModel.$setViewValue(date);
36648 if (closeOnDateSelection) {
36649 scope.isOpen = false;
36650 element[0].focus();
36654 scope.keydown = function(evt) {
36655 if (evt.which === 27) {
36656 evt.stopPropagation();
36657 scope.isOpen = false;
36658 element[0].focus();
36662 scope.select = function(date) {
36663 if (date === 'today') {
36664 var today = new Date();
36665 if (angular.isDate(scope.date)) {
36666 date = new Date(scope.date);
36667 date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
36669 date = new Date(today.setHours(0, 0, 0, 0));
36672 scope.dateSelection(date);
36675 scope.close = function() {
36676 scope.isOpen = false;
36677 element[0].focus();
36680 scope.disabled = angular.isDefined(attrs.disabled) || false;
36681 if (attrs.ngDisabled) {
36682 scope.$parent.$watch($parse(attrs.ngDisabled), function(disabled) {
36683 scope.disabled = disabled;
36687 scope.$watch('isOpen', function(value) {
36689 if (!scope.disabled) {
36690 scope.position = appendToBody ? $position.offset(element) : $position.position(element);
36691 scope.position.top = scope.position.top + element.prop('offsetHeight');
36693 $timeout(function() {
36695 scope.$broadcast('uib:datepicker.focus');
36697 $document.bind('click', documentClickBind);
36700 scope.isOpen = false;
36703 $document.unbind('click', documentClickBind);
36707 function cameltoDash(string) {
36708 return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
36711 function parseDateString(viewValue) {
36712 var date = dateParser.parse(viewValue, dateFormat, scope.date);
36714 for (var i = 0; i < altInputFormats.length; i++) {
36715 date = dateParser.parse(viewValue, altInputFormats[i], scope.date);
36716 if (!isNaN(date)) {
36724 function parseDate(viewValue) {
36725 if (angular.isNumber(viewValue)) {
36726 // presumably timestamp to date object
36727 viewValue = new Date(viewValue);
36734 if (angular.isDate(viewValue) && !isNaN(viewValue)) {
36738 if (angular.isString(viewValue)) {
36739 var date = parseDateString(viewValue);
36740 if (!isNaN(date)) {
36741 return dateParser.toTimezone(date, ngModelOptions.timezone);
36745 return ngModel.$options && ngModel.$options.allowInvalid ? viewValue : undefined;
36748 function validator(modelValue, viewValue) {
36749 var value = modelValue || viewValue;
36751 if (!attrs.ngRequired && !value) {
36755 if (angular.isNumber(value)) {
36756 value = new Date(value);
36763 if (angular.isDate(value) && !isNaN(value)) {
36767 if (angular.isString(value)) {
36768 return !isNaN(parseDateString(viewValue));
36774 function documentClickBind(event) {
36775 if (!scope.isOpen && scope.disabled) {
36779 var popup = $popup[0];
36780 var dpContainsTarget = element[0].contains(event.target);
36781 // The popup node may not be an element node
36782 // In some browsers (IE) only element nodes have the 'contains' function
36783 var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target);
36784 if (scope.isOpen && !(dpContainsTarget || popupContainsTarget)) {
36785 scope.$apply(function() {
36786 scope.isOpen = false;
36791 function inputKeydownBind(evt) {
36792 if (evt.which === 27 && scope.isOpen) {
36793 evt.preventDefault();
36794 evt.stopPropagation();
36795 scope.$apply(function() {
36796 scope.isOpen = false;
36798 element[0].focus();
36799 } else if (evt.which === 40 && !scope.isOpen) {
36800 evt.preventDefault();
36801 evt.stopPropagation();
36802 scope.$apply(function() {
36803 scope.isOpen = true;
36809 .directive('uibDatepickerPopup', function() {
36811 require: ['ngModel', 'uibDatepickerPopup'],
36812 controller: 'UibDatepickerPopupController',
36821 link: function(scope, element, attrs, ctrls) {
36822 var ngModel = ctrls[0],
36825 ctrl.init(ngModel);
36830 .directive('uibDatepickerPopupWrap', function() {
36834 templateUrl: function(element, attrs) {
36835 return attrs.templateUrl || 'uib/template/datepicker/popup.html';
36840 angular.module('ui.bootstrap.debounce', [])
36842 * A helper, internal service that debounces a function
36844 .factory('$$debounce', ['$timeout', function($timeout) {
36845 return function(callback, debounceTime) {
36846 var timeoutPromise;
36848 return function() {
36850 var args = Array.prototype.slice.call(arguments);
36851 if (timeoutPromise) {
36852 $timeout.cancel(timeoutPromise);
36855 timeoutPromise = $timeout(function() {
36856 callback.apply(self, args);
36862 angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
36864 .constant('uibDropdownConfig', {
36865 appendToOpenClass: 'uib-dropdown-open',
36869 .service('uibDropdownService', ['$document', '$rootScope', function($document, $rootScope) {
36870 var openScope = null;
36872 this.open = function(dropdownScope) {
36874 $document.on('click', closeDropdown);
36875 $document.on('keydown', keybindFilter);
36878 if (openScope && openScope !== dropdownScope) {
36879 openScope.isOpen = false;
36882 openScope = dropdownScope;
36885 this.close = function(dropdownScope) {
36886 if (openScope === dropdownScope) {
36888 $document.off('click', closeDropdown);
36889 $document.off('keydown', keybindFilter);
36893 var closeDropdown = function(evt) {
36894 // This method may still be called during the same mouse event that
36895 // unbound this event handler. So check openScope before proceeding.
36896 if (!openScope) { return; }
36898 if (evt && openScope.getAutoClose() === 'disabled') { return; }
36900 if (evt && evt.which === 3) { return; }
36902 var toggleElement = openScope.getToggleElement();
36903 if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
36907 var dropdownElement = openScope.getDropdownElement();
36908 if (evt && openScope.getAutoClose() === 'outsideClick' &&
36909 dropdownElement && dropdownElement[0].contains(evt.target)) {
36913 openScope.isOpen = false;
36915 if (!$rootScope.$$phase) {
36916 openScope.$apply();
36920 var keybindFilter = function(evt) {
36921 if (evt.which === 27) {
36922 openScope.focusToggleElement();
36924 } else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen) {
36925 evt.preventDefault();
36926 evt.stopPropagation();
36927 openScope.focusDropdownEntry(evt.which);
36932 .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) {
36934 scope = $scope.$new(), // create a child scope so we are not polluting original one
36936 appendToOpenClass = dropdownConfig.appendToOpenClass,
36937 openClass = dropdownConfig.openClass,
36939 setIsOpen = angular.noop,
36940 toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
36941 appendToBody = false,
36943 keynavEnabled = false,
36944 selectedOption = null,
36945 body = $document.find('body');
36947 $element.addClass('dropdown');
36949 this.init = function() {
36950 if ($attrs.isOpen) {
36951 getIsOpen = $parse($attrs.isOpen);
36952 setIsOpen = getIsOpen.assign;
36954 $scope.$watch(getIsOpen, function(value) {
36955 scope.isOpen = !!value;
36959 if (angular.isDefined($attrs.dropdownAppendTo)) {
36960 var appendToEl = $parse($attrs.dropdownAppendTo)(scope);
36962 appendTo = angular.element(appendToEl);
36966 appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
36967 keynavEnabled = angular.isDefined($attrs.keyboardNav);
36969 if (appendToBody && !appendTo) {
36973 if (appendTo && self.dropdownMenu) {
36974 appendTo.append(self.dropdownMenu);
36975 $element.on('$destroy', function handleDestroyEvent() {
36976 self.dropdownMenu.remove();
36981 this.toggle = function(open) {
36982 return scope.isOpen = arguments.length ? !!open : !scope.isOpen;
36985 // Allow other directives to watch status
36986 this.isOpen = function() {
36987 return scope.isOpen;
36990 scope.getToggleElement = function() {
36991 return self.toggleElement;
36994 scope.getAutoClose = function() {
36995 return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled'
36998 scope.getElement = function() {
37002 scope.isKeynavEnabled = function() {
37003 return keynavEnabled;
37006 scope.focusDropdownEntry = function(keyCode) {
37007 var elems = self.dropdownMenu ? //If append to body is used.
37008 angular.element(self.dropdownMenu).find('a') :
37009 $element.find('ul').eq(0).find('a');
37013 if (!angular.isNumber(self.selectedOption)) {
37014 self.selectedOption = 0;
37016 self.selectedOption = self.selectedOption === elems.length - 1 ?
37017 self.selectedOption :
37018 self.selectedOption + 1;
37023 if (!angular.isNumber(self.selectedOption)) {
37024 self.selectedOption = elems.length - 1;
37026 self.selectedOption = self.selectedOption === 0 ?
37027 0 : self.selectedOption - 1;
37032 elems[self.selectedOption].focus();
37035 scope.getDropdownElement = function() {
37036 return self.dropdownMenu;
37039 scope.focusToggleElement = function() {
37040 if (self.toggleElement) {
37041 self.toggleElement[0].focus();
37045 scope.$watch('isOpen', function(isOpen, wasOpen) {
37046 if (appendTo && self.dropdownMenu) {
37047 var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true),
37052 top: pos.top + 'px',
37053 display: isOpen ? 'block' : 'none'
37056 rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
37058 css.left = pos.left + 'px';
37059 css.right = 'auto';
37062 css.right = window.innerWidth -
37063 (pos.left + $element.prop('offsetWidth')) + 'px';
37066 // Need to adjust our positioning to be relative to the appendTo container
37067 // if it's not the body element
37068 if (!appendToBody) {
37069 var appendOffset = $position.offset(appendTo);
37071 css.top = pos.top - appendOffset.top + 'px';
37074 css.left = pos.left - appendOffset.left + 'px';
37076 css.right = window.innerWidth -
37077 (pos.left - appendOffset.left + $element.prop('offsetWidth')) + 'px';
37081 self.dropdownMenu.css(css);
37084 var openContainer = appendTo ? appendTo : $element;
37086 $animate[isOpen ? 'addClass' : 'removeClass'](openContainer, appendTo ? appendToOpenClass : openClass).then(function() {
37087 if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
37088 toggleInvoker($scope, { open: !!isOpen });
37093 if (self.dropdownMenuTemplateUrl) {
37094 $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) {
37095 templateScope = scope.$new();
37096 $compile(tplContent.trim())(templateScope, function(dropdownElement) {
37097 var newEl = dropdownElement;
37098 self.dropdownMenu.replaceWith(newEl);
37099 self.dropdownMenu = newEl;
37104 scope.focusToggleElement();
37105 uibDropdownService.open(scope);
37107 if (self.dropdownMenuTemplateUrl) {
37108 if (templateScope) {
37109 templateScope.$destroy();
37111 var newEl = angular.element('<ul class="dropdown-menu"></ul>');
37112 self.dropdownMenu.replaceWith(newEl);
37113 self.dropdownMenu = newEl;
37116 uibDropdownService.close(scope);
37117 self.selectedOption = null;
37120 if (angular.isFunction(setIsOpen)) {
37121 setIsOpen($scope, isOpen);
37125 $scope.$on('$locationChangeSuccess', function() {
37126 if (scope.getAutoClose() !== 'disabled') {
37127 scope.isOpen = false;
37132 .directive('uibDropdown', function() {
37134 controller: 'UibDropdownController',
37135 link: function(scope, element, attrs, dropdownCtrl) {
37136 dropdownCtrl.init();
37141 .directive('uibDropdownMenu', function() {
37144 require: '?^uibDropdown',
37145 link: function(scope, element, attrs, dropdownCtrl) {
37146 if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {
37150 element.addClass('dropdown-menu');
37152 var tplUrl = attrs.templateUrl;
37154 dropdownCtrl.dropdownMenuTemplateUrl = tplUrl;
37157 if (!dropdownCtrl.dropdownMenu) {
37158 dropdownCtrl.dropdownMenu = element;
37164 .directive('uibDropdownToggle', function() {
37166 require: '?^uibDropdown',
37167 link: function(scope, element, attrs, dropdownCtrl) {
37168 if (!dropdownCtrl) {
37172 element.addClass('dropdown-toggle');
37174 dropdownCtrl.toggleElement = element;
37176 var toggleDropdown = function(event) {
37177 event.preventDefault();
37179 if (!element.hasClass('disabled') && !attrs.disabled) {
37180 scope.$apply(function() {
37181 dropdownCtrl.toggle();
37186 element.bind('click', toggleDropdown);
37189 element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
37190 scope.$watch(dropdownCtrl.isOpen, function(isOpen) {
37191 element.attr('aria-expanded', !!isOpen);
37194 scope.$on('$destroy', function() {
37195 element.unbind('click', toggleDropdown);
37201 angular.module('ui.bootstrap.stackedMap', [])
37203 * A helper, internal data structure that acts as a map but also allows getting / removing
37204 * elements in the LIFO order
37206 .factory('$$stackedMap', function() {
37208 createNew: function() {
37212 add: function(key, value) {
37218 get: function(key) {
37219 for (var i = 0; i < stack.length; i++) {
37220 if (key === stack[i].key) {
37227 for (var i = 0; i < stack.length; i++) {
37228 keys.push(stack[i].key);
37233 return stack[stack.length - 1];
37235 remove: function(key) {
37237 for (var i = 0; i < stack.length; i++) {
37238 if (key === stack[i].key) {
37243 return stack.splice(idx, 1)[0];
37245 removeTop: function() {
37246 return stack.splice(stack.length - 1, 1)[0];
37248 length: function() {
37249 return stack.length;
37255 angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
37257 * A helper, internal data structure that stores all references attached to key
37259 .factory('$$multiMap', function() {
37261 createNew: function() {
37265 entries: function() {
37266 return Object.keys(map).map(function(key) {
37273 get: function(key) {
37276 hasKey: function(key) {
37280 return Object.keys(map);
37282 put: function(key, value) {
37287 map[key].push(value);
37289 remove: function(key, value) {
37290 var values = map[key];
37296 var idx = values.indexOf(value);
37299 values.splice(idx, 1);
37302 if (!values.length) {
37312 * Pluggable resolve mechanism for the modal resolve resolution
37313 * Supports UI Router's $resolve service
37315 .provider('$uibResolve', function() {
37316 var resolve = this;
37317 this.resolver = null;
37319 this.setResolver = function(resolver) {
37320 this.resolver = resolver;
37323 this.$get = ['$injector', '$q', function($injector, $q) {
37324 var resolver = resolve.resolver ? $injector.get(resolve.resolver) : null;
37326 resolve: function(invocables, locals, parent, self) {
37328 return resolver.resolve(invocables, locals, parent, self);
37333 angular.forEach(invocables, function(value) {
37334 if (angular.isFunction(value) || angular.isArray(value)) {
37335 promises.push($q.resolve($injector.invoke(value)));
37336 } else if (angular.isString(value)) {
37337 promises.push($q.resolve($injector.get(value)));
37339 promises.push($q.resolve(value));
37343 return $q.all(promises).then(function(resolves) {
37344 var resolveObj = {};
37345 var resolveIter = 0;
37346 angular.forEach(invocables, function(value, key) {
37347 resolveObj[key] = resolves[resolveIter++];
37358 * A helper directive for the $modal service. It creates a backdrop element.
37360 .directive('uibModalBackdrop', ['$animateCss', '$injector', '$uibModalStack',
37361 function($animateCss, $injector, $modalStack) {
37364 templateUrl: 'uib/template/modal/backdrop.html',
37365 compile: function(tElement, tAttrs) {
37366 tElement.addClass(tAttrs.backdropClass);
37371 function linkFn(scope, element, attrs) {
37372 if (attrs.modalInClass) {
37373 $animateCss(element, {
37374 addClass: attrs.modalInClass
37377 scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
37378 var done = setIsAsync();
37379 if (scope.modalOptions.animation) {
37380 $animateCss(element, {
37381 removeClass: attrs.modalInClass
37382 }).start().then(done);
37391 .directive('uibModalWindow', ['$uibModalStack', '$q', '$animate', '$animateCss', '$document',
37392 function($modalStack, $q, $animate, $animateCss, $document) {
37399 templateUrl: function(tElement, tAttrs) {
37400 return tAttrs.templateUrl || 'uib/template/modal/window.html';
37402 link: function(scope, element, attrs) {
37403 element.addClass(attrs.windowClass || '');
37404 element.addClass(attrs.windowTopClass || '');
37405 scope.size = attrs.size;
37407 scope.close = function(evt) {
37408 var modal = $modalStack.getTop();
37409 if (modal && modal.value.backdrop &&
37410 modal.value.backdrop !== 'static' &&
37411 evt.target === evt.currentTarget) {
37412 evt.preventDefault();
37413 evt.stopPropagation();
37414 $modalStack.dismiss(modal.key, 'backdrop click');
37418 // moved from template to fix issue #2280
37419 element.on('click', scope.close);
37421 // This property is only added to the scope for the purpose of detecting when this directive is rendered.
37422 // We can detect that by using this property in the template associated with this directive and then use
37423 // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
37424 scope.$isRendered = true;
37426 // Deferred object that will be resolved when this modal is render.
37427 var modalRenderDeferObj = $q.defer();
37428 // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready.
37429 // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template.
37430 attrs.$observe('modalRender', function(value) {
37431 if (value === 'true') {
37432 modalRenderDeferObj.resolve();
37436 modalRenderDeferObj.promise.then(function() {
37437 var animationPromise = null;
37439 if (attrs.modalInClass) {
37440 animationPromise = $animateCss(element, {
37441 addClass: attrs.modalInClass
37444 scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
37445 var done = setIsAsync();
37447 $animateCss(element, {
37448 removeClass: attrs.modalInClass
37449 }).start().then(done);
37451 $animate.removeClass(element, attrs.modalInClass).then(done);
37457 $q.when(animationPromise).then(function() {
37459 * If something within the freshly-opened modal already has focus (perhaps via a
37460 * directive that causes focus). then no need to try and focus anything.
37462 if (!($document[0].activeElement && element[0].contains($document[0].activeElement))) {
37463 var inputWithAutofocus = element[0].querySelector('[autofocus]');
37465 * Auto-focusing of a freshly-opened modal element causes any child elements
37466 * with the autofocus attribute to lose focus. This is an issue on touch
37467 * based devices which will show and then hide the onscreen keyboard.
37468 * Attempts to refocus the autofocus element via JavaScript will not reopen
37469 * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
37470 * the modal element if the modal does not contain an autofocus element.
37472 if (inputWithAutofocus) {
37473 inputWithAutofocus.focus();
37475 element[0].focus();
37480 // Notify {@link $modalStack} that modal is rendered.
37481 var modal = $modalStack.getTop();
37483 $modalStack.modalRendered(modal.key);
37490 .directive('uibModalAnimationClass', function() {
37492 compile: function(tElement, tAttrs) {
37493 if (tAttrs.modalAnimation) {
37494 tElement.addClass(tAttrs.uibModalAnimationClass);
37500 .directive('uibModalTransclude', function() {
37502 link: function(scope, element, attrs, controller, transclude) {
37503 transclude(scope.$parent, function(clone) {
37505 element.append(clone);
37511 .factory('$uibModalStack', ['$animate', '$animateCss', '$document',
37512 '$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap',
37513 function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap) {
37514 var OPENED_MODAL_CLASS = 'modal-open';
37516 var backdropDomEl, backdropScope;
37517 var openedWindows = $$stackedMap.createNew();
37518 var openedClasses = $$multiMap.createNew();
37519 var $modalStack = {
37520 NOW_CLOSING_EVENT: 'modal.stack.now-closing'
37523 //Modal focus behavior
37524 var focusableElementList;
37525 var focusIndex = 0;
37526 var tababbleSelector = 'a[href], area[href], input:not([disabled]), ' +
37527 'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' +
37528 'iframe, object, embed, *[tabindex], *[contenteditable=true]';
37530 function backdropIndex() {
37531 var topBackdropIndex = -1;
37532 var opened = openedWindows.keys();
37533 for (var i = 0; i < opened.length; i++) {
37534 if (openedWindows.get(opened[i]).value.backdrop) {
37535 topBackdropIndex = i;
37538 return topBackdropIndex;
37541 $rootScope.$watch(backdropIndex, function(newBackdropIndex) {
37542 if (backdropScope) {
37543 backdropScope.index = newBackdropIndex;
37547 function removeModalWindow(modalInstance, elementToReceiveFocus) {
37548 var modalWindow = openedWindows.get(modalInstance).value;
37549 var appendToElement = modalWindow.appendTo;
37551 //clean up the stack
37552 openedWindows.remove(modalInstance);
37554 removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() {
37555 var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS;
37556 openedClasses.remove(modalBodyClass, modalInstance);
37557 appendToElement.toggleClass(modalBodyClass, openedClasses.hasKey(modalBodyClass));
37558 toggleTopWindowClass(true);
37560 checkRemoveBackdrop();
37562 //move focus to specified element if available, or else to body
37563 if (elementToReceiveFocus && elementToReceiveFocus.focus) {
37564 elementToReceiveFocus.focus();
37565 } else if (appendToElement.focus) {
37566 appendToElement.focus();
37570 // Add or remove "windowTopClass" from the top window in the stack
37571 function toggleTopWindowClass(toggleSwitch) {
37574 if (openedWindows.length() > 0) {
37575 modalWindow = openedWindows.top().value;
37576 modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch);
37580 function checkRemoveBackdrop() {
37581 //remove backdrop if no longer needed
37582 if (backdropDomEl && backdropIndex() === -1) {
37583 var backdropScopeRef = backdropScope;
37584 removeAfterAnimate(backdropDomEl, backdropScope, function() {
37585 backdropScopeRef = null;
37587 backdropDomEl = undefined;
37588 backdropScope = undefined;
37592 function removeAfterAnimate(domEl, scope, done, closedDeferred) {
37594 var asyncPromise = null;
37595 var setIsAsync = function() {
37596 if (!asyncDeferred) {
37597 asyncDeferred = $q.defer();
37598 asyncPromise = asyncDeferred.promise;
37601 return function asyncDone() {
37602 asyncDeferred.resolve();
37605 scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync);
37607 // Note that it's intentional that asyncPromise might be null.
37608 // That's when setIsAsync has not been called during the
37609 // NOW_CLOSING_EVENT broadcast.
37610 return $q.when(asyncPromise).then(afterAnimating);
37612 function afterAnimating() {
37613 if (afterAnimating.done) {
37616 afterAnimating.done = true;
37618 $animateCss(domEl, {
37620 }).start().then(function() {
37622 if (closedDeferred) {
37623 closedDeferred.resolve();
37634 $document.on('keydown', keydownListener);
37636 $rootScope.$on('$destroy', function() {
37637 $document.off('keydown', keydownListener);
37640 function keydownListener(evt) {
37641 if (evt.isDefaultPrevented()) {
37645 var modal = openedWindows.top();
37647 switch (evt.which) {
37649 if (modal.value.keyboard) {
37650 evt.preventDefault();
37651 $rootScope.$apply(function() {
37652 $modalStack.dismiss(modal.key, 'escape key press');
37658 $modalStack.loadFocusElementList(modal);
37659 var focusChanged = false;
37660 if (evt.shiftKey) {
37661 if ($modalStack.isFocusInFirstItem(evt)) {
37662 focusChanged = $modalStack.focusLastFocusableElement();
37665 if ($modalStack.isFocusInLastItem(evt)) {
37666 focusChanged = $modalStack.focusFirstFocusableElement();
37670 if (focusChanged) {
37671 evt.preventDefault();
37672 evt.stopPropagation();
37680 $modalStack.open = function(modalInstance, modal) {
37681 var modalOpener = $document[0].activeElement,
37682 modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS;
37684 toggleTopWindowClass(false);
37686 openedWindows.add(modalInstance, {
37687 deferred: modal.deferred,
37688 renderDeferred: modal.renderDeferred,
37689 closedDeferred: modal.closedDeferred,
37690 modalScope: modal.scope,
37691 backdrop: modal.backdrop,
37692 keyboard: modal.keyboard,
37693 openedClass: modal.openedClass,
37694 windowTopClass: modal.windowTopClass,
37695 animation: modal.animation,
37696 appendTo: modal.appendTo
37699 openedClasses.put(modalBodyClass, modalInstance);
37701 var appendToElement = modal.appendTo,
37702 currBackdropIndex = backdropIndex();
37704 if (!appendToElement.length) {
37705 throw new Error('appendTo element not found. Make sure that the element passed is in DOM.');
37708 if (currBackdropIndex >= 0 && !backdropDomEl) {
37709 backdropScope = $rootScope.$new(true);
37710 backdropScope.modalOptions = modal;
37711 backdropScope.index = currBackdropIndex;
37712 backdropDomEl = angular.element('<div uib-modal-backdrop="modal-backdrop"></div>');
37713 backdropDomEl.attr('backdrop-class', modal.backdropClass);
37714 if (modal.animation) {
37715 backdropDomEl.attr('modal-animation', 'true');
37717 $compile(backdropDomEl)(backdropScope);
37718 $animate.enter(backdropDomEl, appendToElement);
37721 var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>');
37722 angularDomEl.attr({
37723 'template-url': modal.windowTemplateUrl,
37724 'window-class': modal.windowClass,
37725 'window-top-class': modal.windowTopClass,
37726 'size': modal.size,
37727 'index': openedWindows.length() - 1,
37728 'animate': 'animate'
37729 }).html(modal.content);
37730 if (modal.animation) {
37731 angularDomEl.attr('modal-animation', 'true');
37734 $animate.enter(angularDomEl, appendToElement)
37736 $compile(angularDomEl)(modal.scope);
37737 $animate.addClass(appendToElement, modalBodyClass);
37740 openedWindows.top().value.modalDomEl = angularDomEl;
37741 openedWindows.top().value.modalOpener = modalOpener;
37743 $modalStack.clearFocusListCache();
37746 function broadcastClosing(modalWindow, resultOrReason, closing) {
37747 return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented;
37750 $modalStack.close = function(modalInstance, result) {
37751 var modalWindow = openedWindows.get(modalInstance);
37752 if (modalWindow && broadcastClosing(modalWindow, result, true)) {
37753 modalWindow.value.modalScope.$$uibDestructionScheduled = true;
37754 modalWindow.value.deferred.resolve(result);
37755 removeModalWindow(modalInstance, modalWindow.value.modalOpener);
37758 return !modalWindow;
37761 $modalStack.dismiss = function(modalInstance, reason) {
37762 var modalWindow = openedWindows.get(modalInstance);
37763 if (modalWindow && broadcastClosing(modalWindow, reason, false)) {
37764 modalWindow.value.modalScope.$$uibDestructionScheduled = true;
37765 modalWindow.value.deferred.reject(reason);
37766 removeModalWindow(modalInstance, modalWindow.value.modalOpener);
37769 return !modalWindow;
37772 $modalStack.dismissAll = function(reason) {
37773 var topModal = this.getTop();
37774 while (topModal && this.dismiss(topModal.key, reason)) {
37775 topModal = this.getTop();
37779 $modalStack.getTop = function() {
37780 return openedWindows.top();
37783 $modalStack.modalRendered = function(modalInstance) {
37784 var modalWindow = openedWindows.get(modalInstance);
37786 modalWindow.value.renderDeferred.resolve();
37790 $modalStack.focusFirstFocusableElement = function() {
37791 if (focusableElementList.length > 0) {
37792 focusableElementList[0].focus();
37797 $modalStack.focusLastFocusableElement = function() {
37798 if (focusableElementList.length > 0) {
37799 focusableElementList[focusableElementList.length - 1].focus();
37805 $modalStack.isFocusInFirstItem = function(evt) {
37806 if (focusableElementList.length > 0) {
37807 return (evt.target || evt.srcElement) === focusableElementList[0];
37812 $modalStack.isFocusInLastItem = function(evt) {
37813 if (focusableElementList.length > 0) {
37814 return (evt.target || evt.srcElement) === focusableElementList[focusableElementList.length - 1];
37819 $modalStack.clearFocusListCache = function() {
37820 focusableElementList = [];
37824 $modalStack.loadFocusElementList = function(modalWindow) {
37825 if (focusableElementList === undefined || !focusableElementList.length) {
37827 var modalDomE1 = modalWindow.value.modalDomEl;
37828 if (modalDomE1 && modalDomE1.length) {
37829 focusableElementList = modalDomE1[0].querySelectorAll(tababbleSelector);
37835 return $modalStack;
37838 .provider('$uibModal', function() {
37839 var $modalProvider = {
37842 backdrop: true, //can also be false or 'static'
37845 $get: ['$rootScope', '$q', '$document', '$templateRequest', '$controller', '$uibResolve', '$uibModalStack',
37846 function ($rootScope, $q, $document, $templateRequest, $controller, $uibResolve, $modalStack) {
37849 function getTemplatePromise(options) {
37850 return options.template ? $q.when(options.template) :
37851 $templateRequest(angular.isFunction(options.templateUrl) ?
37852 options.templateUrl() : options.templateUrl);
37855 var promiseChain = null;
37856 $modal.getPromiseChain = function() {
37857 return promiseChain;
37860 $modal.open = function(modalOptions) {
37861 var modalResultDeferred = $q.defer();
37862 var modalOpenedDeferred = $q.defer();
37863 var modalClosedDeferred = $q.defer();
37864 var modalRenderDeferred = $q.defer();
37866 //prepare an instance of a modal to be injected into controllers and returned to a caller
37867 var modalInstance = {
37868 result: modalResultDeferred.promise,
37869 opened: modalOpenedDeferred.promise,
37870 closed: modalClosedDeferred.promise,
37871 rendered: modalRenderDeferred.promise,
37872 close: function (result) {
37873 return $modalStack.close(modalInstance, result);
37875 dismiss: function (reason) {
37876 return $modalStack.dismiss(modalInstance, reason);
37880 //merge and clean up options
37881 modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
37882 modalOptions.resolve = modalOptions.resolve || {};
37883 modalOptions.appendTo = modalOptions.appendTo || $document.find('body').eq(0);
37886 if (!modalOptions.template && !modalOptions.templateUrl) {
37887 throw new Error('One of template or templateUrl options is required.');
37890 var templateAndResolvePromise =
37891 $q.all([getTemplatePromise(modalOptions), $uibResolve.resolve(modalOptions.resolve, {}, null, null)]);
37893 function resolveWithTemplate() {
37894 return templateAndResolvePromise;
37897 // Wait for the resolution of the existing promise chain.
37898 // Then switch to our own combined promise dependency (regardless of how the previous modal fared).
37899 // Then add to $modalStack and resolve opened.
37900 // Finally clean up the chain variable if no subsequent modal has overwritten it.
37902 samePromise = promiseChain = $q.all([promiseChain])
37903 .then(resolveWithTemplate, resolveWithTemplate)
37904 .then(function resolveSuccess(tplAndVars) {
37905 var providedScope = modalOptions.scope || $rootScope;
37907 var modalScope = providedScope.$new();
37908 modalScope.$close = modalInstance.close;
37909 modalScope.$dismiss = modalInstance.dismiss;
37911 modalScope.$on('$destroy', function() {
37912 if (!modalScope.$$uibDestructionScheduled) {
37913 modalScope.$dismiss('$uibUnscheduledDestruction');
37917 var ctrlInstance, ctrlLocals = {};
37920 if (modalOptions.controller) {
37921 ctrlLocals.$scope = modalScope;
37922 ctrlLocals.$uibModalInstance = modalInstance;
37923 angular.forEach(tplAndVars[1], function(value, key) {
37924 ctrlLocals[key] = value;
37927 ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
37928 if (modalOptions.controllerAs) {
37929 if (modalOptions.bindToController) {
37930 ctrlInstance.$close = modalScope.$close;
37931 ctrlInstance.$dismiss = modalScope.$dismiss;
37932 angular.extend(ctrlInstance, providedScope);
37935 modalScope[modalOptions.controllerAs] = ctrlInstance;
37939 $modalStack.open(modalInstance, {
37941 deferred: modalResultDeferred,
37942 renderDeferred: modalRenderDeferred,
37943 closedDeferred: modalClosedDeferred,
37944 content: tplAndVars[0],
37945 animation: modalOptions.animation,
37946 backdrop: modalOptions.backdrop,
37947 keyboard: modalOptions.keyboard,
37948 backdropClass: modalOptions.backdropClass,
37949 windowTopClass: modalOptions.windowTopClass,
37950 windowClass: modalOptions.windowClass,
37951 windowTemplateUrl: modalOptions.windowTemplateUrl,
37952 size: modalOptions.size,
37953 openedClass: modalOptions.openedClass,
37954 appendTo: modalOptions.appendTo
37956 modalOpenedDeferred.resolve(true);
37958 }, function resolveError(reason) {
37959 modalOpenedDeferred.reject(reason);
37960 modalResultDeferred.reject(reason);
37961 })['finally'](function() {
37962 if (promiseChain === samePromise) {
37963 promiseChain = null;
37967 return modalInstance;
37975 return $modalProvider;
37978 angular.module('ui.bootstrap.paging', [])
37980 * Helper internal service for generating common controller code between the
37981 * pager and pagination components
37983 .factory('uibPaging', ['$parse', function($parse) {
37985 create: function(ctrl, $scope, $attrs) {
37986 ctrl.setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
37987 ctrl.ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl
37989 ctrl.init = function(ngModelCtrl, config) {
37990 ctrl.ngModelCtrl = ngModelCtrl;
37991 ctrl.config = config;
37993 ngModelCtrl.$render = function() {
37997 if ($attrs.itemsPerPage) {
37998 $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) {
37999 ctrl.itemsPerPage = parseInt(value, 10);
38000 $scope.totalPages = ctrl.calculateTotalPages();
38004 ctrl.itemsPerPage = config.itemsPerPage;
38007 $scope.$watch('totalItems', function(newTotal, oldTotal) {
38008 if (angular.isDefined(newTotal) || newTotal !== oldTotal) {
38009 $scope.totalPages = ctrl.calculateTotalPages();
38015 ctrl.calculateTotalPages = function() {
38016 var totalPages = ctrl.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / ctrl.itemsPerPage);
38017 return Math.max(totalPages || 0, 1);
38020 ctrl.render = function() {
38021 $scope.page = parseInt(ctrl.ngModelCtrl.$viewValue, 10) || 1;
38024 $scope.selectPage = function(page, evt) {
38026 evt.preventDefault();
38029 var clickAllowed = !$scope.ngDisabled || !evt;
38030 if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) {
38031 if (evt && evt.target) {
38034 ctrl.ngModelCtrl.$setViewValue(page);
38035 ctrl.ngModelCtrl.$render();
38039 $scope.getText = function(key) {
38040 return $scope[key + 'Text'] || ctrl.config[key + 'Text'];
38043 $scope.noPrevious = function() {
38044 return $scope.page === 1;
38047 $scope.noNext = function() {
38048 return $scope.page === $scope.totalPages;
38051 ctrl.updatePage = function() {
38052 ctrl.setNumPages($scope.$parent, $scope.totalPages); // Readonly variable
38054 if ($scope.page > $scope.totalPages) {
38055 $scope.selectPage($scope.totalPages);
38057 ctrl.ngModelCtrl.$render();
38064 angular.module('ui.bootstrap.pager', ['ui.bootstrap.paging'])
38066 .controller('UibPagerController', ['$scope', '$attrs', 'uibPaging', 'uibPagerConfig', function($scope, $attrs, uibPaging, uibPagerConfig) {
38067 $scope.align = angular.isDefined($attrs.align) ? $scope.$parent.$eval($attrs.align) : uibPagerConfig.align;
38069 uibPaging.create(this, $scope, $attrs);
38072 .constant('uibPagerConfig', {
38074 previousText: '« Previous',
38075 nextText: 'Next »',
38079 .directive('uibPager', ['uibPagerConfig', function(uibPagerConfig) {
38087 require: ['uibPager', '?ngModel'],
38088 controller: 'UibPagerController',
38089 controllerAs: 'pager',
38090 templateUrl: function(element, attrs) {
38091 return attrs.templateUrl || 'uib/template/pager/pager.html';
38094 link: function(scope, element, attrs, ctrls) {
38095 var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
38097 if (!ngModelCtrl) {
38098 return; // do nothing if no ng-model
38101 paginationCtrl.init(ngModelCtrl, uibPagerConfig);
38106 angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging'])
38107 .controller('UibPaginationController', ['$scope', '$attrs', '$parse', 'uibPaging', 'uibPaginationConfig', function($scope, $attrs, $parse, uibPaging, uibPaginationConfig) {
38109 // Setup configuration parameters
38110 var maxSize = angular.isDefined($attrs.maxSize) ? $scope.$parent.$eval($attrs.maxSize) : uibPaginationConfig.maxSize,
38111 rotate = angular.isDefined($attrs.rotate) ? $scope.$parent.$eval($attrs.rotate) : uibPaginationConfig.rotate,
38112 forceEllipses = angular.isDefined($attrs.forceEllipses) ? $scope.$parent.$eval($attrs.forceEllipses) : uibPaginationConfig.forceEllipses,
38113 boundaryLinkNumbers = angular.isDefined($attrs.boundaryLinkNumbers) ? $scope.$parent.$eval($attrs.boundaryLinkNumbers) : uibPaginationConfig.boundaryLinkNumbers;
38114 $scope.boundaryLinks = angular.isDefined($attrs.boundaryLinks) ? $scope.$parent.$eval($attrs.boundaryLinks) : uibPaginationConfig.boundaryLinks;
38115 $scope.directionLinks = angular.isDefined($attrs.directionLinks) ? $scope.$parent.$eval($attrs.directionLinks) : uibPaginationConfig.directionLinks;
38117 uibPaging.create(this, $scope, $attrs);
38119 if ($attrs.maxSize) {
38120 $scope.$parent.$watch($parse($attrs.maxSize), function(value) {
38121 maxSize = parseInt(value, 10);
38126 // Create page object used in template
38127 function makePage(number, text, isActive) {
38135 function getPages(currentPage, totalPages) {
38138 // Default page limits
38139 var startPage = 1, endPage = totalPages;
38140 var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages;
38142 // recompute if maxSize
38145 // Current page is displayed in the middle of the visible ones
38146 startPage = Math.max(currentPage - Math.floor(maxSize / 2), 1);
38147 endPage = startPage + maxSize - 1;
38149 // Adjust if limit is exceeded
38150 if (endPage > totalPages) {
38151 endPage = totalPages;
38152 startPage = endPage - maxSize + 1;
38155 // Visible pages are paginated with maxSize
38156 startPage = (Math.ceil(currentPage / maxSize) - 1) * maxSize + 1;
38158 // Adjust last page if limit is exceeded
38159 endPage = Math.min(startPage + maxSize - 1, totalPages);
38163 // Add page number links
38164 for (var number = startPage; number <= endPage; number++) {
38165 var page = makePage(number, number, number === currentPage);
38169 // Add links to move between page sets
38170 if (isMaxSized && maxSize > 0 && (!rotate || forceEllipses || boundaryLinkNumbers)) {
38171 if (startPage > 1) {
38172 if (!boundaryLinkNumbers || startPage > 3) { //need ellipsis for all options unless range is too close to beginning
38173 var previousPageSet = makePage(startPage - 1, '...', false);
38174 pages.unshift(previousPageSet);
38176 if (boundaryLinkNumbers) {
38177 if (startPage === 3) { //need to replace ellipsis when the buttons would be sequential
38178 var secondPageLink = makePage(2, '2', false);
38179 pages.unshift(secondPageLink);
38181 //add the first page
38182 var firstPageLink = makePage(1, '1', false);
38183 pages.unshift(firstPageLink);
38187 if (endPage < totalPages) {
38188 if (!boundaryLinkNumbers || endPage < totalPages - 2) { //need ellipsis for all options unless range is too close to end
38189 var nextPageSet = makePage(endPage + 1, '...', false);
38190 pages.push(nextPageSet);
38192 if (boundaryLinkNumbers) {
38193 if (endPage === totalPages - 2) { //need to replace ellipsis when the buttons would be sequential
38194 var secondToLastPageLink = makePage(totalPages - 1, totalPages - 1, false);
38195 pages.push(secondToLastPageLink);
38197 //add the last page
38198 var lastPageLink = makePage(totalPages, totalPages, false);
38199 pages.push(lastPageLink);
38206 var originalRender = this.render;
38207 this.render = function() {
38209 if ($scope.page > 0 && $scope.page <= $scope.totalPages) {
38210 $scope.pages = getPages($scope.page, $scope.totalPages);
38215 .constant('uibPaginationConfig', {
38217 boundaryLinks: false,
38218 boundaryLinkNumbers: false,
38219 directionLinks: true,
38220 firstText: 'First',
38221 previousText: 'Previous',
38225 forceEllipses: false
38228 .directive('uibPagination', ['$parse', 'uibPaginationConfig', function($parse, uibPaginationConfig) {
38238 require: ['uibPagination', '?ngModel'],
38239 controller: 'UibPaginationController',
38240 controllerAs: 'pagination',
38241 templateUrl: function(element, attrs) {
38242 return attrs.templateUrl || 'uib/template/pagination/pagination.html';
38245 link: function(scope, element, attrs, ctrls) {
38246 var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
38248 if (!ngModelCtrl) {
38249 return; // do nothing if no ng-model
38252 paginationCtrl.init(ngModelCtrl, uibPaginationConfig);
38258 * The following features are still outstanding: animation as a
38259 * function, placement as a function, inside, support for more triggers than
38260 * just mouse enter/leave, html tooltips, and selector delegation.
38262 angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.stackedMap'])
38265 * The $tooltip service creates tooltip- and popover-like directives as well as
38266 * houses global options for them.
38268 .provider('$uibTooltip', function() {
38269 // The default options tooltip and popover.
38270 var defaultOptions = {
38272 placementClassPrefix: '',
38275 popupCloseDelay: 0,
38276 useContentExp: false
38279 // Default hide triggers for each show trigger
38281 'mouseenter': 'mouseleave',
38283 'outsideClick': 'outsideClick',
38288 // The options specified to the provider globally.
38289 var globalOptions = {};
38292 * `options({})` allows global configuration of all tooltips in the
38295 * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
38296 * // place tooltips left instead of top by default
38297 * $tooltipProvider.options( { placement: 'left' } );
38300 this.options = function(value) {
38301 angular.extend(globalOptions, value);
38305 * This allows you to extend the set of trigger mappings available. E.g.:
38307 * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
38309 this.setTriggers = function setTriggers(triggers) {
38310 angular.extend(triggerMap, triggers);
38314 * This is a helper function for translating camel-case to snake_case.
38316 function snake_case(name) {
38317 var regexp = /[A-Z]/g;
38318 var separator = '-';
38319 return name.replace(regexp, function(letter, pos) {
38320 return (pos ? separator : '') + letter.toLowerCase();
38325 * Returns the actual instance of the $tooltip service.
38326 * TODO support multiple triggers
38328 this.$get = ['$window', '$compile', '$timeout', '$document', '$uibPosition', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) {
38329 var openedTooltips = $$stackedMap.createNew();
38330 $document.on('keypress', keypressListener);
38332 $rootScope.$on('$destroy', function() {
38333 $document.off('keypress', keypressListener);
38336 function keypressListener(e) {
38337 if (e.which === 27) {
38338 var last = openedTooltips.top();
38340 last.value.close();
38341 openedTooltips.removeTop();
38347 return function $tooltip(ttType, prefix, defaultTriggerShow, options) {
38348 options = angular.extend({}, defaultOptions, globalOptions, options);
38351 * Returns an object of show and hide triggers.
38353 * If a trigger is supplied,
38354 * it is used to show the tooltip; otherwise, it will use the `trigger`
38355 * option passed to the `$tooltipProvider.options` method; else it will
38356 * default to the trigger supplied to this directive factory.
38358 * The hide trigger is based on the show trigger. If the `trigger` option
38359 * was passed to the `$tooltipProvider.options` method, it will use the
38360 * mapped trigger from `triggerMap` or the passed trigger if the map is
38361 * undefined; otherwise, it uses the `triggerMap` value of the show
38362 * trigger; else it will just use the show trigger.
38364 function getTriggers(trigger) {
38365 var show = (trigger || options.trigger || defaultTriggerShow).split(' ');
38366 var hide = show.map(function(trigger) {
38367 return triggerMap[trigger] || trigger;
38375 var directiveName = snake_case(ttType);
38377 var startSym = $interpolate.startSymbol();
38378 var endSym = $interpolate.endSymbol();
38380 '<div '+ directiveName + '-popup '+
38381 'title="' + startSym + 'title' + endSym + '" '+
38382 (options.useContentExp ?
38383 'content-exp="contentExp()" ' :
38384 'content="' + startSym + 'content' + endSym + '" ') +
38385 'placement="' + startSym + 'placement' + endSym + '" '+
38386 'popup-class="' + startSym + 'popupClass' + endSym + '" '+
38387 'animation="animation" ' +
38388 'is-open="isOpen"' +
38389 'origin-scope="origScope" ' +
38390 'style="visibility: hidden; display: block; top: -9999px; left: -9999px;"' +
38395 compile: function(tElem, tAttrs) {
38396 var tooltipLinker = $compile(template);
38398 return function link(scope, element, attrs, tooltipCtrl) {
38400 var tooltipLinkedScope;
38401 var transitionTimeout;
38404 var positionTimeout;
38405 var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false;
38406 var triggers = getTriggers(undefined);
38407 var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']);
38408 var ttScope = scope.$new(true);
38409 var repositionScheduled = false;
38410 var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false;
38411 var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false;
38412 var observers = [];
38414 var positionTooltip = function() {
38415 // check if tooltip exists and is not empty
38416 if (!tooltip || !tooltip.html()) { return; }
38418 if (!positionTimeout) {
38419 positionTimeout = $timeout(function() {
38420 // Reset the positioning.
38421 tooltip.css({ top: 0, left: 0 });
38423 // Now set the calculated positioning.
38424 var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);
38425 tooltip.css({ top: ttPosition.top + 'px', left: ttPosition.left + 'px', visibility: 'visible' });
38427 // If the placement class is prefixed, still need
38428 // to remove the TWBS standard class.
38429 if (options.placementClassPrefix) {
38430 tooltip.removeClass('top bottom left right');
38433 tooltip.removeClass(
38434 options.placementClassPrefix + 'top ' +
38435 options.placementClassPrefix + 'top-left ' +
38436 options.placementClassPrefix + 'top-right ' +
38437 options.placementClassPrefix + 'bottom ' +
38438 options.placementClassPrefix + 'bottom-left ' +
38439 options.placementClassPrefix + 'bottom-right ' +
38440 options.placementClassPrefix + 'left ' +
38441 options.placementClassPrefix + 'left-top ' +
38442 options.placementClassPrefix + 'left-bottom ' +
38443 options.placementClassPrefix + 'right ' +
38444 options.placementClassPrefix + 'right-top ' +
38445 options.placementClassPrefix + 'right-bottom');
38447 var placement = ttPosition.placement.split('-');
38448 tooltip.addClass(placement[0], options.placementClassPrefix + ttPosition.placement);
38449 $position.positionArrow(tooltip, ttPosition.placement);
38451 positionTimeout = null;
38456 // Set up the correct scope to allow transclusion later
38457 ttScope.origScope = scope;
38459 // By default, the tooltip is not open.
38460 // TODO add ability to start tooltip opened
38461 ttScope.isOpen = false;
38462 openedTooltips.add(ttScope, {
38466 function toggleTooltipBind() {
38467 if (!ttScope.isOpen) {
38474 // Show the tooltip with delay if specified, otherwise show it immediately
38475 function showTooltipBind() {
38476 if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) {
38483 if (ttScope.popupDelay) {
38484 // Do nothing if the tooltip was already scheduled to pop-up.
38485 // This happens if show is triggered multiple times before any hide is triggered.
38486 if (!showTimeout) {
38487 showTimeout = $timeout(show, ttScope.popupDelay, false);
38494 function hideTooltipBind() {
38497 if (ttScope.popupCloseDelay) {
38498 if (!hideTimeout) {
38499 hideTimeout = $timeout(hide, ttScope.popupCloseDelay, false);
38506 // Show the tooltip popup element.
38511 // Don't show empty tooltips.
38512 if (!ttScope.content) {
38513 return angular.noop;
38518 // And show the tooltip.
38519 ttScope.$evalAsync(function() {
38520 ttScope.isOpen = true;
38521 assignIsOpen(true);
38526 function cancelShow() {
38528 $timeout.cancel(showTimeout);
38529 showTimeout = null;
38532 if (positionTimeout) {
38533 $timeout.cancel(positionTimeout);
38534 positionTimeout = null;
38538 // Hide the tooltip popup element.
38544 // First things first: we don't show it anymore.
38545 ttScope.$evalAsync(function() {
38546 ttScope.isOpen = false;
38547 assignIsOpen(false);
38548 // And now we remove it from the DOM. However, if we have animation, we
38549 // need to wait for it to expire beforehand.
38550 // FIXME: this is a placeholder for a port of the transitions library.
38551 // The fade transition in TWBS is 150ms.
38552 if (ttScope.animation) {
38553 if (!transitionTimeout) {
38554 transitionTimeout = $timeout(removeTooltip, 150, false);
38562 function cancelHide() {
38564 $timeout.cancel(hideTimeout);
38565 hideTimeout = null;
38567 if (transitionTimeout) {
38568 $timeout.cancel(transitionTimeout);
38569 transitionTimeout = null;
38573 function createTooltip() {
38574 // There can only be one tooltip element per directive shown at once.
38579 tooltipLinkedScope = ttScope.$new();
38580 tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) {
38581 if (appendToBody) {
38582 $document.find('body').append(tooltip);
38584 element.after(tooltip);
38591 function removeTooltip() {
38594 unregisterObservers();
38600 if (tooltipLinkedScope) {
38601 tooltipLinkedScope.$destroy();
38602 tooltipLinkedScope = null;
38607 * Set the initial scope values. Once
38608 * the tooltip is created, the observers
38609 * will be added to keep things in sync.
38611 function prepareTooltip() {
38612 ttScope.title = attrs[prefix + 'Title'];
38613 if (contentParse) {
38614 ttScope.content = contentParse(scope);
38616 ttScope.content = attrs[ttType];
38619 ttScope.popupClass = attrs[prefix + 'Class'];
38620 ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement;
38622 var delay = parseInt(attrs[prefix + 'PopupDelay'], 10);
38623 var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10);
38624 ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay;
38625 ttScope.popupCloseDelay = !isNaN(closeDelay) ? closeDelay : options.popupCloseDelay;
38628 function assignIsOpen(isOpen) {
38629 if (isOpenParse && angular.isFunction(isOpenParse.assign)) {
38630 isOpenParse.assign(scope, isOpen);
38634 ttScope.contentExp = function() {
38635 return ttScope.content;
38639 * Observe the relevant attributes.
38641 attrs.$observe('disabled', function(val) {
38646 if (val && ttScope.isOpen) {
38652 scope.$watch(isOpenParse, function(val) {
38653 if (ttScope && !val === ttScope.isOpen) {
38654 toggleTooltipBind();
38659 function prepObservers() {
38660 observers.length = 0;
38662 if (contentParse) {
38664 scope.$watch(contentParse, function(val) {
38665 ttScope.content = val;
38666 if (!val && ttScope.isOpen) {
38673 tooltipLinkedScope.$watch(function() {
38674 if (!repositionScheduled) {
38675 repositionScheduled = true;
38676 tooltipLinkedScope.$$postDigest(function() {
38677 repositionScheduled = false;
38678 if (ttScope && ttScope.isOpen) {
38687 attrs.$observe(ttType, function(val) {
38688 ttScope.content = val;
38689 if (!val && ttScope.isOpen) {
38699 attrs.$observe(prefix + 'Title', function(val) {
38700 ttScope.title = val;
38701 if (ttScope.isOpen) {
38708 attrs.$observe(prefix + 'Placement', function(val) {
38709 ttScope.placement = val ? val : options.placement;
38710 if (ttScope.isOpen) {
38717 function unregisterObservers() {
38718 if (observers.length) {
38719 angular.forEach(observers, function(observer) {
38722 observers.length = 0;
38726 // hide tooltips/popovers for outsideClick trigger
38727 function bodyHideTooltipBind(e) {
38728 if (!ttScope || !ttScope.isOpen || !tooltip) {
38731 // make sure the tooltip/popover link or tool tooltip/popover itself were not clicked
38732 if (!element[0].contains(e.target) && !tooltip[0].contains(e.target)) {
38737 var unregisterTriggers = function() {
38738 triggers.show.forEach(function(trigger) {
38739 if (trigger === 'outsideClick') {
38740 element.off('click', toggleTooltipBind);
38742 element.off(trigger, showTooltipBind);
38743 element.off(trigger, toggleTooltipBind);
38746 triggers.hide.forEach(function(trigger) {
38747 if (trigger === 'outsideClick') {
38748 $document.off('click', bodyHideTooltipBind);
38750 element.off(trigger, hideTooltipBind);
38755 function prepTriggers() {
38756 var val = attrs[prefix + 'Trigger'];
38757 unregisterTriggers();
38759 triggers = getTriggers(val);
38761 if (triggers.show !== 'none') {
38762 triggers.show.forEach(function(trigger, idx) {
38763 if (trigger === 'outsideClick') {
38764 element.on('click', toggleTooltipBind);
38765 $document.on('click', bodyHideTooltipBind);
38766 } else if (trigger === triggers.hide[idx]) {
38767 element.on(trigger, toggleTooltipBind);
38768 } else if (trigger) {
38769 element.on(trigger, showTooltipBind);
38770 element.on(triggers.hide[idx], hideTooltipBind);
38773 element.on('keypress', function(e) {
38774 if (e.which === 27) {
38784 var animation = scope.$eval(attrs[prefix + 'Animation']);
38785 ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation;
38787 var appendToBodyVal;
38788 var appendKey = prefix + 'AppendToBody';
38789 if (appendKey in attrs && attrs[appendKey] === undefined) {
38790 appendToBodyVal = true;
38792 appendToBodyVal = scope.$eval(attrs[appendKey]);
38795 appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;
38797 // if a tooltip is attached to <body> we need to remove it on
38798 // location change as its parent scope will probably not be destroyed
38800 if (appendToBody) {
38801 scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess() {
38802 if (ttScope.isOpen) {
38808 // Make sure tooltip is destroyed and removed.
38809 scope.$on('$destroy', function onDestroyTooltip() {
38810 unregisterTriggers();
38812 openedTooltips.remove(ttScope);
38822 // This is mostly ngInclude code but with a custom scope
38823 .directive('uibTooltipTemplateTransclude', [
38824 '$animate', '$sce', '$compile', '$templateRequest',
38825 function ($animate, $sce, $compile, $templateRequest) {
38827 link: function(scope, elem, attrs) {
38828 var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope);
38830 var changeCounter = 0,
38835 var cleanupLastIncludeContent = function() {
38836 if (previousElement) {
38837 previousElement.remove();
38838 previousElement = null;
38841 if (currentScope) {
38842 currentScope.$destroy();
38843 currentScope = null;
38846 if (currentElement) {
38847 $animate.leave(currentElement).then(function() {
38848 previousElement = null;
38850 previousElement = currentElement;
38851 currentElement = null;
38855 scope.$watch($sce.parseAsResourceUrl(attrs.uibTooltipTemplateTransclude), function(src) {
38856 var thisChangeId = ++changeCounter;
38859 //set the 2nd param to true to ignore the template request error so that the inner
38860 //contents and scope can be cleaned up.
38861 $templateRequest(src, true).then(function(response) {
38862 if (thisChangeId !== changeCounter) { return; }
38863 var newScope = origScope.$new();
38864 var template = response;
38866 var clone = $compile(template)(newScope, function(clone) {
38867 cleanupLastIncludeContent();
38868 $animate.enter(clone, elem);
38871 currentScope = newScope;
38872 currentElement = clone;
38874 currentScope.$emit('$includeContentLoaded', src);
38876 if (thisChangeId === changeCounter) {
38877 cleanupLastIncludeContent();
38878 scope.$emit('$includeContentError', src);
38881 scope.$emit('$includeContentRequested', src);
38883 cleanupLastIncludeContent();
38887 scope.$on('$destroy', cleanupLastIncludeContent);
38893 * Note that it's intentional that these classes are *not* applied through $animate.
38894 * They must not be animated as they're expected to be present on the tooltip on
38897 .directive('uibTooltipClasses', ['$uibPosition', function($uibPosition) {
38900 link: function(scope, element, attrs) {
38901 // need to set the primary position so the
38902 // arrow has space during position measure.
38903 // tooltip.positionTooltip()
38904 if (scope.placement) {
38905 // // There are no top-left etc... classes
38906 // // in TWBS, so we need the primary position.
38907 var position = $uibPosition.parsePlacement(scope.placement);
38908 element.addClass(position[0]);
38910 element.addClass('top');
38913 if (scope.popupClass) {
38914 element.addClass(scope.popupClass);
38917 if (scope.animation()) {
38918 element.addClass(attrs.tooltipAnimationClass);
38924 .directive('uibTooltipPopup', function() {
38927 scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
38928 templateUrl: 'uib/template/tooltip/tooltip-popup.html'
38932 .directive('uibTooltip', [ '$uibTooltip', function($uibTooltip) {
38933 return $uibTooltip('uibTooltip', 'tooltip', 'mouseenter');
38936 .directive('uibTooltipTemplatePopup', function() {
38939 scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
38940 originScope: '&' },
38941 templateUrl: 'uib/template/tooltip/tooltip-template-popup.html'
38945 .directive('uibTooltipTemplate', ['$uibTooltip', function($uibTooltip) {
38946 return $uibTooltip('uibTooltipTemplate', 'tooltip', 'mouseenter', {
38947 useContentExp: true
38951 .directive('uibTooltipHtmlPopup', function() {
38954 scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
38955 templateUrl: 'uib/template/tooltip/tooltip-html-popup.html'
38959 .directive('uibTooltipHtml', ['$uibTooltip', function($uibTooltip) {
38960 return $uibTooltip('uibTooltipHtml', 'tooltip', 'mouseenter', {
38961 useContentExp: true
38966 * The following features are still outstanding: popup delay, animation as a
38967 * function, placement as a function, inside, support for more triggers than
38968 * just mouse enter/leave, and selector delegatation.
38970 angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip'])
38972 .directive('uibPopoverTemplatePopup', function() {
38975 scope: { title: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
38976 originScope: '&' },
38977 templateUrl: 'uib/template/popover/popover-template.html'
38981 .directive('uibPopoverTemplate', ['$uibTooltip', function($uibTooltip) {
38982 return $uibTooltip('uibPopoverTemplate', 'popover', 'click', {
38983 useContentExp: true
38987 .directive('uibPopoverHtmlPopup', function() {
38990 scope: { contentExp: '&', title: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
38991 templateUrl: 'uib/template/popover/popover-html.html'
38995 .directive('uibPopoverHtml', ['$uibTooltip', function($uibTooltip) {
38996 return $uibTooltip('uibPopoverHtml', 'popover', 'click', {
38997 useContentExp: true
39001 .directive('uibPopoverPopup', function() {
39004 scope: { title: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
39005 templateUrl: 'uib/template/popover/popover.html'
39009 .directive('uibPopover', ['$uibTooltip', function($uibTooltip) {
39010 return $uibTooltip('uibPopover', 'popover', 'click');
39013 angular.module('ui.bootstrap.progressbar', [])
39015 .constant('uibProgressConfig', {
39020 .controller('UibProgressController', ['$scope', '$attrs', 'uibProgressConfig', function($scope, $attrs, progressConfig) {
39022 animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
39025 $scope.max = angular.isDefined($scope.max) ? $scope.max : progressConfig.max;
39027 this.addBar = function(bar, element, attrs) {
39029 element.css({'transition': 'none'});
39032 this.bars.push(bar);
39034 bar.max = $scope.max;
39035 bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar';
39037 bar.$watch('value', function(value) {
39038 bar.recalculatePercentage();
39041 bar.recalculatePercentage = function() {
39042 var totalPercentage = self.bars.reduce(function(total, bar) {
39043 bar.percent = +(100 * bar.value / bar.max).toFixed(2);
39044 return total + bar.percent;
39047 if (totalPercentage > 100) {
39048 bar.percent -= totalPercentage - 100;
39052 bar.$on('$destroy', function() {
39054 self.removeBar(bar);
39058 this.removeBar = function(bar) {
39059 this.bars.splice(this.bars.indexOf(bar), 1);
39060 this.bars.forEach(function (bar) {
39061 bar.recalculatePercentage();
39065 $scope.$watch('max', function(max) {
39066 self.bars.forEach(function(bar) {
39067 bar.max = $scope.max;
39068 bar.recalculatePercentage();
39073 .directive('uibProgress', function() {
39077 controller: 'UibProgressController',
39078 require: 'uibProgress',
39082 templateUrl: 'uib/template/progressbar/progress.html'
39086 .directive('uibBar', function() {
39090 require: '^uibProgress',
39095 templateUrl: 'uib/template/progressbar/bar.html',
39096 link: function(scope, element, attrs, progressCtrl) {
39097 progressCtrl.addBar(scope, element, attrs);
39102 .directive('uibProgressbar', function() {
39106 controller: 'UibProgressController',
39112 templateUrl: 'uib/template/progressbar/progressbar.html',
39113 link: function(scope, element, attrs, progressCtrl) {
39114 progressCtrl.addBar(scope, angular.element(element.children()[0]), {title: attrs.title});
39119 angular.module('ui.bootstrap.rating', [])
39121 .constant('uibRatingConfig', {
39125 titles : ['one', 'two', 'three', 'four', 'five']
39128 .controller('UibRatingController', ['$scope', '$attrs', 'uibRatingConfig', function($scope, $attrs, ratingConfig) {
39129 var ngModelCtrl = { $setViewValue: angular.noop };
39131 this.init = function(ngModelCtrl_) {
39132 ngModelCtrl = ngModelCtrl_;
39133 ngModelCtrl.$render = this.render;
39135 ngModelCtrl.$formatters.push(function(value) {
39136 if (angular.isNumber(value) && value << 0 !== value) {
39137 value = Math.round(value);
39143 this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
39144 this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
39145 var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles ;
39146 this.titles = angular.isArray(tmpTitles) && tmpTitles.length > 0 ?
39147 tmpTitles : ratingConfig.titles;
39149 var ratingStates = angular.isDefined($attrs.ratingStates) ?
39150 $scope.$parent.$eval($attrs.ratingStates) :
39151 new Array(angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max);
39152 $scope.range = this.buildTemplateObjects(ratingStates);
39155 this.buildTemplateObjects = function(states) {
39156 for (var i = 0, n = states.length; i < n; i++) {
39157 states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff, title: this.getTitle(i) }, states[i]);
39162 this.getTitle = function(index) {
39163 if (index >= this.titles.length) {
39167 return this.titles[index];
39170 $scope.rate = function(value) {
39171 if (!$scope.readonly && value >= 0 && value <= $scope.range.length) {
39172 ngModelCtrl.$setViewValue(ngModelCtrl.$viewValue === value ? 0 : value);
39173 ngModelCtrl.$render();
39177 $scope.enter = function(value) {
39178 if (!$scope.readonly) {
39179 $scope.value = value;
39181 $scope.onHover({value: value});
39184 $scope.reset = function() {
39185 $scope.value = ngModelCtrl.$viewValue;
39189 $scope.onKeydown = function(evt) {
39190 if (/(37|38|39|40)/.test(evt.which)) {
39191 evt.preventDefault();
39192 evt.stopPropagation();
39193 $scope.rate($scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1));
39197 this.render = function() {
39198 $scope.value = ngModelCtrl.$viewValue;
39202 .directive('uibRating', function() {
39204 require: ['uibRating', 'ngModel'],
39210 controller: 'UibRatingController',
39211 templateUrl: 'uib/template/rating/rating.html',
39213 link: function(scope, element, attrs, ctrls) {
39214 var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];
39215 ratingCtrl.init(ngModelCtrl);
39220 angular.module('ui.bootstrap.tabs', [])
39222 .controller('UibTabsetController', ['$scope', function ($scope) {
39224 tabs = ctrl.tabs = $scope.tabs = [];
39226 ctrl.select = function(selectedTab) {
39227 angular.forEach(tabs, function(tab) {
39228 if (tab.active && tab !== selectedTab) {
39229 tab.active = false;
39231 selectedTab.selectCalled = false;
39234 selectedTab.active = true;
39235 // only call select if it has not already been called
39236 if (!selectedTab.selectCalled) {
39237 selectedTab.onSelect();
39238 selectedTab.selectCalled = true;
39242 ctrl.addTab = function addTab(tab) {
39244 // we can't run the select function on the first tab
39245 // since that would select it twice
39246 if (tabs.length === 1 && tab.active !== false) {
39248 } else if (tab.active) {
39251 tab.active = false;
39255 ctrl.removeTab = function removeTab(tab) {
39256 var index = tabs.indexOf(tab);
39257 //Select a new tab if the tab to be removed is selected and not destroyed
39258 if (tab.active && tabs.length > 1 && !destroyed) {
39259 //If this is the last tab, select the previous tab. else, the next tab.
39260 var newActiveIndex = index === tabs.length - 1 ? index - 1 : index + 1;
39261 ctrl.select(tabs[newActiveIndex]);
39263 tabs.splice(index, 1);
39267 $scope.$on('$destroy', function() {
39272 .directive('uibTabset', function() {
39279 controller: 'UibTabsetController',
39280 templateUrl: 'uib/template/tabs/tabset.html',
39281 link: function(scope, element, attrs) {
39282 scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
39283 scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false;
39288 .directive('uibTab', ['$parse', function($parse) {
39290 require: '^uibTabset',
39292 templateUrl: 'uib/template/tabs/tab.html',
39297 onSelect: '&select', //This callback is called in contentHeadingTransclude
39298 //once it inserts the tab's content into the dom
39299 onDeselect: '&deselect'
39301 controller: function() {
39302 //Empty controller so other directives can require being 'under' a tab
39304 controllerAs: 'tab',
39305 link: function(scope, elm, attrs, tabsetCtrl, transclude) {
39306 scope.$watch('active', function(active) {
39308 tabsetCtrl.select(scope);
39312 scope.disabled = false;
39313 if (attrs.disable) {
39314 scope.$parent.$watch($parse(attrs.disable), function(value) {
39315 scope.disabled = !! value;
39319 scope.select = function() {
39320 if (!scope.disabled) {
39321 scope.active = true;
39325 tabsetCtrl.addTab(scope);
39326 scope.$on('$destroy', function() {
39327 tabsetCtrl.removeTab(scope);
39330 //We need to transclude later, once the content container is ready.
39331 //when this link happens, we're inside a tab heading.
39332 scope.$transcludeFn = transclude;
39337 .directive('uibTabHeadingTransclude', function() {
39340 require: '^uibTab',
39341 link: function(scope, elm) {
39342 scope.$watch('headingElement', function updateHeadingElement(heading) {
39345 elm.append(heading);
39352 .directive('uibTabContentTransclude', function() {
39355 require: '^uibTabset',
39356 link: function(scope, elm, attrs) {
39357 var tab = scope.$eval(attrs.uibTabContentTransclude);
39359 //Now our tab is ready to be transcluded: both the tab heading area
39360 //and the tab content area are loaded. Transclude 'em both.
39361 tab.$transcludeFn(tab.$parent, function(contents) {
39362 angular.forEach(contents, function(node) {
39363 if (isTabHeading(node)) {
39364 //Let tabHeadingTransclude know.
39365 tab.headingElement = node;
39374 function isTabHeading(node) {
39375 return node.tagName && (
39376 node.hasAttribute('uib-tab-heading') ||
39377 node.hasAttribute('data-uib-tab-heading') ||
39378 node.hasAttribute('x-uib-tab-heading') ||
39379 node.tagName.toLowerCase() === 'uib-tab-heading' ||
39380 node.tagName.toLowerCase() === 'data-uib-tab-heading' ||
39381 node.tagName.toLowerCase() === 'x-uib-tab-heading'
39386 angular.module('ui.bootstrap.timepicker', [])
39388 .constant('uibTimepickerConfig', {
39392 showMeridian: true,
39393 showSeconds: false,
39395 readonlyInput: false,
39401 .controller('UibTimepickerController', ['$scope', '$element', '$attrs', '$parse', '$log', '$locale', 'uibTimepickerConfig', function($scope, $element, $attrs, $parse, $log, $locale, timepickerConfig) {
39402 var selected = new Date(),
39403 ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
39404 meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS;
39406 $scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0;
39407 $element.removeAttr('tabindex');
39409 this.init = function(ngModelCtrl_, inputs) {
39410 ngModelCtrl = ngModelCtrl_;
39411 ngModelCtrl.$render = this.render;
39413 ngModelCtrl.$formatters.unshift(function(modelValue) {
39414 return modelValue ? new Date(modelValue) : null;
39417 var hoursInputEl = inputs.eq(0),
39418 minutesInputEl = inputs.eq(1),
39419 secondsInputEl = inputs.eq(2);
39421 var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel;
39424 this.setupMousewheelEvents(hoursInputEl, minutesInputEl, secondsInputEl);
39427 var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys;
39429 this.setupArrowkeyEvents(hoursInputEl, minutesInputEl, secondsInputEl);
39432 $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput;
39433 this.setupInputEvents(hoursInputEl, minutesInputEl, secondsInputEl);
39436 var hourStep = timepickerConfig.hourStep;
39437 if ($attrs.hourStep) {
39438 $scope.$parent.$watch($parse($attrs.hourStep), function(value) {
39439 hourStep = parseInt(value, 10);
39443 var minuteStep = timepickerConfig.minuteStep;
39444 if ($attrs.minuteStep) {
39445 $scope.$parent.$watch($parse($attrs.minuteStep), function(value) {
39446 minuteStep = parseInt(value, 10);
39451 $scope.$parent.$watch($parse($attrs.min), function(value) {
39452 var dt = new Date(value);
39453 min = isNaN(dt) ? undefined : dt;
39457 $scope.$parent.$watch($parse($attrs.max), function(value) {
39458 var dt = new Date(value);
39459 max = isNaN(dt) ? undefined : dt;
39462 var disabled = false;
39463 if ($attrs.ngDisabled) {
39464 $scope.$parent.$watch($parse($attrs.ngDisabled), function(value) {
39469 $scope.noIncrementHours = function() {
39470 var incrementedSelected = addMinutes(selected, hourStep * 60);
39471 return disabled || incrementedSelected > max ||
39472 incrementedSelected < selected && incrementedSelected < min;
39475 $scope.noDecrementHours = function() {
39476 var decrementedSelected = addMinutes(selected, -hourStep * 60);
39477 return disabled || decrementedSelected < min ||
39478 decrementedSelected > selected && decrementedSelected > max;
39481 $scope.noIncrementMinutes = function() {
39482 var incrementedSelected = addMinutes(selected, minuteStep);
39483 return disabled || incrementedSelected > max ||
39484 incrementedSelected < selected && incrementedSelected < min;
39487 $scope.noDecrementMinutes = function() {
39488 var decrementedSelected = addMinutes(selected, -minuteStep);
39489 return disabled || decrementedSelected < min ||
39490 decrementedSelected > selected && decrementedSelected > max;
39493 $scope.noIncrementSeconds = function() {
39494 var incrementedSelected = addSeconds(selected, secondStep);
39495 return disabled || incrementedSelected > max ||
39496 incrementedSelected < selected && incrementedSelected < min;
39499 $scope.noDecrementSeconds = function() {
39500 var decrementedSelected = addSeconds(selected, -secondStep);
39501 return disabled || decrementedSelected < min ||
39502 decrementedSelected > selected && decrementedSelected > max;
39505 $scope.noToggleMeridian = function() {
39506 if (selected.getHours() < 12) {
39507 return disabled || addMinutes(selected, 12 * 60) > max;
39510 return disabled || addMinutes(selected, -12 * 60) < min;
39513 var secondStep = timepickerConfig.secondStep;
39514 if ($attrs.secondStep) {
39515 $scope.$parent.$watch($parse($attrs.secondStep), function(value) {
39516 secondStep = parseInt(value, 10);
39520 $scope.showSeconds = timepickerConfig.showSeconds;
39521 if ($attrs.showSeconds) {
39522 $scope.$parent.$watch($parse($attrs.showSeconds), function(value) {
39523 $scope.showSeconds = !!value;
39528 $scope.showMeridian = timepickerConfig.showMeridian;
39529 if ($attrs.showMeridian) {
39530 $scope.$parent.$watch($parse($attrs.showMeridian), function(value) {
39531 $scope.showMeridian = !!value;
39533 if (ngModelCtrl.$error.time) {
39534 // Evaluate from template
39535 var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();
39536 if (angular.isDefined(hours) && angular.isDefined(minutes)) {
39537 selected.setHours(hours);
39546 // Get $scope.hours in 24H mode if valid
39547 function getHoursFromTemplate() {
39548 var hours = parseInt($scope.hours, 10);
39549 var valid = $scope.showMeridian ? hours > 0 && hours < 13 :
39550 hours >= 0 && hours < 24;
39555 if ($scope.showMeridian) {
39556 if (hours === 12) {
39559 if ($scope.meridian === meridians[1]) {
39560 hours = hours + 12;
39566 function getMinutesFromTemplate() {
39567 var minutes = parseInt($scope.minutes, 10);
39568 return minutes >= 0 && minutes < 60 ? minutes : undefined;
39571 function getSecondsFromTemplate() {
39572 var seconds = parseInt($scope.seconds, 10);
39573 return seconds >= 0 && seconds < 60 ? seconds : undefined;
39576 function pad(value) {
39577 if (value === null) {
39581 return angular.isDefined(value) && value.toString().length < 2 ?
39582 '0' + value : value.toString();
39585 // Respond on mousewheel spin
39586 this.setupMousewheelEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
39587 var isScrollingUp = function(e) {
39588 if (e.originalEvent) {
39589 e = e.originalEvent;
39591 //pick correct delta variable depending on event
39592 var delta = e.wheelDelta ? e.wheelDelta : -e.deltaY;
39593 return e.detail || delta > 0;
39596 hoursInputEl.bind('mousewheel wheel', function(e) {
39598 $scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours());
39600 e.preventDefault();
39603 minutesInputEl.bind('mousewheel wheel', function(e) {
39605 $scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes());
39607 e.preventDefault();
39610 secondsInputEl.bind('mousewheel wheel', function(e) {
39612 $scope.$apply(isScrollingUp(e) ? $scope.incrementSeconds() : $scope.decrementSeconds());
39614 e.preventDefault();
39618 // Respond on up/down arrowkeys
39619 this.setupArrowkeyEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
39620 hoursInputEl.bind('keydown', function(e) {
39622 if (e.which === 38) { // up
39623 e.preventDefault();
39624 $scope.incrementHours();
39626 } else if (e.which === 40) { // down
39627 e.preventDefault();
39628 $scope.decrementHours();
39634 minutesInputEl.bind('keydown', function(e) {
39636 if (e.which === 38) { // up
39637 e.preventDefault();
39638 $scope.incrementMinutes();
39640 } else if (e.which === 40) { // down
39641 e.preventDefault();
39642 $scope.decrementMinutes();
39648 secondsInputEl.bind('keydown', function(e) {
39650 if (e.which === 38) { // up
39651 e.preventDefault();
39652 $scope.incrementSeconds();
39654 } else if (e.which === 40) { // down
39655 e.preventDefault();
39656 $scope.decrementSeconds();
39663 this.setupInputEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
39664 if ($scope.readonlyInput) {
39665 $scope.updateHours = angular.noop;
39666 $scope.updateMinutes = angular.noop;
39667 $scope.updateSeconds = angular.noop;
39671 var invalidate = function(invalidHours, invalidMinutes, invalidSeconds) {
39672 ngModelCtrl.$setViewValue(null);
39673 ngModelCtrl.$setValidity('time', false);
39674 if (angular.isDefined(invalidHours)) {
39675 $scope.invalidHours = invalidHours;
39678 if (angular.isDefined(invalidMinutes)) {
39679 $scope.invalidMinutes = invalidMinutes;
39682 if (angular.isDefined(invalidSeconds)) {
39683 $scope.invalidSeconds = invalidSeconds;
39687 $scope.updateHours = function() {
39688 var hours = getHoursFromTemplate(),
39689 minutes = getMinutesFromTemplate();
39691 ngModelCtrl.$setDirty();
39693 if (angular.isDefined(hours) && angular.isDefined(minutes)) {
39694 selected.setHours(hours);
39695 selected.setMinutes(minutes);
39696 if (selected < min || selected > max) {
39706 hoursInputEl.bind('blur', function(e) {
39707 ngModelCtrl.$setTouched();
39708 if ($scope.hours === null || $scope.hours === '') {
39710 } else if (!$scope.invalidHours && $scope.hours < 10) {
39711 $scope.$apply(function() {
39712 $scope.hours = pad($scope.hours);
39717 $scope.updateMinutes = function() {
39718 var minutes = getMinutesFromTemplate(),
39719 hours = getHoursFromTemplate();
39721 ngModelCtrl.$setDirty();
39723 if (angular.isDefined(minutes) && angular.isDefined(hours)) {
39724 selected.setHours(hours);
39725 selected.setMinutes(minutes);
39726 if (selected < min || selected > max) {
39727 invalidate(undefined, true);
39732 invalidate(undefined, true);
39736 minutesInputEl.bind('blur', function(e) {
39737 ngModelCtrl.$setTouched();
39738 if ($scope.minutes === null) {
39739 invalidate(undefined, true);
39740 } else if (!$scope.invalidMinutes && $scope.minutes < 10) {
39741 $scope.$apply(function() {
39742 $scope.minutes = pad($scope.minutes);
39747 $scope.updateSeconds = function() {
39748 var seconds = getSecondsFromTemplate();
39750 ngModelCtrl.$setDirty();
39752 if (angular.isDefined(seconds)) {
39753 selected.setSeconds(seconds);
39756 invalidate(undefined, undefined, true);
39760 secondsInputEl.bind('blur', function(e) {
39761 if (!$scope.invalidSeconds && $scope.seconds < 10) {
39762 $scope.$apply( function() {
39763 $scope.seconds = pad($scope.seconds);
39770 this.render = function() {
39771 var date = ngModelCtrl.$viewValue;
39774 ngModelCtrl.$setValidity('time', false);
39775 $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.');
39781 if (selected < min || selected > max) {
39782 ngModelCtrl.$setValidity('time', false);
39783 $scope.invalidHours = true;
39784 $scope.invalidMinutes = true;
39792 // Call internally when we know that model is valid.
39793 function refresh(keyboardChange) {
39795 ngModelCtrl.$setViewValue(new Date(selected));
39796 updateTemplate(keyboardChange);
39799 function makeValid() {
39800 ngModelCtrl.$setValidity('time', true);
39801 $scope.invalidHours = false;
39802 $scope.invalidMinutes = false;
39803 $scope.invalidSeconds = false;
39806 function updateTemplate(keyboardChange) {
39807 if (!ngModelCtrl.$modelValue) {
39808 $scope.hours = null;
39809 $scope.minutes = null;
39810 $scope.seconds = null;
39811 $scope.meridian = meridians[0];
39813 var hours = selected.getHours(),
39814 minutes = selected.getMinutes(),
39815 seconds = selected.getSeconds();
39817 if ($scope.showMeridian) {
39818 hours = hours === 0 || hours === 12 ? 12 : hours % 12; // Convert 24 to 12 hour system
39821 $scope.hours = keyboardChange === 'h' ? hours : pad(hours);
39822 if (keyboardChange !== 'm') {
39823 $scope.minutes = pad(minutes);
39825 $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
39827 if (keyboardChange !== 's') {
39828 $scope.seconds = pad(seconds);
39830 $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
39834 function addSecondsToSelected(seconds) {
39835 selected = addSeconds(selected, seconds);
39839 function addMinutes(selected, minutes) {
39840 return addSeconds(selected, minutes*60);
39843 function addSeconds(date, seconds) {
39844 var dt = new Date(date.getTime() + seconds * 1000);
39845 var newDate = new Date(date);
39846 newDate.setHours(dt.getHours(), dt.getMinutes(), dt.getSeconds());
39850 $scope.showSpinners = angular.isDefined($attrs.showSpinners) ?
39851 $scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners;
39853 $scope.incrementHours = function() {
39854 if (!$scope.noIncrementHours()) {
39855 addSecondsToSelected(hourStep * 60 * 60);
39859 $scope.decrementHours = function() {
39860 if (!$scope.noDecrementHours()) {
39861 addSecondsToSelected(-hourStep * 60 * 60);
39865 $scope.incrementMinutes = function() {
39866 if (!$scope.noIncrementMinutes()) {
39867 addSecondsToSelected(minuteStep * 60);
39871 $scope.decrementMinutes = function() {
39872 if (!$scope.noDecrementMinutes()) {
39873 addSecondsToSelected(-minuteStep * 60);
39877 $scope.incrementSeconds = function() {
39878 if (!$scope.noIncrementSeconds()) {
39879 addSecondsToSelected(secondStep);
39883 $scope.decrementSeconds = function() {
39884 if (!$scope.noDecrementSeconds()) {
39885 addSecondsToSelected(-secondStep);
39889 $scope.toggleMeridian = function() {
39890 var minutes = getMinutesFromTemplate(),
39891 hours = getHoursFromTemplate();
39893 if (!$scope.noToggleMeridian()) {
39894 if (angular.isDefined(minutes) && angular.isDefined(hours)) {
39895 addSecondsToSelected(12 * 60 * (selected.getHours() < 12 ? 60 : -60));
39897 $scope.meridian = $scope.meridian === meridians[0] ? meridians[1] : meridians[0];
39902 $scope.blur = function() {
39903 ngModelCtrl.$setTouched();
39907 .directive('uibTimepicker', function() {
39909 require: ['uibTimepicker', '?^ngModel'],
39910 controller: 'UibTimepickerController',
39911 controllerAs: 'timepicker',
39914 templateUrl: function(element, attrs) {
39915 return attrs.templateUrl || 'uib/template/timepicker/timepicker.html';
39917 link: function(scope, element, attrs, ctrls) {
39918 var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
39921 timepickerCtrl.init(ngModelCtrl, element.find('input'));
39927 angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap.position'])
39930 * A helper service that can parse typeahead's syntax (string provided by users)
39931 * Extracted to a separate service for ease of unit testing
39933 .factory('uibTypeaheadParser', ['$parse', function($parse) {
39934 // 00000111000000000000022200000000000000003333333333333330000000000044000
39935 var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
39937 parse: function(input) {
39938 var match = input.match(TYPEAHEAD_REGEXP);
39941 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
39942 ' but got "' + input + '".');
39946 itemName: match[3],
39947 source: $parse(match[4]),
39948 viewMapper: $parse(match[2] || match[1]),
39949 modelMapper: $parse(match[1])
39955 .controller('UibTypeaheadController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$$debounce', '$uibPosition', 'uibTypeaheadParser',
39956 function(originalScope, element, attrs, $compile, $parse, $q, $timeout, $document, $window, $rootScope, $$debounce, $position, typeaheadParser) {
39957 var HOT_KEYS = [9, 13, 27, 38, 40];
39958 var eventDebounceTime = 200;
39959 var modelCtrl, ngModelOptions;
39960 //SUPPORTED ATTRIBUTES (OPTIONS)
39962 //minimal no of characters that needs to be entered before typeahead kicks-in
39963 var minLength = originalScope.$eval(attrs.typeaheadMinLength);
39964 if (!minLength && minLength !== 0) {
39968 //minimal wait time after last character typed before typeahead kicks-in
39969 var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
39971 //should it restrict model values to the ones selected from the popup only?
39972 var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
39973 originalScope.$watch(attrs.typeaheadEditable, function (newVal) {
39974 isEditable = newVal !== false;
39977 //binding to a variable that indicates if matches are being retrieved asynchronously
39978 var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
39980 //a callback executed when a match is selected
39981 var onSelectCallback = $parse(attrs.typeaheadOnSelect);
39983 //should it select highlighted popup value when losing focus?
39984 var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false;
39986 //binding to a variable that indicates if there were no results after the query is completed
39987 var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop;
39989 var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
39991 var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;
39993 var appendTo = attrs.typeaheadAppendTo ?
39994 originalScope.$eval(attrs.typeaheadAppendTo) : null;
39996 var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;
39998 //If input matches an item of the list exactly, select it automatically
39999 var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false;
40001 //binding to a variable that indicates if dropdown is open
40002 var isOpenSetter = $parse(attrs.typeaheadIsOpen).assign || angular.noop;
40004 var showHint = originalScope.$eval(attrs.typeaheadShowHint) || false;
40006 //INTERNAL VARIABLES
40008 //model setter executed upon match selection
40009 var parsedModel = $parse(attrs.ngModel);
40010 var invokeModelSetter = $parse(attrs.ngModel + '($$$p)');
40011 var $setModelValue = function(scope, newValue) {
40012 if (angular.isFunction(parsedModel(originalScope)) &&
40013 ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) {
40014 return invokeModelSetter(scope, {$$$p: newValue});
40017 return parsedModel.assign(scope, newValue);
40020 //expressions used by typeahead
40021 var parserResult = typeaheadParser.parse(attrs.uibTypeahead);
40025 //Used to avoid bug in iOS webview where iOS keyboard does not fire
40026 //mousedown & mouseup events
40030 //create a child scope for the typeahead directive so we are not polluting original scope
40031 //with typeahead-specific data (matches, query etc.)
40032 var scope = originalScope.$new();
40033 var offDestroy = originalScope.$on('$destroy', function() {
40036 scope.$on('$destroy', offDestroy);
40039 var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
40041 'aria-autocomplete': 'list',
40042 'aria-expanded': false,
40043 'aria-owns': popupId
40046 var inputsContainer, hintInputElem;
40047 //add read-only input to show hint
40049 inputsContainer = angular.element('<div></div>');
40050 inputsContainer.css('position', 'relative');
40051 element.after(inputsContainer);
40052 hintInputElem = element.clone();
40053 hintInputElem.attr('placeholder', '');
40054 hintInputElem.val('');
40055 hintInputElem.css({
40056 'position': 'absolute',
40059 'border-color': 'transparent',
40060 'box-shadow': 'none',
40062 'background': 'none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)',
40066 'position': 'relative',
40067 'vertical-align': 'top',
40068 'background-color': 'transparent'
40070 inputsContainer.append(hintInputElem);
40071 hintInputElem.after(element);
40074 //pop-up element used to display matches
40075 var popUpEl = angular.element('<div uib-typeahead-popup></div>');
40078 matches: 'matches',
40079 active: 'activeIdx',
40080 select: 'select(activeIdx, evt)',
40081 'move-in-progress': 'moveInProgress',
40083 position: 'position',
40084 'assign-is-open': 'assignIsOpen(isOpen)',
40085 debounce: 'debounceUpdate'
40087 //custom item template
40088 if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
40089 popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
40092 if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) {
40093 popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl);
40096 var resetHint = function() {
40098 hintInputElem.val('');
40102 var resetMatches = function() {
40103 scope.matches = [];
40104 scope.activeIdx = -1;
40105 element.attr('aria-expanded', false);
40109 var getMatchId = function(index) {
40110 return popupId + '-option-' + index;
40113 // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
40114 // This attribute is added or removed automatically when the `activeIdx` changes.
40115 scope.$watch('activeIdx', function(index) {
40117 element.removeAttr('aria-activedescendant');
40119 element.attr('aria-activedescendant', getMatchId(index));
40123 var inputIsExactMatch = function(inputValue, index) {
40124 if (scope.matches.length > index && inputValue) {
40125 return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase();
40131 var getMatchesAsync = function(inputValue, evt) {
40132 var locals = {$viewValue: inputValue};
40133 isLoadingSetter(originalScope, true);
40134 isNoResultsSetter(originalScope, false);
40135 $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
40136 //it might happen that several async queries were in progress if a user were typing fast
40137 //but we are interested only in responses that correspond to the current view value
40138 var onCurrentRequest = inputValue === modelCtrl.$viewValue;
40139 if (onCurrentRequest && hasFocus) {
40140 if (matches && matches.length > 0) {
40141 scope.activeIdx = focusFirst ? 0 : -1;
40142 isNoResultsSetter(originalScope, false);
40143 scope.matches.length = 0;
40146 for (var i = 0; i < matches.length; i++) {
40147 locals[parserResult.itemName] = matches[i];
40148 scope.matches.push({
40150 label: parserResult.viewMapper(scope, locals),
40155 scope.query = inputValue;
40156 //position pop-up with matches - we need to re-calculate its position each time we are opening a window
40157 //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
40158 //due to other elements being rendered
40159 recalculatePosition();
40161 element.attr('aria-expanded', true);
40163 //Select the single remaining option if user input matches
40164 if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) {
40165 if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
40166 $$debounce(function() {
40167 scope.select(0, evt);
40168 }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
40170 scope.select(0, evt);
40175 var firstLabel = scope.matches[0].label;
40176 if (inputValue.length > 0 && firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) {
40177 hintInputElem.val(inputValue + firstLabel.slice(inputValue.length));
40180 hintInputElem.val('');
40185 isNoResultsSetter(originalScope, true);
40188 if (onCurrentRequest) {
40189 isLoadingSetter(originalScope, false);
40193 isLoadingSetter(originalScope, false);
40194 isNoResultsSetter(originalScope, true);
40198 // bind events only if appendToBody params exist - performance feature
40199 if (appendToBody) {
40200 angular.element($window).on('resize', fireRecalculating);
40201 $document.find('body').on('scroll', fireRecalculating);
40204 // Declare the debounced function outside recalculating for
40205 // proper debouncing
40206 var debouncedRecalculate = $$debounce(function() {
40207 // if popup is visible
40208 if (scope.matches.length) {
40209 recalculatePosition();
40212 scope.moveInProgress = false;
40213 }, eventDebounceTime);
40215 // Default progress type
40216 scope.moveInProgress = false;
40218 function fireRecalculating() {
40219 if (!scope.moveInProgress) {
40220 scope.moveInProgress = true;
40224 debouncedRecalculate();
40227 // recalculate actual position and set new values to scope
40228 // after digest loop is popup in right position
40229 function recalculatePosition() {
40230 scope.position = appendToBody ? $position.offset(element) : $position.position(element);
40231 scope.position.top += element.prop('offsetHeight');
40234 //we need to propagate user's query so we can higlight matches
40235 scope.query = undefined;
40237 //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
40238 var timeoutPromise;
40240 var scheduleSearchWithTimeout = function(inputValue) {
40241 timeoutPromise = $timeout(function() {
40242 getMatchesAsync(inputValue);
40246 var cancelPreviousTimeout = function() {
40247 if (timeoutPromise) {
40248 $timeout.cancel(timeoutPromise);
40254 scope.assignIsOpen = function (isOpen) {
40255 isOpenSetter(originalScope, isOpen);
40258 scope.select = function(activeIdx, evt) {
40259 //called from within the $digest() cycle
40264 locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
40265 model = parserResult.modelMapper(originalScope, locals);
40266 $setModelValue(originalScope, model);
40267 modelCtrl.$setValidity('editable', true);
40268 modelCtrl.$setValidity('parse', true);
40270 onSelectCallback(originalScope, {
40273 $label: parserResult.viewMapper(originalScope, locals),
40279 //return focus to the input element if a match was selected via a mouse click event
40280 // use timeout to avoid $rootScope:inprog error
40281 if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) {
40282 $timeout(function() { element[0].focus(); }, 0, false);
40286 //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
40287 element.on('keydown', function(evt) {
40288 //typeahead is open and an "interesting" key was pressed
40289 if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
40293 // if there's nothing selected (i.e. focusFirst) and enter or tab is hit, clear the results
40294 if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13)) {
40300 evt.preventDefault();
40302 switch (evt.which) {
40305 scope.$apply(function () {
40306 if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
40307 $$debounce(function() {
40308 scope.select(scope.activeIdx, evt);
40309 }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
40311 scope.select(scope.activeIdx, evt);
40316 evt.stopPropagation();
40322 scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
40324 popUpEl.find('li')[scope.activeIdx].scrollIntoView(false);
40327 scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
40329 popUpEl.find('li')[scope.activeIdx].scrollIntoView(false);
40334 element.bind('focus', function (evt) {
40336 if (minLength === 0 && !modelCtrl.$viewValue) {
40337 $timeout(function() {
40338 getMatchesAsync(modelCtrl.$viewValue, evt);
40343 element.bind('blur', function(evt) {
40344 if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) {
40346 scope.$apply(function() {
40347 if (angular.isObject(scope.debounceUpdate) && angular.isNumber(scope.debounceUpdate.blur)) {
40348 $$debounce(function() {
40349 scope.select(scope.activeIdx, evt);
40350 }, scope.debounceUpdate.blur);
40352 scope.select(scope.activeIdx, evt);
40356 if (!isEditable && modelCtrl.$error.editable) {
40357 modelCtrl.$viewValue = '';
40364 // Keep reference to click handler to unbind it.
40365 var dismissClickHandler = function(evt) {
40367 // Firefox treats right click as a click on document
40368 if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) {
40370 if (!$rootScope.$$phase) {
40376 $document.on('click', dismissClickHandler);
40378 originalScope.$on('$destroy', function() {
40379 $document.off('click', dismissClickHandler);
40380 if (appendToBody || appendTo) {
40384 if (appendToBody) {
40385 angular.element($window).off('resize', fireRecalculating);
40386 $document.find('body').off('scroll', fireRecalculating);
40388 // Prevent jQuery cache memory leak
40392 inputsContainer.remove();
40396 var $popup = $compile(popUpEl)(scope);
40398 if (appendToBody) {
40399 $document.find('body').append($popup);
40400 } else if (appendTo) {
40401 angular.element(appendTo).eq(0).append($popup);
40403 element.after($popup);
40406 this.init = function(_modelCtrl, _ngModelOptions) {
40407 modelCtrl = _modelCtrl;
40408 ngModelOptions = _ngModelOptions;
40410 scope.debounceUpdate = modelCtrl.$options && $parse(modelCtrl.$options.debounce)(originalScope);
40412 //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
40413 //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
40414 modelCtrl.$parsers.unshift(function(inputValue) {
40417 if (minLength === 0 || inputValue && inputValue.length >= minLength) {
40418 if (waitTime > 0) {
40419 cancelPreviousTimeout();
40420 scheduleSearchWithTimeout(inputValue);
40422 getMatchesAsync(inputValue);
40425 isLoadingSetter(originalScope, false);
40426 cancelPreviousTimeout();
40435 // Reset in case user had typed something previously.
40436 modelCtrl.$setValidity('editable', true);
40440 modelCtrl.$setValidity('editable', false);
40444 modelCtrl.$formatters.push(function(modelValue) {
40445 var candidateViewValue, emptyViewValue;
40448 // The validity may be set to false via $parsers (see above) if
40449 // the model is restricted to selected values. If the model
40450 // is set manually it is considered to be valid.
40452 modelCtrl.$setValidity('editable', true);
40455 if (inputFormatter) {
40456 locals.$model = modelValue;
40457 return inputFormatter(originalScope, locals);
40460 //it might happen that we don't have enough info to properly render input value
40461 //we need to check for this situation and simply return model value if we can't apply custom formatting
40462 locals[parserResult.itemName] = modelValue;
40463 candidateViewValue = parserResult.viewMapper(originalScope, locals);
40464 locals[parserResult.itemName] = undefined;
40465 emptyViewValue = parserResult.viewMapper(originalScope, locals);
40467 return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;
40472 .directive('uibTypeahead', function() {
40474 controller: 'UibTypeaheadController',
40475 require: ['ngModel', '^?ngModelOptions', 'uibTypeahead'],
40476 link: function(originalScope, element, attrs, ctrls) {
40477 ctrls[2].init(ctrls[0], ctrls[1]);
40482 .directive('uibTypeaheadPopup', ['$$debounce', function($$debounce) {
40489 moveInProgress: '=',
40495 templateUrl: function(element, attrs) {
40496 return attrs.popupTemplateUrl || 'uib/template/typeahead/typeahead-popup.html';
40498 link: function(scope, element, attrs) {
40499 scope.templateUrl = attrs.templateUrl;
40501 scope.isOpen = function() {
40502 var isDropdownOpen = scope.matches.length > 0;
40503 scope.assignIsOpen({ isOpen: isDropdownOpen });
40504 return isDropdownOpen;
40507 scope.isActive = function(matchIdx) {
40508 return scope.active === matchIdx;
40511 scope.selectActive = function(matchIdx) {
40512 scope.active = matchIdx;
40515 scope.selectMatch = function(activeIdx, evt) {
40516 var debounce = scope.debounce();
40517 if (angular.isNumber(debounce) || angular.isObject(debounce)) {
40518 $$debounce(function() {
40519 scope.select({activeIdx: activeIdx, evt: evt});
40520 }, angular.isNumber(debounce) ? debounce : debounce['default']);
40522 scope.select({activeIdx: activeIdx, evt: evt});
40529 .directive('uibTypeaheadMatch', ['$templateRequest', '$compile', '$parse', function($templateRequest, $compile, $parse) {
40536 link: function(scope, element, attrs) {
40537 var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'uib/template/typeahead/typeahead-match.html';
40538 $templateRequest(tplUrl).then(function(tplContent) {
40539 var tplEl = angular.element(tplContent.trim());
40540 element.replaceWith(tplEl);
40541 $compile(tplEl)(scope);
40547 .filter('uibTypeaheadHighlight', ['$sce', '$injector', '$log', function($sce, $injector, $log) {
40548 var isSanitizePresent;
40549 isSanitizePresent = $injector.has('$sanitize');
40551 function escapeRegexp(queryToEscape) {
40552 // Regex: capture the whole query string and replace it with the string that will be used to match
40553 // the results, for example if the capture is "a" the result will be \a
40554 return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
40557 function containsHtml(matchItem) {
40558 return /<.*>/g.test(matchItem);
40561 return function(matchItem, query) {
40562 if (!isSanitizePresent && containsHtml(matchItem)) {
40563 $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger
40565 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
40566 if (!isSanitizePresent) {
40567 matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive
40573 angular.module("uib/template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) {
40574 $templateCache.put("uib/template/accordion/accordion-group.html",
40575 "<div class=\"panel\" ng-class=\"panelClass || 'panel-default'\">\n" +
40576 " <div class=\"panel-heading\" ng-keypress=\"toggleOpen($event)\">\n" +
40577 " <h4 class=\"panel-title\">\n" +
40578 " <div tabindex=\"0\" class=\"accordion-toggle\" ng-click=\"toggleOpen()\" uib-accordion-transclude=\"heading\"><span ng-class=\"{'text-muted': isDisabled}\">{{heading}}</span></div>\n" +
40581 " <div class=\"panel-collapse collapse\" uib-collapse=\"!isOpen\">\n" +
40582 " <div class=\"panel-body\" ng-transclude></div>\n" +
40588 angular.module("uib/template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) {
40589 $templateCache.put("uib/template/accordion/accordion.html",
40590 "<div class=\"panel-group\" ng-transclude></div>");
40593 angular.module("uib/template/alert/alert.html", []).run(["$templateCache", function($templateCache) {
40594 $templateCache.put("uib/template/alert/alert.html",
40595 "<div class=\"alert\" ng-class=\"['alert-' + (type || 'warning'), closeable ? 'alert-dismissible' : null]\" role=\"alert\">\n" +
40596 " <button ng-show=\"closeable\" type=\"button\" class=\"close\" ng-click=\"close({$event: $event})\">\n" +
40597 " <span aria-hidden=\"true\">×</span>\n" +
40598 " <span class=\"sr-only\">Close</span>\n" +
40600 " <div ng-transclude></div>\n" +
40605 angular.module("uib/template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) {
40606 $templateCache.put("uib/template/carousel/carousel.html",
40607 "<div ng-mouseenter=\"pause()\" ng-mouseleave=\"play()\" class=\"carousel\" ng-swipe-right=\"prev()\" ng-swipe-left=\"next()\">\n" +
40608 " <div class=\"carousel-inner\" ng-transclude></div>\n" +
40609 " <a role=\"button\" href class=\"left carousel-control\" ng-click=\"prev()\" ng-show=\"slides.length > 1\">\n" +
40610 " <span aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-left\"></span>\n" +
40611 " <span class=\"sr-only\">previous</span>\n" +
40613 " <a role=\"button\" href class=\"right carousel-control\" ng-click=\"next()\" ng-show=\"slides.length > 1\">\n" +
40614 " <span aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-right\"></span>\n" +
40615 " <span class=\"sr-only\">next</span>\n" +
40617 " <ol class=\"carousel-indicators\" ng-show=\"slides.length > 1\">\n" +
40618 " <li ng-repeat=\"slide in slides | orderBy:indexOfSlide track by $index\" ng-class=\"{ active: isActive(slide) }\" ng-click=\"select(slide)\">\n" +
40619 " <span class=\"sr-only\">slide {{ $index + 1 }} of {{ slides.length }}<span ng-if=\"isActive(slide)\">, currently active</span></span>\n" +
40625 angular.module("uib/template/carousel/slide.html", []).run(["$templateCache", function($templateCache) {
40626 $templateCache.put("uib/template/carousel/slide.html",
40627 "<div ng-class=\"{\n" +
40628 " 'active': active\n" +
40629 " }\" class=\"item text-center\" ng-transclude></div>\n" +
40633 angular.module("uib/template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) {
40634 $templateCache.put("uib/template/datepicker/datepicker.html",
40635 "<div class=\"uib-datepicker\" ng-switch=\"datepickerMode\" role=\"application\" ng-keydown=\"keydown($event)\">\n" +
40636 " <uib-daypicker ng-switch-when=\"day\" tabindex=\"0\"></uib-daypicker>\n" +
40637 " <uib-monthpicker ng-switch-when=\"month\" tabindex=\"0\"></uib-monthpicker>\n" +
40638 " <uib-yearpicker ng-switch-when=\"year\" tabindex=\"0\"></uib-yearpicker>\n" +
40642 angular.module("uib/template/datepicker/day.html", []).run(["$templateCache", function($templateCache) {
40643 $templateCache.put("uib/template/datepicker/day.html",
40644 "<table class=\"uib-daypicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
40647 " <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" +
40648 " <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" +
40649 " <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" +
40652 " <th ng-if=\"showWeeks\" class=\"text-center\"></th>\n" +
40653 " <th ng-repeat=\"label in ::labels track by $index\" class=\"text-center\"><small aria-label=\"{{::label.full}}\">{{::label.abbr}}</small></th>\n" +
40657 " <tr class=\"uib-weeks\" ng-repeat=\"row in rows track by $index\">\n" +
40658 " <td ng-if=\"showWeeks\" class=\"text-center h6\"><em>{{ weekNumbers[$index] }}</em></td>\n" +
40659 " <td ng-repeat=\"dt in row\" class=\"uib-day text-center\" role=\"gridcell\"\n" +
40660 " id=\"{{::dt.uid}}\"\n" +
40661 " ng-class=\"::dt.customClass\">\n" +
40662 " <button type=\"button\" style=\"min-width:100%;\" class=\"btn btn-default btn-sm\"\n" +
40663 " uib-is-class=\"\n" +
40664 " 'btn-info' for selectedDt,\n" +
40665 " 'active' for activeDt\n" +
40667 " ng-click=\"select(dt.date)\"\n" +
40668 " ng-disabled=\"::dt.disabled\"\n" +
40669 " tabindex=\"-1\"><span ng-class=\"::{'text-muted': dt.secondary, 'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
40677 angular.module("uib/template/datepicker/month.html", []).run(["$templateCache", function($templateCache) {
40678 $templateCache.put("uib/template/datepicker/month.html",
40679 "<table class=\"uib-monthpicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
40682 " <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" +
40683 " <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" +
40684 " <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" +
40688 " <tr class=\"uib-months\" ng-repeat=\"row in rows track by $index\">\n" +
40689 " <td ng-repeat=\"dt in row\" class=\"uib-month text-center\" role=\"gridcell\"\n" +
40690 " id=\"{{::dt.uid}}\"\n" +
40691 " ng-class=\"::dt.customClass\">\n" +
40692 " <button type=\"button\" style=\"min-width:100%;\" class=\"btn btn-default\"\n" +
40693 " uib-is-class=\"\n" +
40694 " 'btn-info' for selectedDt,\n" +
40695 " 'active' for activeDt\n" +
40697 " ng-click=\"select(dt.date)\"\n" +
40698 " ng-disabled=\"::dt.disabled\"\n" +
40699 " tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
40707 angular.module("uib/template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) {
40708 $templateCache.put("uib/template/datepicker/popup.html",
40709 "<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" +
40710 " <li ng-transclude></li>\n" +
40711 " <li ng-if=\"showButtonBar\" style=\"padding:10px 9px 2px\" class=\"uib-button-bar\">\n" +
40712 " <span class=\"btn-group pull-left\">\n" +
40713 " <button type=\"button\" class=\"btn btn-sm btn-info uib-datepicker-current\" ng-click=\"select('today')\" ng-disabled=\"isDisabled('today')\">{{ getText('current') }}</button>\n" +
40714 " <button type=\"button\" class=\"btn btn-sm btn-danger uib-clear\" ng-click=\"select(null)\">{{ getText('clear') }}</button>\n" +
40716 " <button type=\"button\" class=\"btn btn-sm btn-success pull-right uib-close\" ng-click=\"close()\">{{ getText('close') }}</button>\n" +
40722 angular.module("uib/template/datepicker/year.html", []).run(["$templateCache", function($templateCache) {
40723 $templateCache.put("uib/template/datepicker/year.html",
40724 "<table class=\"uib-yearpicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
40727 " <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" +
40728 " <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" +
40729 " <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" +
40733 " <tr class=\"uib-years\" ng-repeat=\"row in rows track by $index\">\n" +
40734 " <td ng-repeat=\"dt in row\" class=\"uib-year text-center\" role=\"gridcell\"\n" +
40735 " id=\"{{::dt.uid}}\"\n" +
40736 " ng-class=\"::dt.customClass\">\n" +
40737 " <button type=\"button\" style=\"min-width:100%;\" class=\"btn btn-default\"\n" +
40738 " uib-is-class=\"\n" +
40739 " 'btn-info' for selectedDt,\n" +
40740 " 'active' for activeDt\n" +
40742 " ng-click=\"select(dt.date)\"\n" +
40743 " ng-disabled=\"::dt.disabled\"\n" +
40744 " tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
40752 angular.module("uib/template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) {
40753 $templateCache.put("uib/template/modal/backdrop.html",
40754 "<div class=\"modal-backdrop\"\n" +
40755 " uib-modal-animation-class=\"fade\"\n" +
40756 " modal-in-class=\"in\"\n" +
40757 " ng-style=\"{'z-index': 1040 + (index && 1 || 0) + index*10}\"\n" +
40762 angular.module("uib/template/modal/window.html", []).run(["$templateCache", function($templateCache) {
40763 $templateCache.put("uib/template/modal/window.html",
40764 "<div modal-render=\"{{$isRendered}}\" tabindex=\"-1\" role=\"dialog\" class=\"modal\"\n" +
40765 " uib-modal-animation-class=\"fade\"\n" +
40766 " modal-in-class=\"in\"\n" +
40767 " ng-style=\"{'z-index': 1050 + index*10, display: 'block'}\">\n" +
40768 " <div class=\"modal-dialog\" ng-class=\"size ? 'modal-' + size : ''\"><div class=\"modal-content\" uib-modal-transclude></div></div>\n" +
40773 angular.module("uib/template/pager/pager.html", []).run(["$templateCache", function($templateCache) {
40774 $templateCache.put("uib/template/pager/pager.html",
40775 "<ul class=\"pager\">\n" +
40776 " <li ng-class=\"{disabled: noPrevious()||ngDisabled, previous: align}\"><a href ng-click=\"selectPage(page - 1, $event)\">{{::getText('previous')}}</a></li>\n" +
40777 " <li ng-class=\"{disabled: noNext()||ngDisabled, next: align}\"><a href ng-click=\"selectPage(page + 1, $event)\">{{::getText('next')}}</a></li>\n" +
40782 angular.module("uib/template/pagination/pager.html", []).run(["$templateCache", function($templateCache) {
40783 $templateCache.put("uib/template/pagination/pager.html",
40784 "<ul class=\"pager\">\n" +
40785 " <li ng-class=\"{disabled: noPrevious()||ngDisabled, previous: align}\"><a href ng-click=\"selectPage(page - 1, $event)\">{{::getText('previous')}}</a></li>\n" +
40786 " <li ng-class=\"{disabled: noNext()||ngDisabled, next: align}\"><a href ng-click=\"selectPage(page + 1, $event)\">{{::getText('next')}}</a></li>\n" +
40791 angular.module("uib/template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) {
40792 $templateCache.put("uib/template/pagination/pagination.html",
40793 "<ul class=\"pagination\">\n" +
40794 " <li ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noPrevious()||ngDisabled}\" class=\"pagination-first\"><a href ng-click=\"selectPage(1, $event)\">{{::getText('first')}}</a></li>\n" +
40795 " <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" +
40796 " <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" +
40797 " <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" +
40798 " <li ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noNext()||ngDisabled}\" class=\"pagination-last\"><a href ng-click=\"selectPage(totalPages, $event)\">{{::getText('last')}}</a></li>\n" +
40803 angular.module("uib/template/tooltip/tooltip-html-popup.html", []).run(["$templateCache", function($templateCache) {
40804 $templateCache.put("uib/template/tooltip/tooltip-html-popup.html",
40805 "<div class=\"tooltip\"\n" +
40806 " tooltip-animation-class=\"fade\"\n" +
40807 " uib-tooltip-classes\n" +
40808 " ng-class=\"{ in: isOpen() }\">\n" +
40809 " <div class=\"tooltip-arrow\"></div>\n" +
40810 " <div class=\"tooltip-inner\" ng-bind-html=\"contentExp()\"></div>\n" +
40815 angular.module("uib/template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) {
40816 $templateCache.put("uib/template/tooltip/tooltip-popup.html",
40817 "<div class=\"tooltip\"\n" +
40818 " tooltip-animation-class=\"fade\"\n" +
40819 " uib-tooltip-classes\n" +
40820 " ng-class=\"{ in: isOpen() }\">\n" +
40821 " <div class=\"tooltip-arrow\"></div>\n" +
40822 " <div class=\"tooltip-inner\" ng-bind=\"content\"></div>\n" +
40827 angular.module("uib/template/tooltip/tooltip-template-popup.html", []).run(["$templateCache", function($templateCache) {
40828 $templateCache.put("uib/template/tooltip/tooltip-template-popup.html",
40829 "<div class=\"tooltip\"\n" +
40830 " tooltip-animation-class=\"fade\"\n" +
40831 " uib-tooltip-classes\n" +
40832 " ng-class=\"{ in: isOpen() }\">\n" +
40833 " <div class=\"tooltip-arrow\"></div>\n" +
40834 " <div class=\"tooltip-inner\"\n" +
40835 " uib-tooltip-template-transclude=\"contentExp()\"\n" +
40836 " tooltip-template-transclude-scope=\"originScope()\"></div>\n" +
40841 angular.module("uib/template/popover/popover-html.html", []).run(["$templateCache", function($templateCache) {
40842 $templateCache.put("uib/template/popover/popover-html.html",
40843 "<div class=\"popover\"\n" +
40844 " tooltip-animation-class=\"fade\"\n" +
40845 " uib-tooltip-classes\n" +
40846 " ng-class=\"{ in: isOpen() }\">\n" +
40847 " <div class=\"arrow\"></div>\n" +
40849 " <div class=\"popover-inner\">\n" +
40850 " <h3 class=\"popover-title\" ng-bind=\"title\" ng-if=\"title\"></h3>\n" +
40851 " <div class=\"popover-content\" ng-bind-html=\"contentExp()\"></div>\n" +
40857 angular.module("uib/template/popover/popover-template.html", []).run(["$templateCache", function($templateCache) {
40858 $templateCache.put("uib/template/popover/popover-template.html",
40859 "<div class=\"popover\"\n" +
40860 " tooltip-animation-class=\"fade\"\n" +
40861 " uib-tooltip-classes\n" +
40862 " ng-class=\"{ in: isOpen() }\">\n" +
40863 " <div class=\"arrow\"></div>\n" +
40865 " <div class=\"popover-inner\">\n" +
40866 " <h3 class=\"popover-title\" ng-bind=\"title\" ng-if=\"title\"></h3>\n" +
40867 " <div class=\"popover-content\"\n" +
40868 " uib-tooltip-template-transclude=\"contentExp()\"\n" +
40869 " tooltip-template-transclude-scope=\"originScope()\"></div>\n" +
40875 angular.module("uib/template/popover/popover.html", []).run(["$templateCache", function($templateCache) {
40876 $templateCache.put("uib/template/popover/popover.html",
40877 "<div class=\"popover\"\n" +
40878 " tooltip-animation-class=\"fade\"\n" +
40879 " uib-tooltip-classes\n" +
40880 " ng-class=\"{ in: isOpen() }\">\n" +
40881 " <div class=\"arrow\"></div>\n" +
40883 " <div class=\"popover-inner\">\n" +
40884 " <h3 class=\"popover-title\" ng-bind=\"title\" ng-if=\"title\"></h3>\n" +
40885 " <div class=\"popover-content\" ng-bind=\"content\"></div>\n" +
40891 angular.module("uib/template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) {
40892 $templateCache.put("uib/template/progressbar/bar.html",
40893 "<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" +
40897 angular.module("uib/template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) {
40898 $templateCache.put("uib/template/progressbar/progress.html",
40899 "<div class=\"progress\" ng-transclude aria-labelledby=\"{{::title}}\"></div>");
40902 angular.module("uib/template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) {
40903 $templateCache.put("uib/template/progressbar/progressbar.html",
40904 "<div class=\"progress\">\n" +
40905 " <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" +
40910 angular.module("uib/template/rating/rating.html", []).run(["$templateCache", function($templateCache) {
40911 $templateCache.put("uib/template/rating/rating.html",
40912 "<span ng-mouseleave=\"reset()\" ng-keydown=\"onKeydown($event)\" tabindex=\"0\" role=\"slider\" aria-valuemin=\"0\" aria-valuemax=\"{{range.length}}\" aria-valuenow=\"{{value}}\">\n" +
40913 " <span ng-repeat-start=\"r in range track by $index\" class=\"sr-only\">({{ $index < value ? '*' : ' ' }})</span>\n" +
40914 " <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" +
40919 angular.module("uib/template/tabs/tab.html", []).run(["$templateCache", function($templateCache) {
40920 $templateCache.put("uib/template/tabs/tab.html",
40921 "<li ng-class=\"{active: active, disabled: disabled}\" class=\"uib-tab\">\n" +
40922 " <div ng-click=\"select()\" uib-tab-heading-transclude>{{heading}}</div>\n" +
40927 angular.module("uib/template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) {
40928 $templateCache.put("uib/template/tabs/tabset.html",
40930 " <ul class=\"nav nav-{{type || 'tabs'}}\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" +
40931 " <div class=\"tab-content\">\n" +
40932 " <div class=\"tab-pane\" \n" +
40933 " ng-repeat=\"tab in tabs\" \n" +
40934 " ng-class=\"{active: tab.active}\"\n" +
40935 " uib-tab-content-transclude=\"tab\">\n" +
40942 angular.module("uib/template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) {
40943 $templateCache.put("uib/template/timepicker/timepicker.html",
40944 "<table class=\"uib-timepicker\">\n" +
40946 " <tr class=\"text-center\" ng-show=\"::showSpinners\">\n" +
40947 " <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" +
40948 " <td> </td>\n" +
40949 " <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" +
40950 " <td ng-show=\"showSeconds\"> </td>\n" +
40951 " <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" +
40952 " <td ng-show=\"showMeridian\"></td>\n" +
40955 " <td class=\"form-group uib-time hours\" ng-class=\"{'has-error': invalidHours}\">\n" +
40956 " <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" +
40958 " <td class=\"uib-separator\">:</td>\n" +
40959 " <td class=\"form-group uib-time minutes\" ng-class=\"{'has-error': invalidMinutes}\">\n" +
40960 " <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" +
40962 " <td ng-show=\"showSeconds\" class=\"uib-separator\">:</td>\n" +
40963 " <td class=\"form-group uib-time seconds\" ng-class=\"{'has-error': invalidSeconds}\" ng-show=\"showSeconds\">\n" +
40964 " <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" +
40966 " <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" +
40968 " <tr class=\"text-center\" ng-show=\"::showSpinners\">\n" +
40969 " <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" +
40970 " <td> </td>\n" +
40971 " <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" +
40972 " <td ng-show=\"showSeconds\"> </td>\n" +
40973 " <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" +
40974 " <td ng-show=\"showMeridian\"></td>\n" +
40981 angular.module("uib/template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) {
40982 $templateCache.put("uib/template/typeahead/typeahead-match.html",
40983 "<a href tabindex=\"-1\" ng-bind-html=\"match.label | uibTypeaheadHighlight:query\"></a>\n" +
40987 angular.module("uib/template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) {
40988 $templateCache.put("uib/template/typeahead/typeahead-popup.html",
40989 "<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" +
40990 " <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" +
40991 " <div uib-typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>\n" +
40996 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>'); })
41000 /***/ function(module, exports) {
41005 (function (declares) {
41006 var CommandInfo = (function () {
41007 function CommandInfo(name) {
41010 return CommandInfo;
41012 declares.CommandInfo = CommandInfo;
41013 })(declares = app.declares || (app.declares = {}));
41014 })(app || (app = {}));
41018 (function (services) {
41019 var APIEndPoint = (function () {
41020 function APIEndPoint($resource, $http) {
41021 this.$resource = $resource;
41022 this.$http = $http;
41024 APIEndPoint.prototype.resource = function (endPoint, data) {
41025 var customAction = {
41031 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
41033 return this.$resource(endPoint, {}, { execute: execute });
41035 APIEndPoint.prototype.getOptionControlFile = function (command) {
41036 var endPoint = '/api/v1/optionControlFile/' + command;
41037 return this.resource(endPoint, {}).get();
41039 APIEndPoint.prototype.getFiles = function (fileId) {
41040 var endPoint = '/api/v1/workspace';
41042 endPoint += '/' + fileId;
41044 return this.resource(endPoint, {}).get();
41046 APIEndPoint.prototype.getDirectories = function () {
41047 var endPoint = '/api/v1/all/workspace/directory';
41048 return this.resource(endPoint, {}).get();
41050 APIEndPoint.prototype.execute = function (data) {
41051 var endPoint = '/api/v1/execution';
41052 var fd = new FormData();
41053 fd.append('data', data);
41054 return this.$http.post(endPoint, fd, {
41055 headers: { 'Content-Type': undefined },
41056 transformRequest: angular.identity
41059 return APIEndPoint;
41061 services.APIEndPoint = APIEndPoint;
41062 })(services = app.services || (app.services = {}));
41063 })(app || (app = {}));
41067 (function (services) {
41068 var MyModal = (function () {
41069 function MyModal($uibModal) {
41070 this.$uibModal = $uibModal;
41071 this.modalOption = {
41078 MyModal.prototype.open = function (modalName) {
41079 if (modalName === 'SelectCommand') {
41080 this.modalOption.templateUrl = 'templates/select-command.html';
41081 this.modalOption.size = 'lg';
41083 return this.$uibModal.open(this.modalOption);
41085 MyModal.prototype.selectCommand = function () {
41086 this.modalOption.templateUrl = 'templates/select-command.html';
41087 this.modalOption.controller = 'selectCommandController';
41088 this.modalOption.size = 'lg';
41089 return this.$uibModal.open(this.modalOption);
41093 services.MyModal = MyModal;
41094 })(services = app.services || (app.services = {}));
41095 })(app || (app = {}));
41099 (function (directives) {
41100 var Command = (function () {
41101 function Command() {
41102 this.restrict = 'E';
41103 this.replace = true;
41105 this.controller = 'commandController';
41106 this.controllerAs = 'ctrl';
41107 this.bindToController = {
41113 this.templateUrl = 'templates/command.html';
41115 Command.Factory = function () {
41116 var directive = function () {
41117 return new Command();
41119 directive.$inject = [];
41124 directives.Command = Command;
41125 var CommandController = (function () {
41126 function CommandController(APIEndPoint, $scope) {
41127 this.APIEndPoint = APIEndPoint;
41128 this.$scope = $scope;
41129 var controller = this;
41131 .getOptionControlFile('mrcImageNoiseAdd')
41133 .then(function (result) {
41134 controller.options = result.info;
41139 .then(function (result) {
41140 controller.dirs = result.info;
41142 this.heading = "[" + this.index + "]: dcdFilePring";
41143 this.isOpen = true;
41144 this.$scope.$on('close', function () {
41145 controller.isOpen = false;
41148 CommandController.prototype.submit = function () {
41150 angular.forEach(this.options, function (option) {
41152 name: option.option,
41155 angular.forEach(option.arg, function (arg) {
41157 if (typeof arg.input === 'object') {
41158 obj.arguments.push(arg.input.name);
41161 obj.arguments.push(arg.input);
41165 if (obj.arguments.length > 0) {
41170 command: this.name,
41171 workspace: this.workspace.fileId,
41175 .execute(JSON.stringify(execObj))
41176 .then(function (result) {
41177 console.log(result);
41180 CommandController.prototype.removeMySelf = function (index) {
41181 this.remove()(index, this.list);
41183 CommandController.prototype.reloadFiles = function () {
41185 var fileId = this.workspace.fileId;
41189 .then(function (result) {
41190 var status = result.status;
41191 if (status === 'success') {
41192 _this.files = result.info;
41195 console.log(result.message);
41199 CommandController.prototype.debug = function () {
41200 console.log(this.files);
41201 console.log(this.files);
41202 console.log(this.workspace);
41204 CommandController.$inject = ['APIEndPoint', '$scope'];
41205 return CommandController;
41207 directives.CommandController = CommandController;
41208 })(directives = app.directives || (app.directives = {}));
41209 })(app || (app = {}));
41213 (function (directives) {
41214 var HeaderMenu = (function () {
41215 function HeaderMenu() {
41216 this.restrict = 'E';
41217 this.replace = true;
41218 this.templateUrl = 'templates/header-menu.html';
41220 HeaderMenu.Factory = function () {
41221 var directive = function () {
41222 return new HeaderMenu();
41228 directives.HeaderMenu = HeaderMenu;
41229 })(directives = app.directives || (app.directives = {}));
41230 })(app || (app = {}));
41234 (function (directives) {
41235 var Option = (function () {
41236 function Option() {
41237 this.restrict = 'E';
41238 this.replace = true;
41239 this.controller = 'optionController';
41240 this.bindToController = {
41245 this.templateUrl = 'templates/option.html';
41246 this.controllerAs = 'ctrl';
41248 Option.Factory = function () {
41249 var directive = function () {
41250 return new Option();
41252 directive.$inject = [];
41257 directives.Option = Option;
41258 var OptionController = (function () {
41259 function OptionController() {
41260 var controller = this;
41261 angular.forEach(controller.info.arg, function (arg) {
41262 if (arg.initialValue) {
41263 if (arg.formType === 'number') {
41264 arg.input = parseInt(arg.initialValue);
41267 arg.input = arg.initialValue;
41272 OptionController.$inject = [];
41273 return OptionController;
41275 directives.OptionController = OptionController;
41276 })(directives = app.directives || (app.directives = {}));
41277 })(app || (app = {}));
41281 (function (directives) {
41282 var Directory = (function () {
41283 function Directory() {
41284 this.restrict = 'E';
41285 this.replace = true;
41286 this.controller = 'directoryController';
41287 this.controllerAs = 'ctrl';
41288 this.bindToController = {
41294 this.templateUrl = 'templates/directory.html';
41296 Directory.Factory = function () {
41297 var directive = function () {
41298 return new Directory();
41304 directives.Directory = Directory;
41305 var DirectoryController = (function () {
41306 function DirectoryController(APIEndPoint, $scope) {
41307 this.APIEndPoint = APIEndPoint;
41308 this.$scope = $scope;
41309 var controller = this;
41311 .getFiles(this.info.fileId)
41313 .then(function (result) {
41314 if (result.status === 'success') {
41315 controller.files = result.info;
41316 angular.forEach(result.info, function (file) {
41317 if (file.fileType === '0') {
41319 if (controller.info.path === '/') {
41320 o.path = '/' + file.name;
41323 o.path = controller.info.path + '/' + file.name;
41325 controller.add()(o, controller.list);
41332 DirectoryController.$inject = ['APIEndPoint', '$scope'];
41333 return DirectoryController;
41335 directives.DirectoryController = DirectoryController;
41336 })(directives = app.directives || (app.directives = {}));
41337 })(app || (app = {}));
41341 (function (controllers) {
41342 var Execution = (function () {
41343 function Execution(MyModal, $scope) {
41344 this.MyModal = MyModal;
41345 this.$scope = $scope;
41346 this.commandInfoList = [];
41349 Execution.prototype.add = function () {
41350 this.$scope.$broadcast('close');
41351 this.commandInfoList.push(new app.declares.CommandInfo('mrcImageNoiseAdd'));
41353 Execution.prototype.open = function () {
41354 var result = this.MyModal.open('SelectCommand');
41355 console.log(result);
41357 Execution.prototype.remove = function (index, list) {
41358 list.splice(index, 1);
41360 Execution.prototype.close = function () {
41361 console.log("close");
41363 Execution.$inject = ['MyModal', '$scope'];
41366 controllers.Execution = Execution;
41367 })(controllers = app.controllers || (app.controllers = {}));
41368 })(app || (app = {}));
41372 (function (controllers) {
41373 var Workspace = (function () {
41374 function Workspace($scope, APIEndPoint) {
41375 this.$scope = $scope;
41376 this.APIEndPoint = APIEndPoint;
41377 this.directoryList = [];
41378 var controller = this;
41379 var directoryList = this.directoryList;
41381 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
41389 directoryList.push(o);
41391 Workspace.prototype.addDirectory = function (info, directoryList) {
41392 directoryList.push(info);
41394 Workspace.$inject = ['$scope', 'APIEndPoint'];
41397 controllers.Workspace = Workspace;
41398 })(controllers = app.controllers || (app.controllers = {}));
41399 })(app || (app = {}));
41403 (function (controllers) {
41404 var History = (function () {
41405 function History($scope) {
41406 this.page = "History";
41408 History.$inject = ['$scope'];
41411 controllers.History = History;
41412 })(controllers = app.controllers || (app.controllers = {}));
41413 })(app || (app = {}));
41417 (function (controllers) {
41418 var SelectCommand = (function () {
41419 function SelectCommand($scope, APIEndPOint) {
41420 this.APIEndPOint = APIEndPOint;
41421 this.page = "History";
41423 SelectCommand.$inject = ['$scope', 'APIEndPoint'];
41424 return SelectCommand;
41426 controllers.SelectCommand = SelectCommand;
41427 })(controllers = app.controllers || (app.controllers = {}));
41428 })(app || (app = {}));
41432 var appName = 'zephyr';
41433 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
41434 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
41435 $urlRouterProvider.otherwise('/execution');
41436 $locationProvider.html5Mode({
41441 .state('execution', {
41443 templateUrl: 'templates/execution.html',
41444 controller: 'executionController',
41447 .state('workspace', {
41449 templateUrl: 'templates/workspace.html',
41450 controller: 'workspaceController',
41453 .state('history', {
41455 templateUrl: 'templates/history.html',
41456 controller: 'historyController',
41460 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
41461 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
41462 app.zephyr.service('MyModal', app.services.MyModal);
41463 app.zephyr.controller('executionController', app.controllers.Execution);
41464 app.zephyr.controller('workspaceController', app.controllers.Workspace);
41465 app.zephyr.controller('historyController', app.controllers.History);
41466 app.zephyr.controller('commandController', app.directives.CommandController);
41467 app.zephyr.controller('optionController', app.directives.OptionController);
41468 app.zephyr.controller('directoryController', app.directives.DirectoryController);
41469 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
41470 app.zephyr.directive('command', app.directives.Command.Factory());
41471 app.zephyr.directive('option', app.directives.Option.Factory());
41472 app.zephyr.directive('directory', app.directives.Directory.Factory());
41473 })(app || (app = {}));
41478 /***/ function(module, exports) {
41483 (function (declares) {
41484 var CommandInfo = (function () {
41485 function CommandInfo(name) {
41488 return CommandInfo;
41490 declares.CommandInfo = CommandInfo;
41491 })(declares = app.declares || (app.declares = {}));
41492 })(app || (app = {}));
41496 (function (services) {
41497 var APIEndPoint = (function () {
41498 function APIEndPoint($resource, $http) {
41499 this.$resource = $resource;
41500 this.$http = $http;
41502 APIEndPoint.prototype.resource = function (endPoint, data) {
41503 var customAction = {
41509 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
41511 return this.$resource(endPoint, {}, { execute: execute });
41513 APIEndPoint.prototype.getOptionControlFile = function (command) {
41514 var endPoint = '/api/v1/optionControlFile/' + command;
41515 return this.resource(endPoint, {}).get();
41517 APIEndPoint.prototype.getFiles = function (fileId) {
41518 var endPoint = '/api/v1/workspace';
41520 endPoint += '/' + fileId;
41522 return this.resource(endPoint, {}).get();
41524 APIEndPoint.prototype.getDirectories = function () {
41525 var endPoint = '/api/v1/all/workspace/directory';
41526 return this.resource(endPoint, {}).get();
41528 APIEndPoint.prototype.execute = function (data) {
41529 var endPoint = '/api/v1/execution';
41530 var fd = new FormData();
41531 fd.append('data', data);
41532 return this.$http.post(endPoint, fd, {
41533 headers: { 'Content-Type': undefined },
41534 transformRequest: angular.identity
41537 return APIEndPoint;
41539 services.APIEndPoint = APIEndPoint;
41540 })(services = app.services || (app.services = {}));
41541 })(app || (app = {}));
41545 (function (services) {
41546 var MyModal = (function () {
41547 function MyModal($uibModal) {
41548 this.$uibModal = $uibModal;
41549 this.modalOption = {
41556 MyModal.prototype.open = function (modalName) {
41557 if (modalName === 'SelectCommand') {
41558 this.modalOption.templateUrl = 'templates/select-command.html';
41559 this.modalOption.size = 'lg';
41561 return this.$uibModal.open(this.modalOption);
41563 MyModal.prototype.selectCommand = function () {
41564 this.modalOption.templateUrl = 'templates/select-command.html';
41565 this.modalOption.controller = 'selectCommandController';
41566 this.modalOption.size = 'lg';
41567 return this.$uibModal.open(this.modalOption);
41571 services.MyModal = MyModal;
41572 })(services = app.services || (app.services = {}));
41573 })(app || (app = {}));
41577 (function (directives) {
41578 var Command = (function () {
41579 function Command() {
41580 this.restrict = 'E';
41581 this.replace = true;
41583 this.controller = 'commandController';
41584 this.controllerAs = 'ctrl';
41585 this.bindToController = {
41591 this.templateUrl = 'templates/command.html';
41593 Command.Factory = function () {
41594 var directive = function () {
41595 return new Command();
41597 directive.$inject = [];
41602 directives.Command = Command;
41603 var CommandController = (function () {
41604 function CommandController(APIEndPoint, $scope) {
41605 this.APIEndPoint = APIEndPoint;
41606 this.$scope = $scope;
41607 var controller = this;
41609 .getOptionControlFile('mrcImageNoiseAdd')
41611 .then(function (result) {
41612 controller.options = result.info;
41617 .then(function (result) {
41618 controller.dirs = result.info;
41620 this.heading = "[" + this.index + "]: dcdFilePring";
41621 this.isOpen = true;
41622 this.$scope.$on('close', function () {
41623 controller.isOpen = false;
41626 CommandController.prototype.submit = function () {
41628 angular.forEach(this.options, function (option) {
41630 name: option.option,
41633 angular.forEach(option.arg, function (arg) {
41635 if (typeof arg.input === 'object') {
41636 obj.arguments.push(arg.input.name);
41639 obj.arguments.push(arg.input);
41643 if (obj.arguments.length > 0) {
41648 command: this.name,
41649 workspace: this.workspace.fileId,
41653 .execute(JSON.stringify(execObj))
41654 .then(function (result) {
41655 console.log(result);
41658 CommandController.prototype.removeMySelf = function (index) {
41659 this.remove()(index, this.list);
41661 CommandController.prototype.reloadFiles = function () {
41663 var fileId = this.workspace.fileId;
41667 .then(function (result) {
41668 var status = result.status;
41669 if (status === 'success') {
41670 _this.files = result.info;
41673 console.log(result.message);
41677 CommandController.prototype.debug = function () {
41678 console.log(this.files);
41679 console.log(this.files);
41680 console.log(this.workspace);
41682 CommandController.$inject = ['APIEndPoint', '$scope'];
41683 return CommandController;
41685 directives.CommandController = CommandController;
41686 })(directives = app.directives || (app.directives = {}));
41687 })(app || (app = {}));
41691 (function (directives) {
41692 var HeaderMenu = (function () {
41693 function HeaderMenu() {
41694 this.restrict = 'E';
41695 this.replace = true;
41696 this.templateUrl = 'templates/header-menu.html';
41698 HeaderMenu.Factory = function () {
41699 var directive = function () {
41700 return new HeaderMenu();
41706 directives.HeaderMenu = HeaderMenu;
41707 })(directives = app.directives || (app.directives = {}));
41708 })(app || (app = {}));
41712 (function (directives) {
41713 var Option = (function () {
41714 function Option() {
41715 this.restrict = 'E';
41716 this.replace = true;
41717 this.controller = 'optionController';
41718 this.bindToController = {
41723 this.templateUrl = 'templates/option.html';
41724 this.controllerAs = 'ctrl';
41726 Option.Factory = function () {
41727 var directive = function () {
41728 return new Option();
41730 directive.$inject = [];
41735 directives.Option = Option;
41736 var OptionController = (function () {
41737 function OptionController() {
41738 var controller = this;
41739 angular.forEach(controller.info.arg, function (arg) {
41740 if (arg.initialValue) {
41741 if (arg.formType === 'number') {
41742 arg.input = parseInt(arg.initialValue);
41745 arg.input = arg.initialValue;
41750 OptionController.$inject = [];
41751 return OptionController;
41753 directives.OptionController = OptionController;
41754 })(directives = app.directives || (app.directives = {}));
41755 })(app || (app = {}));
41759 (function (directives) {
41760 var Directory = (function () {
41761 function Directory() {
41762 this.restrict = 'E';
41763 this.replace = true;
41764 this.controller = 'directoryController';
41765 this.controllerAs = 'ctrl';
41766 this.bindToController = {
41772 this.templateUrl = 'templates/directory.html';
41774 Directory.Factory = function () {
41775 var directive = function () {
41776 return new Directory();
41782 directives.Directory = Directory;
41783 var DirectoryController = (function () {
41784 function DirectoryController(APIEndPoint, $scope) {
41785 this.APIEndPoint = APIEndPoint;
41786 this.$scope = $scope;
41787 var controller = this;
41789 .getFiles(this.info.fileId)
41791 .then(function (result) {
41792 if (result.status === 'success') {
41793 controller.files = result.info;
41794 angular.forEach(result.info, function (file) {
41795 if (file.fileType === '0') {
41797 if (controller.info.path === '/') {
41798 o.path = '/' + file.name;
41801 o.path = controller.info.path + '/' + file.name;
41803 controller.add()(o, controller.list);
41810 DirectoryController.$inject = ['APIEndPoint', '$scope'];
41811 return DirectoryController;
41813 directives.DirectoryController = DirectoryController;
41814 })(directives = app.directives || (app.directives = {}));
41815 })(app || (app = {}));
41819 (function (controllers) {
41820 var Execution = (function () {
41821 function Execution(MyModal, $scope) {
41822 this.MyModal = MyModal;
41823 this.$scope = $scope;
41824 this.commandInfoList = [];
41827 Execution.prototype.add = function () {
41828 this.$scope.$broadcast('close');
41829 this.commandInfoList.push(new app.declares.CommandInfo('mrcImageNoiseAdd'));
41831 Execution.prototype.open = function () {
41832 var result = this.MyModal.open('SelectCommand');
41833 console.log(result);
41835 Execution.prototype.remove = function (index, list) {
41836 list.splice(index, 1);
41838 Execution.prototype.close = function () {
41839 console.log("close");
41841 Execution.$inject = ['MyModal', '$scope'];
41844 controllers.Execution = Execution;
41845 })(controllers = app.controllers || (app.controllers = {}));
41846 })(app || (app = {}));
41850 (function (controllers) {
41851 var Workspace = (function () {
41852 function Workspace($scope, APIEndPoint) {
41853 this.$scope = $scope;
41854 this.APIEndPoint = APIEndPoint;
41855 this.directoryList = [];
41856 var controller = this;
41857 var directoryList = this.directoryList;
41859 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
41867 directoryList.push(o);
41869 Workspace.prototype.addDirectory = function (info, directoryList) {
41870 directoryList.push(info);
41872 Workspace.$inject = ['$scope', 'APIEndPoint'];
41875 controllers.Workspace = Workspace;
41876 })(controllers = app.controllers || (app.controllers = {}));
41877 })(app || (app = {}));
41881 (function (controllers) {
41882 var History = (function () {
41883 function History($scope) {
41884 this.page = "History";
41886 History.$inject = ['$scope'];
41889 controllers.History = History;
41890 })(controllers = app.controllers || (app.controllers = {}));
41891 })(app || (app = {}));
41895 (function (controllers) {
41896 var SelectCommand = (function () {
41897 function SelectCommand($scope, APIEndPOint) {
41898 this.APIEndPOint = APIEndPOint;
41899 this.page = "History";
41901 SelectCommand.$inject = ['$scope', 'APIEndPoint'];
41902 return SelectCommand;
41904 controllers.SelectCommand = SelectCommand;
41905 })(controllers = app.controllers || (app.controllers = {}));
41906 })(app || (app = {}));
41910 var appName = 'zephyr';
41911 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
41912 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
41913 $urlRouterProvider.otherwise('/execution');
41914 $locationProvider.html5Mode({
41919 .state('execution', {
41921 templateUrl: 'templates/execution.html',
41922 controller: 'executionController',
41925 .state('workspace', {
41927 templateUrl: 'templates/workspace.html',
41928 controller: 'workspaceController',
41931 .state('history', {
41933 templateUrl: 'templates/history.html',
41934 controller: 'historyController',
41938 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
41939 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
41940 app.zephyr.service('MyModal', app.services.MyModal);
41941 app.zephyr.controller('executionController', app.controllers.Execution);
41942 app.zephyr.controller('workspaceController', app.controllers.Workspace);
41943 app.zephyr.controller('historyController', app.controllers.History);
41944 app.zephyr.controller('commandController', app.directives.CommandController);
41945 app.zephyr.controller('optionController', app.directives.OptionController);
41946 app.zephyr.controller('directoryController', app.directives.DirectoryController);
41947 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
41948 app.zephyr.directive('command', app.directives.Command.Factory());
41949 app.zephyr.directive('option', app.directives.Option.Factory());
41950 app.zephyr.directive('directory', app.directives.Directory.Factory());
41951 })(app || (app = {}));
41956 /***/ function(module, exports) {
41961 (function (declares) {
41962 var CommandInfo = (function () {
41963 function CommandInfo(name) {
41966 return CommandInfo;
41968 declares.CommandInfo = CommandInfo;
41969 })(declares = app.declares || (app.declares = {}));
41970 })(app || (app = {}));
41974 (function (services) {
41975 var APIEndPoint = (function () {
41976 function APIEndPoint($resource, $http) {
41977 this.$resource = $resource;
41978 this.$http = $http;
41980 APIEndPoint.prototype.resource = function (endPoint, data) {
41981 var customAction = {
41987 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
41989 return this.$resource(endPoint, {}, { execute: execute });
41991 APIEndPoint.prototype.getOptionControlFile = function (command) {
41992 var endPoint = '/api/v1/optionControlFile/' + command;
41993 return this.resource(endPoint, {}).get();
41995 APIEndPoint.prototype.getFiles = function (fileId) {
41996 var endPoint = '/api/v1/workspace';
41998 endPoint += '/' + fileId;
42000 return this.resource(endPoint, {}).get();
42002 APIEndPoint.prototype.getDirectories = function () {
42003 var endPoint = '/api/v1/all/workspace/directory';
42004 return this.resource(endPoint, {}).get();
42006 APIEndPoint.prototype.execute = function (data) {
42007 var endPoint = '/api/v1/execution';
42008 var fd = new FormData();
42009 fd.append('data', data);
42010 return this.$http.post(endPoint, fd, {
42011 headers: { 'Content-Type': undefined },
42012 transformRequest: angular.identity
42015 return APIEndPoint;
42017 services.APIEndPoint = APIEndPoint;
42018 })(services = app.services || (app.services = {}));
42019 })(app || (app = {}));
42023 (function (services) {
42024 var MyModal = (function () {
42025 function MyModal($uibModal) {
42026 this.$uibModal = $uibModal;
42027 this.modalOption = {
42034 MyModal.prototype.open = function (modalName) {
42035 if (modalName === 'SelectCommand') {
42036 this.modalOption.templateUrl = 'templates/select-command.html';
42037 this.modalOption.size = 'lg';
42039 return this.$uibModal.open(this.modalOption);
42041 MyModal.prototype.selectCommand = function () {
42042 this.modalOption.templateUrl = 'templates/select-command.html';
42043 this.modalOption.controller = 'selectCommandController';
42044 this.modalOption.size = 'lg';
42045 return this.$uibModal.open(this.modalOption);
42049 services.MyModal = MyModal;
42050 })(services = app.services || (app.services = {}));
42051 })(app || (app = {}));
42055 (function (directives) {
42056 var Command = (function () {
42057 function Command() {
42058 this.restrict = 'E';
42059 this.replace = true;
42061 this.controller = 'commandController';
42062 this.controllerAs = 'ctrl';
42063 this.bindToController = {
42069 this.templateUrl = 'templates/command.html';
42071 Command.Factory = function () {
42072 var directive = function () {
42073 return new Command();
42075 directive.$inject = [];
42080 directives.Command = Command;
42081 var CommandController = (function () {
42082 function CommandController(APIEndPoint, $scope) {
42083 this.APIEndPoint = APIEndPoint;
42084 this.$scope = $scope;
42085 var controller = this;
42087 .getOptionControlFile('mrcImageNoiseAdd')
42089 .then(function (result) {
42090 controller.options = result.info;
42095 .then(function (result) {
42096 controller.dirs = result.info;
42098 this.heading = "[" + this.index + "]: dcdFilePring";
42099 this.isOpen = true;
42100 this.$scope.$on('close', function () {
42101 controller.isOpen = false;
42104 CommandController.prototype.submit = function () {
42106 angular.forEach(this.options, function (option) {
42108 name: option.option,
42111 angular.forEach(option.arg, function (arg) {
42113 if (typeof arg.input === 'object') {
42114 obj.arguments.push(arg.input.name);
42117 obj.arguments.push(arg.input);
42121 if (obj.arguments.length > 0) {
42126 command: this.name,
42127 workspace: this.workspace.fileId,
42131 .execute(JSON.stringify(execObj))
42132 .then(function (result) {
42133 console.log(result);
42136 CommandController.prototype.removeMySelf = function (index) {
42137 this.remove()(index, this.list);
42139 CommandController.prototype.reloadFiles = function () {
42141 var fileId = this.workspace.fileId;
42145 .then(function (result) {
42146 var status = result.status;
42147 if (status === 'success') {
42148 _this.files = result.info;
42151 console.log(result.message);
42155 CommandController.prototype.debug = function () {
42156 console.log(this.files);
42157 console.log(this.files);
42158 console.log(this.workspace);
42160 CommandController.$inject = ['APIEndPoint', '$scope'];
42161 return CommandController;
42163 directives.CommandController = CommandController;
42164 })(directives = app.directives || (app.directives = {}));
42165 })(app || (app = {}));
42169 (function (directives) {
42170 var HeaderMenu = (function () {
42171 function HeaderMenu() {
42172 this.restrict = 'E';
42173 this.replace = true;
42174 this.templateUrl = 'templates/header-menu.html';
42176 HeaderMenu.Factory = function () {
42177 var directive = function () {
42178 return new HeaderMenu();
42184 directives.HeaderMenu = HeaderMenu;
42185 })(directives = app.directives || (app.directives = {}));
42186 })(app || (app = {}));
42190 (function (directives) {
42191 var Option = (function () {
42192 function Option() {
42193 this.restrict = 'E';
42194 this.replace = true;
42195 this.controller = 'optionController';
42196 this.bindToController = {
42201 this.templateUrl = 'templates/option.html';
42202 this.controllerAs = 'ctrl';
42204 Option.Factory = function () {
42205 var directive = function () {
42206 return new Option();
42208 directive.$inject = [];
42213 directives.Option = Option;
42214 var OptionController = (function () {
42215 function OptionController() {
42216 var controller = this;
42217 angular.forEach(controller.info.arg, function (arg) {
42218 if (arg.initialValue) {
42219 if (arg.formType === 'number') {
42220 arg.input = parseInt(arg.initialValue);
42223 arg.input = arg.initialValue;
42228 OptionController.$inject = [];
42229 return OptionController;
42231 directives.OptionController = OptionController;
42232 })(directives = app.directives || (app.directives = {}));
42233 })(app || (app = {}));
42237 (function (directives) {
42238 var Directory = (function () {
42239 function Directory() {
42240 this.restrict = 'E';
42241 this.replace = true;
42242 this.controller = 'directoryController';
42243 this.controllerAs = 'ctrl';
42244 this.bindToController = {
42250 this.templateUrl = 'templates/directory.html';
42252 Directory.Factory = function () {
42253 var directive = function () {
42254 return new Directory();
42260 directives.Directory = Directory;
42261 var DirectoryController = (function () {
42262 function DirectoryController(APIEndPoint, $scope) {
42263 this.APIEndPoint = APIEndPoint;
42264 this.$scope = $scope;
42265 var controller = this;
42267 .getFiles(this.info.fileId)
42269 .then(function (result) {
42270 if (result.status === 'success') {
42271 controller.files = result.info;
42272 angular.forEach(result.info, function (file) {
42273 if (file.fileType === '0') {
42275 if (controller.info.path === '/') {
42276 o.path = '/' + file.name;
42279 o.path = controller.info.path + '/' + file.name;
42281 controller.add()(o, controller.list);
42288 DirectoryController.$inject = ['APIEndPoint', '$scope'];
42289 return DirectoryController;
42291 directives.DirectoryController = DirectoryController;
42292 })(directives = app.directives || (app.directives = {}));
42293 })(app || (app = {}));
42297 (function (controllers) {
42298 var Execution = (function () {
42299 function Execution(MyModal, $scope) {
42300 this.MyModal = MyModal;
42301 this.$scope = $scope;
42302 this.commandInfoList = [];
42305 Execution.prototype.add = function () {
42306 this.$scope.$broadcast('close');
42307 this.commandInfoList.push(new app.declares.CommandInfo('mrcImageNoiseAdd'));
42309 Execution.prototype.open = function () {
42310 var result = this.MyModal.open('SelectCommand');
42311 console.log(result);
42313 Execution.prototype.remove = function (index, list) {
42314 list.splice(index, 1);
42316 Execution.prototype.close = function () {
42317 console.log("close");
42319 Execution.$inject = ['MyModal', '$scope'];
42322 controllers.Execution = Execution;
42323 })(controllers = app.controllers || (app.controllers = {}));
42324 })(app || (app = {}));
42328 (function (controllers) {
42329 var Workspace = (function () {
42330 function Workspace($scope, APIEndPoint) {
42331 this.$scope = $scope;
42332 this.APIEndPoint = APIEndPoint;
42333 this.directoryList = [];
42334 var controller = this;
42335 var directoryList = this.directoryList;
42337 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
42345 directoryList.push(o);
42347 Workspace.prototype.addDirectory = function (info, directoryList) {
42348 directoryList.push(info);
42350 Workspace.$inject = ['$scope', 'APIEndPoint'];
42353 controllers.Workspace = Workspace;
42354 })(controllers = app.controllers || (app.controllers = {}));
42355 })(app || (app = {}));
42359 (function (controllers) {
42360 var History = (function () {
42361 function History($scope) {
42362 this.page = "History";
42364 History.$inject = ['$scope'];
42367 controllers.History = History;
42368 })(controllers = app.controllers || (app.controllers = {}));
42369 })(app || (app = {}));
42373 (function (controllers) {
42374 var SelectCommand = (function () {
42375 function SelectCommand($scope, APIEndPOint) {
42376 this.APIEndPOint = APIEndPOint;
42377 this.page = "History";
42379 SelectCommand.$inject = ['$scope', 'APIEndPoint'];
42380 return SelectCommand;
42382 controllers.SelectCommand = SelectCommand;
42383 })(controllers = app.controllers || (app.controllers = {}));
42384 })(app || (app = {}));
42388 var appName = 'zephyr';
42389 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
42390 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
42391 $urlRouterProvider.otherwise('/execution');
42392 $locationProvider.html5Mode({
42397 .state('execution', {
42399 templateUrl: 'templates/execution.html',
42400 controller: 'executionController',
42403 .state('workspace', {
42405 templateUrl: 'templates/workspace.html',
42406 controller: 'workspaceController',
42409 .state('history', {
42411 templateUrl: 'templates/history.html',
42412 controller: 'historyController',
42416 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
42417 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
42418 app.zephyr.service('MyModal', app.services.MyModal);
42419 app.zephyr.controller('executionController', app.controllers.Execution);
42420 app.zephyr.controller('workspaceController', app.controllers.Workspace);
42421 app.zephyr.controller('historyController', app.controllers.History);
42422 app.zephyr.controller('commandController', app.directives.CommandController);
42423 app.zephyr.controller('optionController', app.directives.OptionController);
42424 app.zephyr.controller('directoryController', app.directives.DirectoryController);
42425 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
42426 app.zephyr.directive('command', app.directives.Command.Factory());
42427 app.zephyr.directive('option', app.directives.Option.Factory());
42428 app.zephyr.directive('directory', app.directives.Directory.Factory());
42429 })(app || (app = {}));
42434 /***/ function(module, exports) {
42439 (function (declares) {
42440 var CommandInfo = (function () {
42441 function CommandInfo(name) {
42444 return CommandInfo;
42446 declares.CommandInfo = CommandInfo;
42447 })(declares = app.declares || (app.declares = {}));
42448 })(app || (app = {}));
42452 (function (services) {
42453 var APIEndPoint = (function () {
42454 function APIEndPoint($resource, $http) {
42455 this.$resource = $resource;
42456 this.$http = $http;
42458 APIEndPoint.prototype.resource = function (endPoint, data) {
42459 var customAction = {
42465 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
42467 return this.$resource(endPoint, {}, { execute: execute });
42469 APIEndPoint.prototype.getOptionControlFile = function (command) {
42470 var endPoint = '/api/v1/optionControlFile/' + command;
42471 return this.resource(endPoint, {}).get();
42473 APIEndPoint.prototype.getFiles = function (fileId) {
42474 var endPoint = '/api/v1/workspace';
42476 endPoint += '/' + fileId;
42478 return this.resource(endPoint, {}).get();
42480 APIEndPoint.prototype.getDirectories = function () {
42481 var endPoint = '/api/v1/all/workspace/directory';
42482 return this.resource(endPoint, {}).get();
42484 APIEndPoint.prototype.execute = function (data) {
42485 var endPoint = '/api/v1/execution';
42486 var fd = new FormData();
42487 fd.append('data', data);
42488 return this.$http.post(endPoint, fd, {
42489 headers: { 'Content-Type': undefined },
42490 transformRequest: angular.identity
42493 return APIEndPoint;
42495 services.APIEndPoint = APIEndPoint;
42496 })(services = app.services || (app.services = {}));
42497 })(app || (app = {}));
42501 (function (services) {
42502 var MyModal = (function () {
42503 function MyModal($uibModal) {
42504 this.$uibModal = $uibModal;
42505 this.modalOption = {
42512 MyModal.prototype.open = function (modalName) {
42513 if (modalName === 'SelectCommand') {
42514 this.modalOption.templateUrl = 'templates/select-command.html';
42515 this.modalOption.size = 'lg';
42517 return this.$uibModal.open(this.modalOption);
42519 MyModal.prototype.selectCommand = function () {
42520 this.modalOption.templateUrl = 'templates/select-command.html';
42521 this.modalOption.controller = 'selectCommandController';
42522 this.modalOption.size = 'lg';
42523 return this.$uibModal.open(this.modalOption);
42527 services.MyModal = MyModal;
42528 })(services = app.services || (app.services = {}));
42529 })(app || (app = {}));
42533 (function (directives) {
42534 var Command = (function () {
42535 function Command() {
42536 this.restrict = 'E';
42537 this.replace = true;
42539 this.controller = 'commandController';
42540 this.controllerAs = 'ctrl';
42541 this.bindToController = {
42547 this.templateUrl = 'templates/command.html';
42549 Command.Factory = function () {
42550 var directive = function () {
42551 return new Command();
42553 directive.$inject = [];
42558 directives.Command = Command;
42559 var CommandController = (function () {
42560 function CommandController(APIEndPoint, $scope) {
42561 this.APIEndPoint = APIEndPoint;
42562 this.$scope = $scope;
42563 var controller = this;
42565 .getOptionControlFile('mrcImageNoiseAdd')
42567 .then(function (result) {
42568 controller.options = result.info;
42573 .then(function (result) {
42574 controller.dirs = result.info;
42576 this.heading = "[" + this.index + "]: dcdFilePring";
42577 this.isOpen = true;
42578 this.$scope.$on('close', function () {
42579 controller.isOpen = false;
42582 CommandController.prototype.submit = function () {
42584 angular.forEach(this.options, function (option) {
42586 name: option.option,
42589 angular.forEach(option.arg, function (arg) {
42591 if (typeof arg.input === 'object') {
42592 obj.arguments.push(arg.input.name);
42595 obj.arguments.push(arg.input);
42599 if (obj.arguments.length > 0) {
42604 command: this.name,
42605 workspace: this.workspace.fileId,
42609 .execute(JSON.stringify(execObj))
42610 .then(function (result) {
42611 console.log(result);
42614 CommandController.prototype.removeMySelf = function (index) {
42615 this.remove()(index, this.list);
42617 CommandController.prototype.reloadFiles = function () {
42619 var fileId = this.workspace.fileId;
42623 .then(function (result) {
42624 var status = result.status;
42625 if (status === 'success') {
42626 _this.files = result.info;
42629 console.log(result.message);
42633 CommandController.prototype.debug = function () {
42634 console.log(this.files);
42635 console.log(this.files);
42636 console.log(this.workspace);
42638 CommandController.$inject = ['APIEndPoint', '$scope'];
42639 return CommandController;
42641 directives.CommandController = CommandController;
42642 })(directives = app.directives || (app.directives = {}));
42643 })(app || (app = {}));
42647 (function (directives) {
42648 var HeaderMenu = (function () {
42649 function HeaderMenu() {
42650 this.restrict = 'E';
42651 this.replace = true;
42652 this.templateUrl = 'templates/header-menu.html';
42654 HeaderMenu.Factory = function () {
42655 var directive = function () {
42656 return new HeaderMenu();
42662 directives.HeaderMenu = HeaderMenu;
42663 })(directives = app.directives || (app.directives = {}));
42664 })(app || (app = {}));
42668 (function (directives) {
42669 var Option = (function () {
42670 function Option() {
42671 this.restrict = 'E';
42672 this.replace = true;
42673 this.controller = 'optionController';
42674 this.bindToController = {
42679 this.templateUrl = 'templates/option.html';
42680 this.controllerAs = 'ctrl';
42682 Option.Factory = function () {
42683 var directive = function () {
42684 return new Option();
42686 directive.$inject = [];
42691 directives.Option = Option;
42692 var OptionController = (function () {
42693 function OptionController() {
42694 var controller = this;
42695 angular.forEach(controller.info.arg, function (arg) {
42696 if (arg.initialValue) {
42697 if (arg.formType === 'number') {
42698 arg.input = parseInt(arg.initialValue);
42701 arg.input = arg.initialValue;
42706 OptionController.$inject = [];
42707 return OptionController;
42709 directives.OptionController = OptionController;
42710 })(directives = app.directives || (app.directives = {}));
42711 })(app || (app = {}));
42715 (function (directives) {
42716 var Directory = (function () {
42717 function Directory() {
42718 this.restrict = 'E';
42719 this.replace = true;
42720 this.controller = 'directoryController';
42721 this.controllerAs = 'ctrl';
42722 this.bindToController = {
42728 this.templateUrl = 'templates/directory.html';
42730 Directory.Factory = function () {
42731 var directive = function () {
42732 return new Directory();
42738 directives.Directory = Directory;
42739 var DirectoryController = (function () {
42740 function DirectoryController(APIEndPoint, $scope) {
42741 this.APIEndPoint = APIEndPoint;
42742 this.$scope = $scope;
42743 var controller = this;
42745 .getFiles(this.info.fileId)
42747 .then(function (result) {
42748 if (result.status === 'success') {
42749 controller.files = result.info;
42750 angular.forEach(result.info, function (file) {
42751 if (file.fileType === '0') {
42753 if (controller.info.path === '/') {
42754 o.path = '/' + file.name;
42757 o.path = controller.info.path + '/' + file.name;
42759 controller.add()(o, controller.list);
42766 DirectoryController.$inject = ['APIEndPoint', '$scope'];
42767 return DirectoryController;
42769 directives.DirectoryController = DirectoryController;
42770 })(directives = app.directives || (app.directives = {}));
42771 })(app || (app = {}));
42775 (function (controllers) {
42776 var Execution = (function () {
42777 function Execution(MyModal, $scope) {
42778 this.MyModal = MyModal;
42779 this.$scope = $scope;
42780 this.commandInfoList = [];
42783 Execution.prototype.add = function () {
42784 this.$scope.$broadcast('close');
42785 this.commandInfoList.push(new app.declares.CommandInfo('mrcImageNoiseAdd'));
42787 Execution.prototype.open = function () {
42788 var result = this.MyModal.open('SelectCommand');
42789 console.log(result);
42791 Execution.prototype.remove = function (index, list) {
42792 list.splice(index, 1);
42794 Execution.prototype.close = function () {
42795 console.log("close");
42797 Execution.$inject = ['MyModal', '$scope'];
42800 controllers.Execution = Execution;
42801 })(controllers = app.controllers || (app.controllers = {}));
42802 })(app || (app = {}));
42806 (function (controllers) {
42807 var Workspace = (function () {
42808 function Workspace($scope, APIEndPoint) {
42809 this.$scope = $scope;
42810 this.APIEndPoint = APIEndPoint;
42811 this.directoryList = [];
42812 var controller = this;
42813 var directoryList = this.directoryList;
42815 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
42823 directoryList.push(o);
42825 Workspace.prototype.addDirectory = function (info, directoryList) {
42826 directoryList.push(info);
42828 Workspace.$inject = ['$scope', 'APIEndPoint'];
42831 controllers.Workspace = Workspace;
42832 })(controllers = app.controllers || (app.controllers = {}));
42833 })(app || (app = {}));
42837 (function (controllers) {
42838 var History = (function () {
42839 function History($scope) {
42840 this.page = "History";
42842 History.$inject = ['$scope'];
42845 controllers.History = History;
42846 })(controllers = app.controllers || (app.controllers = {}));
42847 })(app || (app = {}));
42851 (function (controllers) {
42852 var SelectCommand = (function () {
42853 function SelectCommand($scope, APIEndPOint) {
42854 this.APIEndPOint = APIEndPOint;
42855 this.page = "History";
42857 SelectCommand.$inject = ['$scope', 'APIEndPoint'];
42858 return SelectCommand;
42860 controllers.SelectCommand = SelectCommand;
42861 })(controllers = app.controllers || (app.controllers = {}));
42862 })(app || (app = {}));
42866 var appName = 'zephyr';
42867 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
42868 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
42869 $urlRouterProvider.otherwise('/execution');
42870 $locationProvider.html5Mode({
42875 .state('execution', {
42877 templateUrl: 'templates/execution.html',
42878 controller: 'executionController',
42881 .state('workspace', {
42883 templateUrl: 'templates/workspace.html',
42884 controller: 'workspaceController',
42887 .state('history', {
42889 templateUrl: 'templates/history.html',
42890 controller: 'historyController',
42894 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
42895 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
42896 app.zephyr.service('MyModal', app.services.MyModal);
42897 app.zephyr.controller('executionController', app.controllers.Execution);
42898 app.zephyr.controller('workspaceController', app.controllers.Workspace);
42899 app.zephyr.controller('historyController', app.controllers.History);
42900 app.zephyr.controller('commandController', app.directives.CommandController);
42901 app.zephyr.controller('optionController', app.directives.OptionController);
42902 app.zephyr.controller('directoryController', app.directives.DirectoryController);
42903 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
42904 app.zephyr.directive('command', app.directives.Command.Factory());
42905 app.zephyr.directive('option', app.directives.Option.Factory());
42906 app.zephyr.directive('directory', app.directives.Directory.Factory());
42907 })(app || (app = {}));
42912 /***/ function(module, exports) {
42917 (function (declares) {
42918 var CommandInfo = (function () {
42919 function CommandInfo(name) {
42922 return CommandInfo;
42924 declares.CommandInfo = CommandInfo;
42925 })(declares = app.declares || (app.declares = {}));
42926 })(app || (app = {}));
42930 (function (services) {
42931 var APIEndPoint = (function () {
42932 function APIEndPoint($resource, $http) {
42933 this.$resource = $resource;
42934 this.$http = $http;
42936 APIEndPoint.prototype.resource = function (endPoint, data) {
42937 var customAction = {
42943 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
42945 return this.$resource(endPoint, {}, { execute: execute });
42947 APIEndPoint.prototype.getOptionControlFile = function (command) {
42948 var endPoint = '/api/v1/optionControlFile/' + command;
42949 return this.resource(endPoint, {}).get();
42951 APIEndPoint.prototype.getFiles = function (fileId) {
42952 var endPoint = '/api/v1/workspace';
42954 endPoint += '/' + fileId;
42956 return this.resource(endPoint, {}).get();
42958 APIEndPoint.prototype.getDirectories = function () {
42959 var endPoint = '/api/v1/all/workspace/directory';
42960 return this.resource(endPoint, {}).get();
42962 APIEndPoint.prototype.execute = function (data) {
42963 var endPoint = '/api/v1/execution';
42964 var fd = new FormData();
42965 fd.append('data', data);
42966 return this.$http.post(endPoint, fd, {
42967 headers: { 'Content-Type': undefined },
42968 transformRequest: angular.identity
42971 return APIEndPoint;
42973 services.APIEndPoint = APIEndPoint;
42974 })(services = app.services || (app.services = {}));
42975 })(app || (app = {}));
42979 (function (services) {
42980 var MyModal = (function () {
42981 function MyModal($uibModal) {
42982 this.$uibModal = $uibModal;
42983 this.modalOption = {
42990 MyModal.prototype.open = function (modalName) {
42991 if (modalName === 'SelectCommand') {
42992 this.modalOption.templateUrl = 'templates/select-command.html';
42993 this.modalOption.size = 'lg';
42995 return this.$uibModal.open(this.modalOption);
42997 MyModal.prototype.selectCommand = function () {
42998 this.modalOption.templateUrl = 'templates/select-command.html';
42999 this.modalOption.controller = 'selectCommandController';
43000 this.modalOption.size = 'lg';
43001 return this.$uibModal.open(this.modalOption);
43005 services.MyModal = MyModal;
43006 })(services = app.services || (app.services = {}));
43007 })(app || (app = {}));
43011 (function (directives) {
43012 var Command = (function () {
43013 function Command() {
43014 this.restrict = 'E';
43015 this.replace = true;
43017 this.controller = 'commandController';
43018 this.controllerAs = 'ctrl';
43019 this.bindToController = {
43025 this.templateUrl = 'templates/command.html';
43027 Command.Factory = function () {
43028 var directive = function () {
43029 return new Command();
43031 directive.$inject = [];
43036 directives.Command = Command;
43037 var CommandController = (function () {
43038 function CommandController(APIEndPoint, $scope) {
43039 this.APIEndPoint = APIEndPoint;
43040 this.$scope = $scope;
43041 var controller = this;
43043 .getOptionControlFile('mrcImageNoiseAdd')
43045 .then(function (result) {
43046 controller.options = result.info;
43051 .then(function (result) {
43052 controller.dirs = result.info;
43054 this.heading = "[" + this.index + "]: dcdFilePring";
43055 this.isOpen = true;
43056 this.$scope.$on('close', function () {
43057 controller.isOpen = false;
43060 CommandController.prototype.submit = function () {
43062 angular.forEach(this.options, function (option) {
43064 name: option.option,
43067 angular.forEach(option.arg, function (arg) {
43069 if (typeof arg.input === 'object') {
43070 obj.arguments.push(arg.input.name);
43073 obj.arguments.push(arg.input);
43077 if (obj.arguments.length > 0) {
43082 command: this.name,
43083 workspace: this.workspace.fileId,
43087 .execute(JSON.stringify(execObj))
43088 .then(function (result) {
43089 console.log(result);
43092 CommandController.prototype.removeMySelf = function (index) {
43093 this.remove()(index, this.list);
43095 CommandController.prototype.reloadFiles = function () {
43097 var fileId = this.workspace.fileId;
43101 .then(function (result) {
43102 var status = result.status;
43103 if (status === 'success') {
43104 _this.files = result.info;
43107 console.log(result.message);
43111 CommandController.prototype.debug = function () {
43112 console.log(this.files);
43113 console.log(this.files);
43114 console.log(this.workspace);
43116 CommandController.$inject = ['APIEndPoint', '$scope'];
43117 return CommandController;
43119 directives.CommandController = CommandController;
43120 })(directives = app.directives || (app.directives = {}));
43121 })(app || (app = {}));
43125 (function (directives) {
43126 var HeaderMenu = (function () {
43127 function HeaderMenu() {
43128 this.restrict = 'E';
43129 this.replace = true;
43130 this.templateUrl = 'templates/header-menu.html';
43132 HeaderMenu.Factory = function () {
43133 var directive = function () {
43134 return new HeaderMenu();
43140 directives.HeaderMenu = HeaderMenu;
43141 })(directives = app.directives || (app.directives = {}));
43142 })(app || (app = {}));
43146 (function (directives) {
43147 var Option = (function () {
43148 function Option() {
43149 this.restrict = 'E';
43150 this.replace = true;
43151 this.controller = 'optionController';
43152 this.bindToController = {
43157 this.templateUrl = 'templates/option.html';
43158 this.controllerAs = 'ctrl';
43160 Option.Factory = function () {
43161 var directive = function () {
43162 return new Option();
43164 directive.$inject = [];
43169 directives.Option = Option;
43170 var OptionController = (function () {
43171 function OptionController() {
43172 var controller = this;
43173 angular.forEach(controller.info.arg, function (arg) {
43174 if (arg.initialValue) {
43175 if (arg.formType === 'number') {
43176 arg.input = parseInt(arg.initialValue);
43179 arg.input = arg.initialValue;
43184 OptionController.$inject = [];
43185 return OptionController;
43187 directives.OptionController = OptionController;
43188 })(directives = app.directives || (app.directives = {}));
43189 })(app || (app = {}));
43193 (function (directives) {
43194 var Directory = (function () {
43195 function Directory() {
43196 this.restrict = 'E';
43197 this.replace = true;
43198 this.controller = 'directoryController';
43199 this.controllerAs = 'ctrl';
43200 this.bindToController = {
43206 this.templateUrl = 'templates/directory.html';
43208 Directory.Factory = function () {
43209 var directive = function () {
43210 return new Directory();
43216 directives.Directory = Directory;
43217 var DirectoryController = (function () {
43218 function DirectoryController(APIEndPoint, $scope) {
43219 this.APIEndPoint = APIEndPoint;
43220 this.$scope = $scope;
43221 var controller = this;
43223 .getFiles(this.info.fileId)
43225 .then(function (result) {
43226 if (result.status === 'success') {
43227 controller.files = result.info;
43228 angular.forEach(result.info, function (file) {
43229 if (file.fileType === '0') {
43231 if (controller.info.path === '/') {
43232 o.path = '/' + file.name;
43235 o.path = controller.info.path + '/' + file.name;
43237 controller.add()(o, controller.list);
43244 DirectoryController.$inject = ['APIEndPoint', '$scope'];
43245 return DirectoryController;
43247 directives.DirectoryController = DirectoryController;
43248 })(directives = app.directives || (app.directives = {}));
43249 })(app || (app = {}));
43253 (function (controllers) {
43254 var Execution = (function () {
43255 function Execution(MyModal, $scope) {
43256 this.MyModal = MyModal;
43257 this.$scope = $scope;
43258 this.commandInfoList = [];
43261 Execution.prototype.add = function () {
43262 this.$scope.$broadcast('close');
43263 this.commandInfoList.push(new app.declares.CommandInfo('mrcImageNoiseAdd'));
43265 Execution.prototype.open = function () {
43266 var result = this.MyModal.open('SelectCommand');
43267 console.log(result);
43269 Execution.prototype.remove = function (index, list) {
43270 list.splice(index, 1);
43272 Execution.prototype.close = function () {
43273 console.log("close");
43275 Execution.$inject = ['MyModal', '$scope'];
43278 controllers.Execution = Execution;
43279 })(controllers = app.controllers || (app.controllers = {}));
43280 })(app || (app = {}));
43284 (function (controllers) {
43285 var Workspace = (function () {
43286 function Workspace($scope, APIEndPoint) {
43287 this.$scope = $scope;
43288 this.APIEndPoint = APIEndPoint;
43289 this.directoryList = [];
43290 var controller = this;
43291 var directoryList = this.directoryList;
43293 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
43301 directoryList.push(o);
43303 Workspace.prototype.addDirectory = function (info, directoryList) {
43304 directoryList.push(info);
43306 Workspace.$inject = ['$scope', 'APIEndPoint'];
43309 controllers.Workspace = Workspace;
43310 })(controllers = app.controllers || (app.controllers = {}));
43311 })(app || (app = {}));
43315 (function (controllers) {
43316 var History = (function () {
43317 function History($scope) {
43318 this.page = "History";
43320 History.$inject = ['$scope'];
43323 controllers.History = History;
43324 })(controllers = app.controllers || (app.controllers = {}));
43325 })(app || (app = {}));
43329 (function (controllers) {
43330 var SelectCommand = (function () {
43331 function SelectCommand($scope, APIEndPOint) {
43332 this.APIEndPOint = APIEndPOint;
43333 this.page = "History";
43335 SelectCommand.$inject = ['$scope', 'APIEndPoint'];
43336 return SelectCommand;
43338 controllers.SelectCommand = SelectCommand;
43339 })(controllers = app.controllers || (app.controllers = {}));
43340 })(app || (app = {}));
43344 var appName = 'zephyr';
43345 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
43346 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
43347 $urlRouterProvider.otherwise('/execution');
43348 $locationProvider.html5Mode({
43353 .state('execution', {
43355 templateUrl: 'templates/execution.html',
43356 controller: 'executionController',
43359 .state('workspace', {
43361 templateUrl: 'templates/workspace.html',
43362 controller: 'workspaceController',
43365 .state('history', {
43367 templateUrl: 'templates/history.html',
43368 controller: 'historyController',
43372 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
43373 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
43374 app.zephyr.service('MyModal', app.services.MyModal);
43375 app.zephyr.controller('executionController', app.controllers.Execution);
43376 app.zephyr.controller('workspaceController', app.controllers.Workspace);
43377 app.zephyr.controller('historyController', app.controllers.History);
43378 app.zephyr.controller('commandController', app.directives.CommandController);
43379 app.zephyr.controller('optionController', app.directives.OptionController);
43380 app.zephyr.controller('directoryController', app.directives.DirectoryController);
43381 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
43382 app.zephyr.directive('command', app.directives.Command.Factory());
43383 app.zephyr.directive('option', app.directives.Option.Factory());
43384 app.zephyr.directive('directory', app.directives.Directory.Factory());
43385 })(app || (app = {}));
43390 /***/ function(module, exports) {
43395 (function (declares) {
43396 var CommandInfo = (function () {
43397 function CommandInfo(name) {
43400 return CommandInfo;
43402 declares.CommandInfo = CommandInfo;
43403 })(declares = app.declares || (app.declares = {}));
43404 })(app || (app = {}));
43408 (function (services) {
43409 var APIEndPoint = (function () {
43410 function APIEndPoint($resource, $http) {
43411 this.$resource = $resource;
43412 this.$http = $http;
43414 APIEndPoint.prototype.resource = function (endPoint, data) {
43415 var customAction = {
43421 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
43423 return this.$resource(endPoint, {}, { execute: execute });
43425 APIEndPoint.prototype.getOptionControlFile = function (command) {
43426 var endPoint = '/api/v1/optionControlFile/' + command;
43427 return this.resource(endPoint, {}).get();
43429 APIEndPoint.prototype.getFiles = function (fileId) {
43430 var endPoint = '/api/v1/workspace';
43432 endPoint += '/' + fileId;
43434 return this.resource(endPoint, {}).get();
43436 APIEndPoint.prototype.getDirectories = function () {
43437 var endPoint = '/api/v1/all/workspace/directory';
43438 return this.resource(endPoint, {}).get();
43440 APIEndPoint.prototype.execute = function (data) {
43441 var endPoint = '/api/v1/execution';
43442 var fd = new FormData();
43443 fd.append('data', data);
43444 return this.$http.post(endPoint, fd, {
43445 headers: { 'Content-Type': undefined },
43446 transformRequest: angular.identity
43449 return APIEndPoint;
43451 services.APIEndPoint = APIEndPoint;
43452 })(services = app.services || (app.services = {}));
43453 })(app || (app = {}));
43457 (function (services) {
43458 var MyModal = (function () {
43459 function MyModal($uibModal) {
43460 this.$uibModal = $uibModal;
43461 this.modalOption = {
43468 MyModal.prototype.open = function (modalName) {
43469 if (modalName === 'SelectCommand') {
43470 this.modalOption.templateUrl = 'templates/select-command.html';
43471 this.modalOption.size = 'lg';
43473 return this.$uibModal.open(this.modalOption);
43475 MyModal.prototype.selectCommand = function () {
43476 this.modalOption.templateUrl = 'templates/select-command.html';
43477 this.modalOption.controller = 'selectCommandController';
43478 this.modalOption.size = 'lg';
43479 return this.$uibModal.open(this.modalOption);
43483 services.MyModal = MyModal;
43484 })(services = app.services || (app.services = {}));
43485 })(app || (app = {}));
43489 (function (directives) {
43490 var Command = (function () {
43491 function Command() {
43492 this.restrict = 'E';
43493 this.replace = true;
43495 this.controller = 'commandController';
43496 this.controllerAs = 'ctrl';
43497 this.bindToController = {
43503 this.templateUrl = 'templates/command.html';
43505 Command.Factory = function () {
43506 var directive = function () {
43507 return new Command();
43509 directive.$inject = [];
43514 directives.Command = Command;
43515 var CommandController = (function () {
43516 function CommandController(APIEndPoint, $scope) {
43517 this.APIEndPoint = APIEndPoint;
43518 this.$scope = $scope;
43519 var controller = this;
43521 .getOptionControlFile('mrcImageNoiseAdd')
43523 .then(function (result) {
43524 controller.options = result.info;
43529 .then(function (result) {
43530 controller.dirs = result.info;
43532 this.heading = "[" + this.index + "]: dcdFilePring";
43533 this.isOpen = true;
43534 this.$scope.$on('close', function () {
43535 controller.isOpen = false;
43538 CommandController.prototype.submit = function () {
43540 angular.forEach(this.options, function (option) {
43542 name: option.option,
43545 angular.forEach(option.arg, function (arg) {
43547 if (typeof arg.input === 'object') {
43548 obj.arguments.push(arg.input.name);
43551 obj.arguments.push(arg.input);
43555 if (obj.arguments.length > 0) {
43560 command: this.name,
43561 workspace: this.workspace.fileId,
43565 .execute(JSON.stringify(execObj))
43566 .then(function (result) {
43567 console.log(result);
43570 CommandController.prototype.removeMySelf = function (index) {
43571 this.remove()(index, this.list);
43573 CommandController.prototype.reloadFiles = function () {
43575 var fileId = this.workspace.fileId;
43579 .then(function (result) {
43580 var status = result.status;
43581 if (status === 'success') {
43582 _this.files = result.info;
43585 console.log(result.message);
43589 CommandController.prototype.debug = function () {
43590 console.log(this.files);
43591 console.log(this.files);
43592 console.log(this.workspace);
43594 CommandController.$inject = ['APIEndPoint', '$scope'];
43595 return CommandController;
43597 directives.CommandController = CommandController;
43598 })(directives = app.directives || (app.directives = {}));
43599 })(app || (app = {}));
43603 (function (directives) {
43604 var HeaderMenu = (function () {
43605 function HeaderMenu() {
43606 this.restrict = 'E';
43607 this.replace = true;
43608 this.templateUrl = 'templates/header-menu.html';
43610 HeaderMenu.Factory = function () {
43611 var directive = function () {
43612 return new HeaderMenu();
43618 directives.HeaderMenu = HeaderMenu;
43619 })(directives = app.directives || (app.directives = {}));
43620 })(app || (app = {}));
43624 (function (directives) {
43625 var Option = (function () {
43626 function Option() {
43627 this.restrict = 'E';
43628 this.replace = true;
43629 this.controller = 'optionController';
43630 this.bindToController = {
43635 this.templateUrl = 'templates/option.html';
43636 this.controllerAs = 'ctrl';
43638 Option.Factory = function () {
43639 var directive = function () {
43640 return new Option();
43642 directive.$inject = [];
43647 directives.Option = Option;
43648 var OptionController = (function () {
43649 function OptionController() {
43650 var controller = this;
43651 angular.forEach(controller.info.arg, function (arg) {
43652 if (arg.initialValue) {
43653 if (arg.formType === 'number') {
43654 arg.input = parseInt(arg.initialValue);
43657 arg.input = arg.initialValue;
43662 OptionController.$inject = [];
43663 return OptionController;
43665 directives.OptionController = OptionController;
43666 })(directives = app.directives || (app.directives = {}));
43667 })(app || (app = {}));
43671 (function (directives) {
43672 var Directory = (function () {
43673 function Directory() {
43674 this.restrict = 'E';
43675 this.replace = true;
43676 this.controller = 'directoryController';
43677 this.controllerAs = 'ctrl';
43678 this.bindToController = {
43684 this.templateUrl = 'templates/directory.html';
43686 Directory.Factory = function () {
43687 var directive = function () {
43688 return new Directory();
43694 directives.Directory = Directory;
43695 var DirectoryController = (function () {
43696 function DirectoryController(APIEndPoint, $scope) {
43697 this.APIEndPoint = APIEndPoint;
43698 this.$scope = $scope;
43699 var controller = this;
43701 .getFiles(this.info.fileId)
43703 .then(function (result) {
43704 if (result.status === 'success') {
43705 controller.files = result.info;
43706 angular.forEach(result.info, function (file) {
43707 if (file.fileType === '0') {
43709 if (controller.info.path === '/') {
43710 o.path = '/' + file.name;
43713 o.path = controller.info.path + '/' + file.name;
43715 controller.add()(o, controller.list);
43722 DirectoryController.$inject = ['APIEndPoint', '$scope'];
43723 return DirectoryController;
43725 directives.DirectoryController = DirectoryController;
43726 })(directives = app.directives || (app.directives = {}));
43727 })(app || (app = {}));
43731 (function (controllers) {
43732 var Execution = (function () {
43733 function Execution(MyModal, $scope) {
43734 this.MyModal = MyModal;
43735 this.$scope = $scope;
43736 this.commandInfoList = [];
43739 Execution.prototype.add = function () {
43740 this.$scope.$broadcast('close');
43741 this.commandInfoList.push(new app.declares.CommandInfo('mrcImageNoiseAdd'));
43743 Execution.prototype.open = function () {
43744 var result = this.MyModal.open('SelectCommand');
43745 console.log(result);
43747 Execution.prototype.remove = function (index, list) {
43748 list.splice(index, 1);
43750 Execution.prototype.close = function () {
43751 console.log("close");
43753 Execution.$inject = ['MyModal', '$scope'];
43756 controllers.Execution = Execution;
43757 })(controllers = app.controllers || (app.controllers = {}));
43758 })(app || (app = {}));
43762 (function (controllers) {
43763 var Workspace = (function () {
43764 function Workspace($scope, APIEndPoint) {
43765 this.$scope = $scope;
43766 this.APIEndPoint = APIEndPoint;
43767 this.directoryList = [];
43768 var controller = this;
43769 var directoryList = this.directoryList;
43771 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
43779 directoryList.push(o);
43781 Workspace.prototype.addDirectory = function (info, directoryList) {
43782 directoryList.push(info);
43784 Workspace.$inject = ['$scope', 'APIEndPoint'];
43787 controllers.Workspace = Workspace;
43788 })(controllers = app.controllers || (app.controllers = {}));
43789 })(app || (app = {}));
43793 (function (controllers) {
43794 var History = (function () {
43795 function History($scope) {
43796 this.page = "History";
43798 History.$inject = ['$scope'];
43801 controllers.History = History;
43802 })(controllers = app.controllers || (app.controllers = {}));
43803 })(app || (app = {}));
43807 (function (controllers) {
43808 var SelectCommand = (function () {
43809 function SelectCommand($scope, APIEndPOint) {
43810 this.APIEndPOint = APIEndPOint;
43811 this.page = "History";
43813 SelectCommand.$inject = ['$scope', 'APIEndPoint'];
43814 return SelectCommand;
43816 controllers.SelectCommand = SelectCommand;
43817 })(controllers = app.controllers || (app.controllers = {}));
43818 })(app || (app = {}));
43822 var appName = 'zephyr';
43823 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
43824 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
43825 $urlRouterProvider.otherwise('/execution');
43826 $locationProvider.html5Mode({
43831 .state('execution', {
43833 templateUrl: 'templates/execution.html',
43834 controller: 'executionController',
43837 .state('workspace', {
43839 templateUrl: 'templates/workspace.html',
43840 controller: 'workspaceController',
43843 .state('history', {
43845 templateUrl: 'templates/history.html',
43846 controller: 'historyController',
43850 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
43851 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
43852 app.zephyr.service('MyModal', app.services.MyModal);
43853 app.zephyr.controller('executionController', app.controllers.Execution);
43854 app.zephyr.controller('workspaceController', app.controllers.Workspace);
43855 app.zephyr.controller('historyController', app.controllers.History);
43856 app.zephyr.controller('commandController', app.directives.CommandController);
43857 app.zephyr.controller('optionController', app.directives.OptionController);
43858 app.zephyr.controller('directoryController', app.directives.DirectoryController);
43859 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
43860 app.zephyr.directive('command', app.directives.Command.Factory());
43861 app.zephyr.directive('option', app.directives.Option.Factory());
43862 app.zephyr.directive('directory', app.directives.Directory.Factory());
43863 })(app || (app = {}));
43868 /***/ function(module, exports) {
43873 (function (declares) {
43874 var CommandInfo = (function () {
43875 function CommandInfo(name) {
43878 return CommandInfo;
43880 declares.CommandInfo = CommandInfo;
43881 })(declares = app.declares || (app.declares = {}));
43882 })(app || (app = {}));
43886 (function (services) {
43887 var APIEndPoint = (function () {
43888 function APIEndPoint($resource, $http) {
43889 this.$resource = $resource;
43890 this.$http = $http;
43892 APIEndPoint.prototype.resource = function (endPoint, data) {
43893 var customAction = {
43899 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
43901 return this.$resource(endPoint, {}, { execute: execute });
43903 APIEndPoint.prototype.getOptionControlFile = function (command) {
43904 var endPoint = '/api/v1/optionControlFile/' + command;
43905 return this.resource(endPoint, {}).get();
43907 APIEndPoint.prototype.getFiles = function (fileId) {
43908 var endPoint = '/api/v1/workspace';
43910 endPoint += '/' + fileId;
43912 return this.resource(endPoint, {}).get();
43914 APIEndPoint.prototype.getDirectories = function () {
43915 var endPoint = '/api/v1/all/workspace/directory';
43916 return this.resource(endPoint, {}).get();
43918 APIEndPoint.prototype.execute = function (data) {
43919 var endPoint = '/api/v1/execution';
43920 var fd = new FormData();
43921 fd.append('data', data);
43922 return this.$http.post(endPoint, fd, {
43923 headers: { 'Content-Type': undefined },
43924 transformRequest: angular.identity
43927 return APIEndPoint;
43929 services.APIEndPoint = APIEndPoint;
43930 })(services = app.services || (app.services = {}));
43931 })(app || (app = {}));
43935 (function (services) {
43936 var MyModal = (function () {
43937 function MyModal($uibModal) {
43938 this.$uibModal = $uibModal;
43939 this.modalOption = {
43946 MyModal.prototype.open = function (modalName) {
43947 if (modalName === 'SelectCommand') {
43948 this.modalOption.templateUrl = 'templates/select-command.html';
43949 this.modalOption.size = 'lg';
43951 return this.$uibModal.open(this.modalOption);
43953 MyModal.prototype.selectCommand = function () {
43954 this.modalOption.templateUrl = 'templates/select-command.html';
43955 this.modalOption.controller = 'selectCommandController';
43956 this.modalOption.size = 'lg';
43957 return this.$uibModal.open(this.modalOption);
43961 services.MyModal = MyModal;
43962 })(services = app.services || (app.services = {}));
43963 })(app || (app = {}));
43967 (function (directives) {
43968 var Command = (function () {
43969 function Command() {
43970 this.restrict = 'E';
43971 this.replace = true;
43973 this.controller = 'commandController';
43974 this.controllerAs = 'ctrl';
43975 this.bindToController = {
43981 this.templateUrl = 'templates/command.html';
43983 Command.Factory = function () {
43984 var directive = function () {
43985 return new Command();
43987 directive.$inject = [];
43992 directives.Command = Command;
43993 var CommandController = (function () {
43994 function CommandController(APIEndPoint, $scope) {
43995 this.APIEndPoint = APIEndPoint;
43996 this.$scope = $scope;
43997 var controller = this;
43999 .getOptionControlFile('mrcImageNoiseAdd')
44001 .then(function (result) {
44002 controller.options = result.info;
44007 .then(function (result) {
44008 controller.dirs = result.info;
44010 this.heading = "[" + this.index + "]: dcdFilePring";
44011 this.isOpen = true;
44012 this.$scope.$on('close', function () {
44013 controller.isOpen = false;
44016 CommandController.prototype.submit = function () {
44018 angular.forEach(this.options, function (option) {
44020 name: option.option,
44023 angular.forEach(option.arg, function (arg) {
44025 if (typeof arg.input === 'object') {
44026 obj.arguments.push(arg.input.name);
44029 obj.arguments.push(arg.input);
44033 if (obj.arguments.length > 0) {
44038 command: this.name,
44039 workspace: this.workspace.fileId,
44043 .execute(JSON.stringify(execObj))
44044 .then(function (result) {
44045 console.log(result);
44048 CommandController.prototype.removeMySelf = function (index) {
44049 this.remove()(index, this.list);
44051 CommandController.prototype.reloadFiles = function () {
44053 var fileId = this.workspace.fileId;
44057 .then(function (result) {
44058 var status = result.status;
44059 if (status === 'success') {
44060 _this.files = result.info;
44063 console.log(result.message);
44067 CommandController.prototype.debug = function () {
44068 console.log(this.files);
44069 console.log(this.files);
44070 console.log(this.workspace);
44072 CommandController.$inject = ['APIEndPoint', '$scope'];
44073 return CommandController;
44075 directives.CommandController = CommandController;
44076 })(directives = app.directives || (app.directives = {}));
44077 })(app || (app = {}));
44081 (function (directives) {
44082 var HeaderMenu = (function () {
44083 function HeaderMenu() {
44084 this.restrict = 'E';
44085 this.replace = true;
44086 this.templateUrl = 'templates/header-menu.html';
44088 HeaderMenu.Factory = function () {
44089 var directive = function () {
44090 return new HeaderMenu();
44096 directives.HeaderMenu = HeaderMenu;
44097 })(directives = app.directives || (app.directives = {}));
44098 })(app || (app = {}));
44102 (function (directives) {
44103 var Option = (function () {
44104 function Option() {
44105 this.restrict = 'E';
44106 this.replace = true;
44107 this.controller = 'optionController';
44108 this.bindToController = {
44113 this.templateUrl = 'templates/option.html';
44114 this.controllerAs = 'ctrl';
44116 Option.Factory = function () {
44117 var directive = function () {
44118 return new Option();
44120 directive.$inject = [];
44125 directives.Option = Option;
44126 var OptionController = (function () {
44127 function OptionController() {
44128 var controller = this;
44129 angular.forEach(controller.info.arg, function (arg) {
44130 if (arg.initialValue) {
44131 if (arg.formType === 'number') {
44132 arg.input = parseInt(arg.initialValue);
44135 arg.input = arg.initialValue;
44140 OptionController.$inject = [];
44141 return OptionController;
44143 directives.OptionController = OptionController;
44144 })(directives = app.directives || (app.directives = {}));
44145 })(app || (app = {}));
44149 (function (directives) {
44150 var Directory = (function () {
44151 function Directory() {
44152 this.restrict = 'E';
44153 this.replace = true;
44154 this.controller = 'directoryController';
44155 this.controllerAs = 'ctrl';
44156 this.bindToController = {
44162 this.templateUrl = 'templates/directory.html';
44164 Directory.Factory = function () {
44165 var directive = function () {
44166 return new Directory();
44172 directives.Directory = Directory;
44173 var DirectoryController = (function () {
44174 function DirectoryController(APIEndPoint, $scope) {
44175 this.APIEndPoint = APIEndPoint;
44176 this.$scope = $scope;
44177 var controller = this;
44179 .getFiles(this.info.fileId)
44181 .then(function (result) {
44182 if (result.status === 'success') {
44183 controller.files = result.info;
44184 angular.forEach(result.info, function (file) {
44185 if (file.fileType === '0') {
44187 if (controller.info.path === '/') {
44188 o.path = '/' + file.name;
44191 o.path = controller.info.path + '/' + file.name;
44193 controller.add()(o, controller.list);
44200 DirectoryController.$inject = ['APIEndPoint', '$scope'];
44201 return DirectoryController;
44203 directives.DirectoryController = DirectoryController;
44204 })(directives = app.directives || (app.directives = {}));
44205 })(app || (app = {}));
44209 (function (controllers) {
44210 var Execution = (function () {
44211 function Execution(MyModal, $scope) {
44212 this.MyModal = MyModal;
44213 this.$scope = $scope;
44214 this.commandInfoList = [];
44217 Execution.prototype.add = function () {
44218 this.$scope.$broadcast('close');
44219 this.commandInfoList.push(new app.declares.CommandInfo('mrcImageNoiseAdd'));
44221 Execution.prototype.open = function () {
44222 var result = this.MyModal.open('SelectCommand');
44223 console.log(result);
44225 Execution.prototype.remove = function (index, list) {
44226 list.splice(index, 1);
44228 Execution.prototype.close = function () {
44229 console.log("close");
44231 Execution.$inject = ['MyModal', '$scope'];
44234 controllers.Execution = Execution;
44235 })(controllers = app.controllers || (app.controllers = {}));
44236 })(app || (app = {}));
44240 (function (controllers) {
44241 var Workspace = (function () {
44242 function Workspace($scope, APIEndPoint) {
44243 this.$scope = $scope;
44244 this.APIEndPoint = APIEndPoint;
44245 this.directoryList = [];
44246 var controller = this;
44247 var directoryList = this.directoryList;
44249 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
44257 directoryList.push(o);
44259 Workspace.prototype.addDirectory = function (info, directoryList) {
44260 directoryList.push(info);
44262 Workspace.$inject = ['$scope', 'APIEndPoint'];
44265 controllers.Workspace = Workspace;
44266 })(controllers = app.controllers || (app.controllers = {}));
44267 })(app || (app = {}));
44271 (function (controllers) {
44272 var History = (function () {
44273 function History($scope) {
44274 this.page = "History";
44276 History.$inject = ['$scope'];
44279 controllers.History = History;
44280 })(controllers = app.controllers || (app.controllers = {}));
44281 })(app || (app = {}));
44285 (function (controllers) {
44286 var SelectCommand = (function () {
44287 function SelectCommand($scope, APIEndPOint) {
44288 this.APIEndPOint = APIEndPOint;
44289 this.page = "History";
44291 SelectCommand.$inject = ['$scope', 'APIEndPoint'];
44292 return SelectCommand;
44294 controllers.SelectCommand = SelectCommand;
44295 })(controllers = app.controllers || (app.controllers = {}));
44296 })(app || (app = {}));
44300 var appName = 'zephyr';
44301 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
44302 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
44303 $urlRouterProvider.otherwise('/execution');
44304 $locationProvider.html5Mode({
44309 .state('execution', {
44311 templateUrl: 'templates/execution.html',
44312 controller: 'executionController',
44315 .state('workspace', {
44317 templateUrl: 'templates/workspace.html',
44318 controller: 'workspaceController',
44321 .state('history', {
44323 templateUrl: 'templates/history.html',
44324 controller: 'historyController',
44328 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
44329 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
44330 app.zephyr.service('MyModal', app.services.MyModal);
44331 app.zephyr.controller('executionController', app.controllers.Execution);
44332 app.zephyr.controller('workspaceController', app.controllers.Workspace);
44333 app.zephyr.controller('historyController', app.controllers.History);
44334 app.zephyr.controller('commandController', app.directives.CommandController);
44335 app.zephyr.controller('optionController', app.directives.OptionController);
44336 app.zephyr.controller('directoryController', app.directives.DirectoryController);
44337 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
44338 app.zephyr.directive('command', app.directives.Command.Factory());
44339 app.zephyr.directive('option', app.directives.Option.Factory());
44340 app.zephyr.directive('directory', app.directives.Directory.Factory());
44341 })(app || (app = {}));
44346 /***/ function(module, exports) {
44351 (function (declares) {
44352 var CommandInfo = (function () {
44353 function CommandInfo(name) {
44356 return CommandInfo;
44358 declares.CommandInfo = CommandInfo;
44359 })(declares = app.declares || (app.declares = {}));
44360 })(app || (app = {}));
44364 (function (services) {
44365 var APIEndPoint = (function () {
44366 function APIEndPoint($resource, $http) {
44367 this.$resource = $resource;
44368 this.$http = $http;
44370 APIEndPoint.prototype.resource = function (endPoint, data) {
44371 var customAction = {
44377 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
44379 return this.$resource(endPoint, {}, { execute: execute });
44381 APIEndPoint.prototype.getOptionControlFile = function (command) {
44382 var endPoint = '/api/v1/optionControlFile/' + command;
44383 return this.resource(endPoint, {}).get();
44385 APIEndPoint.prototype.getFiles = function (fileId) {
44386 var endPoint = '/api/v1/workspace';
44388 endPoint += '/' + fileId;
44390 return this.resource(endPoint, {}).get();
44392 APIEndPoint.prototype.getDirectories = function () {
44393 var endPoint = '/api/v1/all/workspace/directory';
44394 return this.resource(endPoint, {}).get();
44396 APIEndPoint.prototype.execute = function (data) {
44397 var endPoint = '/api/v1/execution';
44398 var fd = new FormData();
44399 fd.append('data', data);
44400 return this.$http.post(endPoint, fd, {
44401 headers: { 'Content-Type': undefined },
44402 transformRequest: angular.identity
44405 return APIEndPoint;
44407 services.APIEndPoint = APIEndPoint;
44408 })(services = app.services || (app.services = {}));
44409 })(app || (app = {}));
44413 (function (services) {
44414 var MyModal = (function () {
44415 function MyModal($uibModal) {
44416 this.$uibModal = $uibModal;
44417 this.modalOption = {
44424 MyModal.prototype.open = function (modalName) {
44425 if (modalName === 'SelectCommand') {
44426 this.modalOption.templateUrl = 'templates/select-command.html';
44427 this.modalOption.size = 'lg';
44429 return this.$uibModal.open(this.modalOption);
44431 MyModal.prototype.selectCommand = function () {
44432 this.modalOption.templateUrl = 'templates/select-command.html';
44433 this.modalOption.controller = 'selectCommandController';
44434 this.modalOption.size = 'lg';
44435 return this.$uibModal.open(this.modalOption);
44439 services.MyModal = MyModal;
44440 })(services = app.services || (app.services = {}));
44441 })(app || (app = {}));
44445 (function (directives) {
44446 var Command = (function () {
44447 function Command() {
44448 this.restrict = 'E';
44449 this.replace = true;
44451 this.controller = 'commandController';
44452 this.controllerAs = 'ctrl';
44453 this.bindToController = {
44459 this.templateUrl = 'templates/command.html';
44461 Command.Factory = function () {
44462 var directive = function () {
44463 return new Command();
44465 directive.$inject = [];
44470 directives.Command = Command;
44471 var CommandController = (function () {
44472 function CommandController(APIEndPoint, $scope) {
44473 this.APIEndPoint = APIEndPoint;
44474 this.$scope = $scope;
44475 var controller = this;
44477 .getOptionControlFile('mrcImageNoiseAdd')
44479 .then(function (result) {
44480 controller.options = result.info;
44485 .then(function (result) {
44486 controller.dirs = result.info;
44488 this.heading = "[" + this.index + "]: dcdFilePring";
44489 this.isOpen = true;
44490 this.$scope.$on('close', function () {
44491 controller.isOpen = false;
44494 CommandController.prototype.submit = function () {
44496 angular.forEach(this.options, function (option) {
44498 name: option.option,
44501 angular.forEach(option.arg, function (arg) {
44503 if (typeof arg.input === 'object') {
44504 obj.arguments.push(arg.input.name);
44507 obj.arguments.push(arg.input);
44511 if (obj.arguments.length > 0) {
44516 command: this.name,
44517 workspace: this.workspace.fileId,
44521 .execute(JSON.stringify(execObj))
44522 .then(function (result) {
44523 console.log(result);
44526 CommandController.prototype.removeMySelf = function (index) {
44527 this.remove()(index, this.list);
44529 CommandController.prototype.reloadFiles = function () {
44531 var fileId = this.workspace.fileId;
44535 .then(function (result) {
44536 var status = result.status;
44537 if (status === 'success') {
44538 _this.files = result.info;
44541 console.log(result.message);
44545 CommandController.prototype.debug = function () {
44546 console.log(this.files);
44547 console.log(this.files);
44548 console.log(this.workspace);
44550 CommandController.$inject = ['APIEndPoint', '$scope'];
44551 return CommandController;
44553 directives.CommandController = CommandController;
44554 })(directives = app.directives || (app.directives = {}));
44555 })(app || (app = {}));
44559 (function (directives) {
44560 var HeaderMenu = (function () {
44561 function HeaderMenu() {
44562 this.restrict = 'E';
44563 this.replace = true;
44564 this.templateUrl = 'templates/header-menu.html';
44566 HeaderMenu.Factory = function () {
44567 var directive = function () {
44568 return new HeaderMenu();
44574 directives.HeaderMenu = HeaderMenu;
44575 })(directives = app.directives || (app.directives = {}));
44576 })(app || (app = {}));
44580 (function (directives) {
44581 var Option = (function () {
44582 function Option() {
44583 this.restrict = 'E';
44584 this.replace = true;
44585 this.controller = 'optionController';
44586 this.bindToController = {
44591 this.templateUrl = 'templates/option.html';
44592 this.controllerAs = 'ctrl';
44594 Option.Factory = function () {
44595 var directive = function () {
44596 return new Option();
44598 directive.$inject = [];
44603 directives.Option = Option;
44604 var OptionController = (function () {
44605 function OptionController() {
44606 var controller = this;
44607 angular.forEach(controller.info.arg, function (arg) {
44608 if (arg.initialValue) {
44609 if (arg.formType === 'number') {
44610 arg.input = parseInt(arg.initialValue);
44613 arg.input = arg.initialValue;
44618 OptionController.$inject = [];
44619 return OptionController;
44621 directives.OptionController = OptionController;
44622 })(directives = app.directives || (app.directives = {}));
44623 })(app || (app = {}));
44627 (function (directives) {
44628 var Directory = (function () {
44629 function Directory() {
44630 this.restrict = 'E';
44631 this.replace = true;
44632 this.controller = 'directoryController';
44633 this.controllerAs = 'ctrl';
44634 this.bindToController = {
44640 this.templateUrl = 'templates/directory.html';
44642 Directory.Factory = function () {
44643 var directive = function () {
44644 return new Directory();
44650 directives.Directory = Directory;
44651 var DirectoryController = (function () {
44652 function DirectoryController(APIEndPoint, $scope) {
44653 this.APIEndPoint = APIEndPoint;
44654 this.$scope = $scope;
44655 var controller = this;
44657 .getFiles(this.info.fileId)
44659 .then(function (result) {
44660 if (result.status === 'success') {
44661 controller.files = result.info;
44662 angular.forEach(result.info, function (file) {
44663 if (file.fileType === '0') {
44665 if (controller.info.path === '/') {
44666 o.path = '/' + file.name;
44669 o.path = controller.info.path + '/' + file.name;
44671 controller.add()(o, controller.list);
44678 DirectoryController.$inject = ['APIEndPoint', '$scope'];
44679 return DirectoryController;
44681 directives.DirectoryController = DirectoryController;
44682 })(directives = app.directives || (app.directives = {}));
44683 })(app || (app = {}));
44687 (function (controllers) {
44688 var Execution = (function () {
44689 function Execution(MyModal, $scope) {
44690 this.MyModal = MyModal;
44691 this.$scope = $scope;
44692 this.commandInfoList = [];
44695 Execution.prototype.add = function () {
44696 this.$scope.$broadcast('close');
44697 this.commandInfoList.push(new app.declares.CommandInfo('mrcImageNoiseAdd'));
44699 Execution.prototype.open = function () {
44700 var result = this.MyModal.open('SelectCommand');
44701 console.log(result);
44703 Execution.prototype.remove = function (index, list) {
44704 list.splice(index, 1);
44706 Execution.prototype.close = function () {
44707 console.log("close");
44709 Execution.$inject = ['MyModal', '$scope'];
44712 controllers.Execution = Execution;
44713 })(controllers = app.controllers || (app.controllers = {}));
44714 })(app || (app = {}));
44718 (function (controllers) {
44719 var Workspace = (function () {
44720 function Workspace($scope, APIEndPoint) {
44721 this.$scope = $scope;
44722 this.APIEndPoint = APIEndPoint;
44723 this.directoryList = [];
44724 var controller = this;
44725 var directoryList = this.directoryList;
44727 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
44735 directoryList.push(o);
44737 Workspace.prototype.addDirectory = function (info, directoryList) {
44738 directoryList.push(info);
44740 Workspace.$inject = ['$scope', 'APIEndPoint'];
44743 controllers.Workspace = Workspace;
44744 })(controllers = app.controllers || (app.controllers = {}));
44745 })(app || (app = {}));
44749 (function (controllers) {
44750 var History = (function () {
44751 function History($scope) {
44752 this.page = "History";
44754 History.$inject = ['$scope'];
44757 controllers.History = History;
44758 })(controllers = app.controllers || (app.controllers = {}));
44759 })(app || (app = {}));
44763 (function (controllers) {
44764 var SelectCommand = (function () {
44765 function SelectCommand($scope, APIEndPOint) {
44766 this.APIEndPOint = APIEndPOint;
44767 this.page = "History";
44769 SelectCommand.$inject = ['$scope', 'APIEndPoint'];
44770 return SelectCommand;
44772 controllers.SelectCommand = SelectCommand;
44773 })(controllers = app.controllers || (app.controllers = {}));
44774 })(app || (app = {}));
44778 var appName = 'zephyr';
44779 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
44780 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
44781 $urlRouterProvider.otherwise('/execution');
44782 $locationProvider.html5Mode({
44787 .state('execution', {
44789 templateUrl: 'templates/execution.html',
44790 controller: 'executionController',
44793 .state('workspace', {
44795 templateUrl: 'templates/workspace.html',
44796 controller: 'workspaceController',
44799 .state('history', {
44801 templateUrl: 'templates/history.html',
44802 controller: 'historyController',
44806 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
44807 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
44808 app.zephyr.service('MyModal', app.services.MyModal);
44809 app.zephyr.controller('executionController', app.controllers.Execution);
44810 app.zephyr.controller('workspaceController', app.controllers.Workspace);
44811 app.zephyr.controller('historyController', app.controllers.History);
44812 app.zephyr.controller('commandController', app.directives.CommandController);
44813 app.zephyr.controller('optionController', app.directives.OptionController);
44814 app.zephyr.controller('directoryController', app.directives.DirectoryController);
44815 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
44816 app.zephyr.directive('command', app.directives.Command.Factory());
44817 app.zephyr.directive('option', app.directives.Option.Factory());
44818 app.zephyr.directive('directory', app.directives.Directory.Factory());
44819 })(app || (app = {}));
44824 /***/ function(module, exports) {
44829 (function (declares) {
44830 var CommandInfo = (function () {
44831 function CommandInfo(name) {
44834 return CommandInfo;
44836 declares.CommandInfo = CommandInfo;
44837 })(declares = app.declares || (app.declares = {}));
44838 })(app || (app = {}));
44842 (function (services) {
44843 var APIEndPoint = (function () {
44844 function APIEndPoint($resource, $http) {
44845 this.$resource = $resource;
44846 this.$http = $http;
44848 APIEndPoint.prototype.resource = function (endPoint, data) {
44849 var customAction = {
44855 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
44857 return this.$resource(endPoint, {}, { execute: execute });
44859 APIEndPoint.prototype.getOptionControlFile = function (command) {
44860 var endPoint = '/api/v1/optionControlFile/' + command;
44861 return this.resource(endPoint, {}).get();
44863 APIEndPoint.prototype.getFiles = function (fileId) {
44864 var endPoint = '/api/v1/workspace';
44866 endPoint += '/' + fileId;
44868 return this.resource(endPoint, {}).get();
44870 APIEndPoint.prototype.getDirectories = function () {
44871 var endPoint = '/api/v1/all/workspace/directory';
44872 return this.resource(endPoint, {}).get();
44874 APIEndPoint.prototype.execute = function (data) {
44875 var endPoint = '/api/v1/execution';
44876 var fd = new FormData();
44877 fd.append('data', data);
44878 return this.$http.post(endPoint, fd, {
44879 headers: { 'Content-Type': undefined },
44880 transformRequest: angular.identity
44883 return APIEndPoint;
44885 services.APIEndPoint = APIEndPoint;
44886 })(services = app.services || (app.services = {}));
44887 })(app || (app = {}));
44891 (function (services) {
44892 var MyModal = (function () {
44893 function MyModal($uibModal) {
44894 this.$uibModal = $uibModal;
44895 this.modalOption = {
44902 MyModal.prototype.open = function (modalName) {
44903 if (modalName === 'SelectCommand') {
44904 this.modalOption.templateUrl = 'templates/select-command.html';
44905 this.modalOption.size = 'lg';
44907 return this.$uibModal.open(this.modalOption);
44909 MyModal.prototype.selectCommand = function () {
44910 this.modalOption.templateUrl = 'templates/select-command.html';
44911 this.modalOption.controller = 'selectCommandController';
44912 this.modalOption.size = 'lg';
44913 return this.$uibModal.open(this.modalOption);
44917 services.MyModal = MyModal;
44918 })(services = app.services || (app.services = {}));
44919 })(app || (app = {}));
44923 (function (directives) {
44924 var Command = (function () {
44925 function Command() {
44926 this.restrict = 'E';
44927 this.replace = true;
44929 this.controller = 'commandController';
44930 this.controllerAs = 'ctrl';
44931 this.bindToController = {
44937 this.templateUrl = 'templates/command.html';
44939 Command.Factory = function () {
44940 var directive = function () {
44941 return new Command();
44943 directive.$inject = [];
44948 directives.Command = Command;
44949 var CommandController = (function () {
44950 function CommandController(APIEndPoint, $scope) {
44951 this.APIEndPoint = APIEndPoint;
44952 this.$scope = $scope;
44953 var controller = this;
44955 .getOptionControlFile('mrcImageNoiseAdd')
44957 .then(function (result) {
44958 controller.options = result.info;
44963 .then(function (result) {
44964 controller.dirs = result.info;
44966 this.heading = "[" + this.index + "]: dcdFilePring";
44967 this.isOpen = true;
44968 this.$scope.$on('close', function () {
44969 controller.isOpen = false;
44972 CommandController.prototype.submit = function () {
44974 angular.forEach(this.options, function (option) {
44976 name: option.option,
44979 angular.forEach(option.arg, function (arg) {
44981 if (typeof arg.input === 'object') {
44982 obj.arguments.push(arg.input.name);
44985 obj.arguments.push(arg.input);
44989 if (obj.arguments.length > 0) {
44994 command: this.name,
44995 workspace: this.workspace.fileId,
44999 .execute(JSON.stringify(execObj))
45000 .then(function (result) {
45001 console.log(result);
45004 CommandController.prototype.removeMySelf = function (index) {
45005 this.remove()(index, this.list);
45007 CommandController.prototype.reloadFiles = function () {
45009 var fileId = this.workspace.fileId;
45013 .then(function (result) {
45014 var status = result.status;
45015 if (status === 'success') {
45016 _this.files = result.info;
45019 console.log(result.message);
45023 CommandController.prototype.debug = function () {
45024 console.log(this.files);
45025 console.log(this.files);
45026 console.log(this.workspace);
45028 CommandController.$inject = ['APIEndPoint', '$scope'];
45029 return CommandController;
45031 directives.CommandController = CommandController;
45032 })(directives = app.directives || (app.directives = {}));
45033 })(app || (app = {}));
45037 (function (directives) {
45038 var HeaderMenu = (function () {
45039 function HeaderMenu() {
45040 this.restrict = 'E';
45041 this.replace = true;
45042 this.templateUrl = 'templates/header-menu.html';
45044 HeaderMenu.Factory = function () {
45045 var directive = function () {
45046 return new HeaderMenu();
45052 directives.HeaderMenu = HeaderMenu;
45053 })(directives = app.directives || (app.directives = {}));
45054 })(app || (app = {}));
45058 (function (directives) {
45059 var Option = (function () {
45060 function Option() {
45061 this.restrict = 'E';
45062 this.replace = true;
45063 this.controller = 'optionController';
45064 this.bindToController = {
45069 this.templateUrl = 'templates/option.html';
45070 this.controllerAs = 'ctrl';
45072 Option.Factory = function () {
45073 var directive = function () {
45074 return new Option();
45076 directive.$inject = [];
45081 directives.Option = Option;
45082 var OptionController = (function () {
45083 function OptionController() {
45084 var controller = this;
45085 angular.forEach(controller.info.arg, function (arg) {
45086 if (arg.initialValue) {
45087 if (arg.formType === 'number') {
45088 arg.input = parseInt(arg.initialValue);
45091 arg.input = arg.initialValue;
45096 OptionController.$inject = [];
45097 return OptionController;
45099 directives.OptionController = OptionController;
45100 })(directives = app.directives || (app.directives = {}));
45101 })(app || (app = {}));
45105 (function (directives) {
45106 var Directory = (function () {
45107 function Directory() {
45108 this.restrict = 'E';
45109 this.replace = true;
45110 this.controller = 'directoryController';
45111 this.controllerAs = 'ctrl';
45112 this.bindToController = {
45118 this.templateUrl = 'templates/directory.html';
45120 Directory.Factory = function () {
45121 var directive = function () {
45122 return new Directory();
45128 directives.Directory = Directory;
45129 var DirectoryController = (function () {
45130 function DirectoryController(APIEndPoint, $scope) {
45131 this.APIEndPoint = APIEndPoint;
45132 this.$scope = $scope;
45133 var controller = this;
45135 .getFiles(this.info.fileId)
45137 .then(function (result) {
45138 if (result.status === 'success') {
45139 controller.files = result.info;
45140 angular.forEach(result.info, function (file) {
45141 if (file.fileType === '0') {
45143 if (controller.info.path === '/') {
45144 o.path = '/' + file.name;
45147 o.path = controller.info.path + '/' + file.name;
45149 controller.add()(o, controller.list);
45156 DirectoryController.$inject = ['APIEndPoint', '$scope'];
45157 return DirectoryController;
45159 directives.DirectoryController = DirectoryController;
45160 })(directives = app.directives || (app.directives = {}));
45161 })(app || (app = {}));
45165 (function (controllers) {
45166 var Execution = (function () {
45167 function Execution(MyModal, $scope) {
45168 this.MyModal = MyModal;
45169 this.$scope = $scope;
45170 this.commandInfoList = [];
45173 Execution.prototype.add = function () {
45174 this.$scope.$broadcast('close');
45175 this.commandInfoList.push(new app.declares.CommandInfo('mrcImageNoiseAdd'));
45177 Execution.prototype.open = function () {
45178 var result = this.MyModal.open('SelectCommand');
45179 console.log(result);
45181 Execution.prototype.remove = function (index, list) {
45182 list.splice(index, 1);
45184 Execution.prototype.close = function () {
45185 console.log("close");
45187 Execution.$inject = ['MyModal', '$scope'];
45190 controllers.Execution = Execution;
45191 })(controllers = app.controllers || (app.controllers = {}));
45192 })(app || (app = {}));
45196 (function (controllers) {
45197 var Workspace = (function () {
45198 function Workspace($scope, APIEndPoint) {
45199 this.$scope = $scope;
45200 this.APIEndPoint = APIEndPoint;
45201 this.directoryList = [];
45202 var controller = this;
45203 var directoryList = this.directoryList;
45205 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
45213 directoryList.push(o);
45215 Workspace.prototype.addDirectory = function (info, directoryList) {
45216 directoryList.push(info);
45218 Workspace.$inject = ['$scope', 'APIEndPoint'];
45221 controllers.Workspace = Workspace;
45222 })(controllers = app.controllers || (app.controllers = {}));
45223 })(app || (app = {}));
45227 (function (controllers) {
45228 var History = (function () {
45229 function History($scope) {
45230 this.page = "History";
45232 History.$inject = ['$scope'];
45235 controllers.History = History;
45236 })(controllers = app.controllers || (app.controllers = {}));
45237 })(app || (app = {}));
45241 (function (controllers) {
45242 var SelectCommand = (function () {
45243 function SelectCommand($scope, APIEndPOint) {
45244 this.APIEndPOint = APIEndPOint;
45245 this.page = "History";
45247 SelectCommand.$inject = ['$scope', 'APIEndPoint'];
45248 return SelectCommand;
45250 controllers.SelectCommand = SelectCommand;
45251 })(controllers = app.controllers || (app.controllers = {}));
45252 })(app || (app = {}));
45256 var appName = 'zephyr';
45257 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
45258 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
45259 $urlRouterProvider.otherwise('/execution');
45260 $locationProvider.html5Mode({
45265 .state('execution', {
45267 templateUrl: 'templates/execution.html',
45268 controller: 'executionController',
45271 .state('workspace', {
45273 templateUrl: 'templates/workspace.html',
45274 controller: 'workspaceController',
45277 .state('history', {
45279 templateUrl: 'templates/history.html',
45280 controller: 'historyController',
45284 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
45285 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
45286 app.zephyr.service('MyModal', app.services.MyModal);
45287 app.zephyr.controller('executionController', app.controllers.Execution);
45288 app.zephyr.controller('workspaceController', app.controllers.Workspace);
45289 app.zephyr.controller('historyController', app.controllers.History);
45290 app.zephyr.controller('commandController', app.directives.CommandController);
45291 app.zephyr.controller('optionController', app.directives.OptionController);
45292 app.zephyr.controller('directoryController', app.directives.DirectoryController);
45293 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
45294 app.zephyr.directive('command', app.directives.Command.Factory());
45295 app.zephyr.directive('option', app.directives.Option.Factory());
45296 app.zephyr.directive('directory', app.directives.Directory.Factory());
45297 })(app || (app = {}));
45302 /***/ function(module, exports) {
45307 (function (declares) {
45308 var CommandInfo = (function () {
45309 function CommandInfo(name) {
45312 return CommandInfo;
45314 declares.CommandInfo = CommandInfo;
45315 })(declares = app.declares || (app.declares = {}));
45316 })(app || (app = {}));
45320 (function (services) {
45321 var APIEndPoint = (function () {
45322 function APIEndPoint($resource, $http) {
45323 this.$resource = $resource;
45324 this.$http = $http;
45326 APIEndPoint.prototype.resource = function (endPoint, data) {
45327 var customAction = {
45333 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
45335 return this.$resource(endPoint, {}, { execute: execute });
45337 APIEndPoint.prototype.getOptionControlFile = function (command) {
45338 var endPoint = '/api/v1/optionControlFile/' + command;
45339 return this.resource(endPoint, {}).get();
45341 APIEndPoint.prototype.getFiles = function (fileId) {
45342 var endPoint = '/api/v1/workspace';
45344 endPoint += '/' + fileId;
45346 return this.resource(endPoint, {}).get();
45348 APIEndPoint.prototype.getDirectories = function () {
45349 var endPoint = '/api/v1/all/workspace/directory';
45350 return this.resource(endPoint, {}).get();
45352 APIEndPoint.prototype.execute = function (data) {
45353 var endPoint = '/api/v1/execution';
45354 var fd = new FormData();
45355 fd.append('data', data);
45356 return this.$http.post(endPoint, fd, {
45357 headers: { 'Content-Type': undefined },
45358 transformRequest: angular.identity
45361 return APIEndPoint;
45363 services.APIEndPoint = APIEndPoint;
45364 })(services = app.services || (app.services = {}));
45365 })(app || (app = {}));
45369 (function (services) {
45370 var MyModal = (function () {
45371 function MyModal($uibModal) {
45372 this.$uibModal = $uibModal;
45373 this.modalOption = {
45380 MyModal.prototype.open = function (modalName) {
45381 if (modalName === 'SelectCommand') {
45382 this.modalOption.templateUrl = 'templates/select-command.html';
45383 this.modalOption.size = 'lg';
45385 return this.$uibModal.open(this.modalOption);
45387 MyModal.prototype.selectCommand = function () {
45388 this.modalOption.templateUrl = 'templates/select-command.html';
45389 this.modalOption.controller = 'selectCommandController';
45390 this.modalOption.size = 'lg';
45391 return this.$uibModal.open(this.modalOption);
45395 services.MyModal = MyModal;
45396 })(services = app.services || (app.services = {}));
45397 })(app || (app = {}));
45401 (function (directives) {
45402 var Command = (function () {
45403 function Command() {
45404 this.restrict = 'E';
45405 this.replace = true;
45407 this.controller = 'commandController';
45408 this.controllerAs = 'ctrl';
45409 this.bindToController = {
45415 this.templateUrl = 'templates/command.html';
45417 Command.Factory = function () {
45418 var directive = function () {
45419 return new Command();
45421 directive.$inject = [];
45426 directives.Command = Command;
45427 var CommandController = (function () {
45428 function CommandController(APIEndPoint, $scope) {
45429 this.APIEndPoint = APIEndPoint;
45430 this.$scope = $scope;
45431 var controller = this;
45433 .getOptionControlFile('mrcImageNoiseAdd')
45435 .then(function (result) {
45436 controller.options = result.info;
45441 .then(function (result) {
45442 controller.dirs = result.info;
45444 this.heading = "[" + this.index + "]: dcdFilePring";
45445 this.isOpen = true;
45446 this.$scope.$on('close', function () {
45447 controller.isOpen = false;
45450 CommandController.prototype.submit = function () {
45452 angular.forEach(this.options, function (option) {
45454 name: option.option,
45457 angular.forEach(option.arg, function (arg) {
45459 if (typeof arg.input === 'object') {
45460 obj.arguments.push(arg.input.name);
45463 obj.arguments.push(arg.input);
45467 if (obj.arguments.length > 0) {
45472 command: this.name,
45473 workspace: this.workspace.fileId,
45477 .execute(JSON.stringify(execObj))
45478 .then(function (result) {
45479 console.log(result);
45482 CommandController.prototype.removeMySelf = function (index) {
45483 this.remove()(index, this.list);
45485 CommandController.prototype.reloadFiles = function () {
45487 var fileId = this.workspace.fileId;
45491 .then(function (result) {
45492 var status = result.status;
45493 if (status === 'success') {
45494 _this.files = result.info;
45497 console.log(result.message);
45501 CommandController.prototype.debug = function () {
45502 console.log(this.files);
45503 console.log(this.files);
45504 console.log(this.workspace);
45506 CommandController.$inject = ['APIEndPoint', '$scope'];
45507 return CommandController;
45509 directives.CommandController = CommandController;
45510 })(directives = app.directives || (app.directives = {}));
45511 })(app || (app = {}));
45515 (function (directives) {
45516 var HeaderMenu = (function () {
45517 function HeaderMenu() {
45518 this.restrict = 'E';
45519 this.replace = true;
45520 this.templateUrl = 'templates/header-menu.html';
45522 HeaderMenu.Factory = function () {
45523 var directive = function () {
45524 return new HeaderMenu();
45530 directives.HeaderMenu = HeaderMenu;
45531 })(directives = app.directives || (app.directives = {}));
45532 })(app || (app = {}));
45536 (function (directives) {
45537 var Option = (function () {
45538 function Option() {
45539 this.restrict = 'E';
45540 this.replace = true;
45541 this.controller = 'optionController';
45542 this.bindToController = {
45547 this.templateUrl = 'templates/option.html';
45548 this.controllerAs = 'ctrl';
45550 Option.Factory = function () {
45551 var directive = function () {
45552 return new Option();
45554 directive.$inject = [];
45559 directives.Option = Option;
45560 var OptionController = (function () {
45561 function OptionController() {
45562 var controller = this;
45563 angular.forEach(controller.info.arg, function (arg) {
45564 if (arg.initialValue) {
45565 if (arg.formType === 'number') {
45566 arg.input = parseInt(arg.initialValue);
45569 arg.input = arg.initialValue;
45574 OptionController.$inject = [];
45575 return OptionController;
45577 directives.OptionController = OptionController;
45578 })(directives = app.directives || (app.directives = {}));
45579 })(app || (app = {}));
45583 (function (directives) {
45584 var Directory = (function () {
45585 function Directory() {
45586 this.restrict = 'E';
45587 this.replace = true;
45588 this.controller = 'directoryController';
45589 this.controllerAs = 'ctrl';
45590 this.bindToController = {
45596 this.templateUrl = 'templates/directory.html';
45598 Directory.Factory = function () {
45599 var directive = function () {
45600 return new Directory();
45606 directives.Directory = Directory;
45607 var DirectoryController = (function () {
45608 function DirectoryController(APIEndPoint, $scope) {
45609 this.APIEndPoint = APIEndPoint;
45610 this.$scope = $scope;
45611 var controller = this;
45613 .getFiles(this.info.fileId)
45615 .then(function (result) {
45616 if (result.status === 'success') {
45617 controller.files = result.info;
45618 angular.forEach(result.info, function (file) {
45619 if (file.fileType === '0') {
45621 if (controller.info.path === '/') {
45622 o.path = '/' + file.name;
45625 o.path = controller.info.path + '/' + file.name;
45627 controller.add()(o, controller.list);
45634 DirectoryController.$inject = ['APIEndPoint', '$scope'];
45635 return DirectoryController;
45637 directives.DirectoryController = DirectoryController;
45638 })(directives = app.directives || (app.directives = {}));
45639 })(app || (app = {}));
45643 (function (controllers) {
45644 var Execution = (function () {
45645 function Execution(MyModal, $scope) {
45646 this.MyModal = MyModal;
45647 this.$scope = $scope;
45648 this.commandInfoList = [];
45651 Execution.prototype.add = function () {
45652 this.$scope.$broadcast('close');
45653 this.commandInfoList.push(new app.declares.CommandInfo('mrcImageNoiseAdd'));
45655 Execution.prototype.open = function () {
45656 var result = this.MyModal.open('SelectCommand');
45657 console.log(result);
45659 Execution.prototype.remove = function (index, list) {
45660 list.splice(index, 1);
45662 Execution.prototype.close = function () {
45663 console.log("close");
45665 Execution.$inject = ['MyModal', '$scope'];
45668 controllers.Execution = Execution;
45669 })(controllers = app.controllers || (app.controllers = {}));
45670 })(app || (app = {}));
45674 (function (controllers) {
45675 var Workspace = (function () {
45676 function Workspace($scope, APIEndPoint) {
45677 this.$scope = $scope;
45678 this.APIEndPoint = APIEndPoint;
45679 this.directoryList = [];
45680 var controller = this;
45681 var directoryList = this.directoryList;
45683 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
45691 directoryList.push(o);
45693 Workspace.prototype.addDirectory = function (info, directoryList) {
45694 directoryList.push(info);
45696 Workspace.$inject = ['$scope', 'APIEndPoint'];
45699 controllers.Workspace = Workspace;
45700 })(controllers = app.controllers || (app.controllers = {}));
45701 })(app || (app = {}));
45705 (function (controllers) {
45706 var History = (function () {
45707 function History($scope) {
45708 this.page = "History";
45710 History.$inject = ['$scope'];
45713 controllers.History = History;
45714 })(controllers = app.controllers || (app.controllers = {}));
45715 })(app || (app = {}));
45719 (function (controllers) {
45720 var SelectCommand = (function () {
45721 function SelectCommand($scope, APIEndPOint) {
45722 this.APIEndPOint = APIEndPOint;
45723 this.page = "History";
45725 SelectCommand.$inject = ['$scope', 'APIEndPoint'];
45726 return SelectCommand;
45728 controllers.SelectCommand = SelectCommand;
45729 })(controllers = app.controllers || (app.controllers = {}));
45730 })(app || (app = {}));
45734 var appName = 'zephyr';
45735 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
45736 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
45737 $urlRouterProvider.otherwise('/execution');
45738 $locationProvider.html5Mode({
45743 .state('execution', {
45745 templateUrl: 'templates/execution.html',
45746 controller: 'executionController',
45749 .state('workspace', {
45751 templateUrl: 'templates/workspace.html',
45752 controller: 'workspaceController',
45755 .state('history', {
45757 templateUrl: 'templates/history.html',
45758 controller: 'historyController',
45762 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
45763 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
45764 app.zephyr.service('MyModal', app.services.MyModal);
45765 app.zephyr.controller('executionController', app.controllers.Execution);
45766 app.zephyr.controller('workspaceController', app.controllers.Workspace);
45767 app.zephyr.controller('historyController', app.controllers.History);
45768 app.zephyr.controller('commandController', app.directives.CommandController);
45769 app.zephyr.controller('optionController', app.directives.OptionController);
45770 app.zephyr.controller('directoryController', app.directives.DirectoryController);
45771 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
45772 app.zephyr.directive('command', app.directives.Command.Factory());
45773 app.zephyr.directive('option', app.directives.Option.Factory());
45774 app.zephyr.directive('directory', app.directives.Directory.Factory());
45775 })(app || (app = {}));
45780 /***/ function(module, exports) {
45785 (function (declares) {
45786 var CommandInfo = (function () {
45787 function CommandInfo(name) {
45790 return CommandInfo;
45792 declares.CommandInfo = CommandInfo;
45793 })(declares = app.declares || (app.declares = {}));
45794 })(app || (app = {}));
45798 (function (services) {
45799 var APIEndPoint = (function () {
45800 function APIEndPoint($resource, $http) {
45801 this.$resource = $resource;
45802 this.$http = $http;
45804 APIEndPoint.prototype.resource = function (endPoint, data) {
45805 var customAction = {
45811 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
45813 return this.$resource(endPoint, {}, { execute: execute });
45815 APIEndPoint.prototype.getOptionControlFile = function (command) {
45816 var endPoint = '/api/v1/optionControlFile/' + command;
45817 return this.resource(endPoint, {}).get();
45819 APIEndPoint.prototype.getFiles = function (fileId) {
45820 var endPoint = '/api/v1/workspace';
45822 endPoint += '/' + fileId;
45824 return this.resource(endPoint, {}).get();
45826 APIEndPoint.prototype.getDirectories = function () {
45827 var endPoint = '/api/v1/all/workspace/directory';
45828 return this.resource(endPoint, {}).get();
45830 APIEndPoint.prototype.execute = function (data) {
45831 var endPoint = '/api/v1/execution';
45832 var fd = new FormData();
45833 fd.append('data', data);
45834 return this.$http.post(endPoint, fd, {
45835 headers: { 'Content-Type': undefined },
45836 transformRequest: angular.identity
45839 return APIEndPoint;
45841 services.APIEndPoint = APIEndPoint;
45842 })(services = app.services || (app.services = {}));
45843 })(app || (app = {}));
45847 (function (services) {
45848 var MyModal = (function () {
45849 function MyModal($uibModal) {
45850 this.$uibModal = $uibModal;
45851 this.modalOption = {
45858 MyModal.prototype.open = function (modalName) {
45859 if (modalName === 'SelectCommand') {
45860 this.modalOption.templateUrl = 'templates/select-command.html';
45861 this.modalOption.size = 'lg';
45863 return this.$uibModal.open(this.modalOption);
45865 MyModal.prototype.selectCommand = function () {
45866 this.modalOption.templateUrl = 'templates/select-command.html';
45867 this.modalOption.controller = 'selectCommandController';
45868 this.modalOption.size = 'lg';
45869 return this.$uibModal.open(this.modalOption);
45873 services.MyModal = MyModal;
45874 })(services = app.services || (app.services = {}));
45875 })(app || (app = {}));
45879 (function (directives) {
45880 var Command = (function () {
45881 function Command() {
45882 this.restrict = 'E';
45883 this.replace = true;
45885 this.controller = 'commandController';
45886 this.controllerAs = 'ctrl';
45887 this.bindToController = {
45893 this.templateUrl = 'templates/command.html';
45895 Command.Factory = function () {
45896 var directive = function () {
45897 return new Command();
45899 directive.$inject = [];
45904 directives.Command = Command;
45905 var CommandController = (function () {
45906 function CommandController(APIEndPoint, $scope) {
45907 this.APIEndPoint = APIEndPoint;
45908 this.$scope = $scope;
45909 var controller = this;
45911 .getOptionControlFile('mrcImageNoiseAdd')
45913 .then(function (result) {
45914 controller.options = result.info;
45919 .then(function (result) {
45920 controller.dirs = result.info;
45922 this.heading = "[" + this.index + "]: dcdFilePring";
45923 this.isOpen = true;
45924 this.$scope.$on('close', function () {
45925 controller.isOpen = false;
45928 CommandController.prototype.submit = function () {
45930 angular.forEach(this.options, function (option) {
45932 name: option.option,
45935 angular.forEach(option.arg, function (arg) {
45937 if (typeof arg.input === 'object') {
45938 obj.arguments.push(arg.input.name);
45941 obj.arguments.push(arg.input);
45945 if (obj.arguments.length > 0) {
45950 command: this.name,
45951 workspace: this.workspace.fileId,
45955 .execute(JSON.stringify(execObj))
45956 .then(function (result) {
45957 console.log(result);
45960 CommandController.prototype.removeMySelf = function (index) {
45961 this.remove()(index, this.list);
45963 CommandController.prototype.reloadFiles = function () {
45965 var fileId = this.workspace.fileId;
45969 .then(function (result) {
45970 var status = result.status;
45971 if (status === 'success') {
45972 _this.files = result.info;
45975 console.log(result.message);
45979 CommandController.prototype.debug = function () {
45980 console.log(this.files);
45981 console.log(this.files);
45982 console.log(this.workspace);
45984 CommandController.$inject = ['APIEndPoint', '$scope'];
45985 return CommandController;
45987 directives.CommandController = CommandController;
45988 })(directives = app.directives || (app.directives = {}));
45989 })(app || (app = {}));
45993 (function (directives) {
45994 var HeaderMenu = (function () {
45995 function HeaderMenu() {
45996 this.restrict = 'E';
45997 this.replace = true;
45998 this.templateUrl = 'templates/header-menu.html';
46000 HeaderMenu.Factory = function () {
46001 var directive = function () {
46002 return new HeaderMenu();
46008 directives.HeaderMenu = HeaderMenu;
46009 })(directives = app.directives || (app.directives = {}));
46010 })(app || (app = {}));
46014 (function (directives) {
46015 var Option = (function () {
46016 function Option() {
46017 this.restrict = 'E';
46018 this.replace = true;
46019 this.controller = 'optionController';
46020 this.bindToController = {
46025 this.templateUrl = 'templates/option.html';
46026 this.controllerAs = 'ctrl';
46028 Option.Factory = function () {
46029 var directive = function () {
46030 return new Option();
46032 directive.$inject = [];
46037 directives.Option = Option;
46038 var OptionController = (function () {
46039 function OptionController() {
46040 var controller = this;
46041 angular.forEach(controller.info.arg, function (arg) {
46042 if (arg.initialValue) {
46043 if (arg.formType === 'number') {
46044 arg.input = parseInt(arg.initialValue);
46047 arg.input = arg.initialValue;
46052 OptionController.$inject = [];
46053 return OptionController;
46055 directives.OptionController = OptionController;
46056 })(directives = app.directives || (app.directives = {}));
46057 })(app || (app = {}));
46061 (function (directives) {
46062 var Directory = (function () {
46063 function Directory() {
46064 this.restrict = 'E';
46065 this.replace = true;
46066 this.controller = 'directoryController';
46067 this.controllerAs = 'ctrl';
46068 this.bindToController = {
46074 this.templateUrl = 'templates/directory.html';
46076 Directory.Factory = function () {
46077 var directive = function () {
46078 return new Directory();
46084 directives.Directory = Directory;
46085 var DirectoryController = (function () {
46086 function DirectoryController(APIEndPoint, $scope) {
46087 this.APIEndPoint = APIEndPoint;
46088 this.$scope = $scope;
46089 var controller = this;
46091 .getFiles(this.info.fileId)
46093 .then(function (result) {
46094 if (result.status === 'success') {
46095 controller.files = result.info;
46096 angular.forEach(result.info, function (file) {
46097 if (file.fileType === '0') {
46099 if (controller.info.path === '/') {
46100 o.path = '/' + file.name;
46103 o.path = controller.info.path + '/' + file.name;
46105 controller.add()(o, controller.list);
46112 DirectoryController.$inject = ['APIEndPoint', '$scope'];
46113 return DirectoryController;
46115 directives.DirectoryController = DirectoryController;
46116 })(directives = app.directives || (app.directives = {}));
46117 })(app || (app = {}));
46121 (function (controllers) {
46122 var Execution = (function () {
46123 function Execution(MyModal, $scope) {
46124 this.MyModal = MyModal;
46125 this.$scope = $scope;
46126 this.commandInfoList = [];
46129 Execution.prototype.add = function () {
46130 this.$scope.$broadcast('close');
46131 this.commandInfoList.push(new app.declares.CommandInfo('mrcImageNoiseAdd'));
46133 Execution.prototype.open = function () {
46134 var result = this.MyModal.open('SelectCommand');
46135 console.log(result);
46137 Execution.prototype.remove = function (index, list) {
46138 list.splice(index, 1);
46140 Execution.prototype.close = function () {
46141 console.log("close");
46143 Execution.$inject = ['MyModal', '$scope'];
46146 controllers.Execution = Execution;
46147 })(controllers = app.controllers || (app.controllers = {}));
46148 })(app || (app = {}));
46152 (function (controllers) {
46153 var Workspace = (function () {
46154 function Workspace($scope, APIEndPoint) {
46155 this.$scope = $scope;
46156 this.APIEndPoint = APIEndPoint;
46157 this.directoryList = [];
46158 var controller = this;
46159 var directoryList = this.directoryList;
46161 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
46169 directoryList.push(o);
46171 Workspace.prototype.addDirectory = function (info, directoryList) {
46172 directoryList.push(info);
46174 Workspace.$inject = ['$scope', 'APIEndPoint'];
46177 controllers.Workspace = Workspace;
46178 })(controllers = app.controllers || (app.controllers = {}));
46179 })(app || (app = {}));
46183 (function (controllers) {
46184 var History = (function () {
46185 function History($scope) {
46186 this.page = "History";
46188 History.$inject = ['$scope'];
46191 controllers.History = History;
46192 })(controllers = app.controllers || (app.controllers = {}));
46193 })(app || (app = {}));
46197 (function (controllers) {
46198 var SelectCommand = (function () {
46199 function SelectCommand($scope, APIEndPOint) {
46200 this.APIEndPOint = APIEndPOint;
46201 this.page = "History";
46203 SelectCommand.$inject = ['$scope', 'APIEndPoint'];
46204 return SelectCommand;
46206 controllers.SelectCommand = SelectCommand;
46207 })(controllers = app.controllers || (app.controllers = {}));
46208 })(app || (app = {}));
46212 var appName = 'zephyr';
46213 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
46214 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
46215 $urlRouterProvider.otherwise('/execution');
46216 $locationProvider.html5Mode({
46221 .state('execution', {
46223 templateUrl: 'templates/execution.html',
46224 controller: 'executionController',
46227 .state('workspace', {
46229 templateUrl: 'templates/workspace.html',
46230 controller: 'workspaceController',
46233 .state('history', {
46235 templateUrl: 'templates/history.html',
46236 controller: 'historyController',
46240 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
46241 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
46242 app.zephyr.service('MyModal', app.services.MyModal);
46243 app.zephyr.controller('executionController', app.controllers.Execution);
46244 app.zephyr.controller('workspaceController', app.controllers.Workspace);
46245 app.zephyr.controller('historyController', app.controllers.History);
46246 app.zephyr.controller('commandController', app.directives.CommandController);
46247 app.zephyr.controller('optionController', app.directives.OptionController);
46248 app.zephyr.controller('directoryController', app.directives.DirectoryController);
46249 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
46250 app.zephyr.directive('command', app.directives.Command.Factory());
46251 app.zephyr.directive('option', app.directives.Option.Factory());
46252 app.zephyr.directive('directory', app.directives.Directory.Factory());
46253 })(app || (app = {}));
46258 /***/ function(module, exports) {
46263 (function (declares) {
46264 var CommandInfo = (function () {
46265 function CommandInfo(name) {
46268 return CommandInfo;
46270 declares.CommandInfo = CommandInfo;
46271 })(declares = app.declares || (app.declares = {}));
46272 })(app || (app = {}));
46276 (function (services) {
46277 var APIEndPoint = (function () {
46278 function APIEndPoint($resource, $http) {
46279 this.$resource = $resource;
46280 this.$http = $http;
46282 APIEndPoint.prototype.resource = function (endPoint, data) {
46283 var customAction = {
46289 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
46291 return this.$resource(endPoint, {}, { execute: execute });
46293 APIEndPoint.prototype.getOptionControlFile = function (command) {
46294 var endPoint = '/api/v1/optionControlFile/' + command;
46295 return this.resource(endPoint, {}).get();
46297 APIEndPoint.prototype.getFiles = function (fileId) {
46298 var endPoint = '/api/v1/workspace';
46300 endPoint += '/' + fileId;
46302 return this.resource(endPoint, {}).get();
46304 APIEndPoint.prototype.getDirectories = function () {
46305 var endPoint = '/api/v1/all/workspace/directory';
46306 return this.resource(endPoint, {}).get();
46308 APIEndPoint.prototype.execute = function (data) {
46309 var endPoint = '/api/v1/execution';
46310 var fd = new FormData();
46311 fd.append('data', data);
46312 return this.$http.post(endPoint, fd, {
46313 headers: { 'Content-Type': undefined },
46314 transformRequest: angular.identity
46317 return APIEndPoint;
46319 services.APIEndPoint = APIEndPoint;
46320 })(services = app.services || (app.services = {}));
46321 })(app || (app = {}));
46325 (function (services) {
46326 var MyModal = (function () {
46327 function MyModal($uibModal) {
46328 this.$uibModal = $uibModal;
46329 this.modalOption = {
46336 MyModal.prototype.open = function (modalName) {
46337 if (modalName === 'SelectCommand') {
46338 this.modalOption.templateUrl = 'templates/select-command.html';
46339 this.modalOption.size = 'lg';
46341 return this.$uibModal.open(this.modalOption);
46343 MyModal.prototype.selectCommand = function () {
46344 this.modalOption.templateUrl = 'templates/select-command.html';
46345 this.modalOption.controller = 'selectCommandController';
46346 this.modalOption.size = 'lg';
46347 return this.$uibModal.open(this.modalOption);
46351 services.MyModal = MyModal;
46352 })(services = app.services || (app.services = {}));
46353 })(app || (app = {}));
46357 (function (directives) {
46358 var Command = (function () {
46359 function Command() {
46360 this.restrict = 'E';
46361 this.replace = true;
46363 this.controller = 'commandController';
46364 this.controllerAs = 'ctrl';
46365 this.bindToController = {
46371 this.templateUrl = 'templates/command.html';
46373 Command.Factory = function () {
46374 var directive = function () {
46375 return new Command();
46377 directive.$inject = [];
46382 directives.Command = Command;
46383 var CommandController = (function () {
46384 function CommandController(APIEndPoint, $scope) {
46385 this.APIEndPoint = APIEndPoint;
46386 this.$scope = $scope;
46387 var controller = this;
46389 .getOptionControlFile('mrcImageNoiseAdd')
46391 .then(function (result) {
46392 controller.options = result.info;
46397 .then(function (result) {
46398 controller.dirs = result.info;
46400 this.heading = "[" + this.index + "]: dcdFilePring";
46401 this.isOpen = true;
46402 this.$scope.$on('close', function () {
46403 controller.isOpen = false;
46406 CommandController.prototype.submit = function () {
46408 angular.forEach(this.options, function (option) {
46410 name: option.option,
46413 angular.forEach(option.arg, function (arg) {
46415 if (typeof arg.input === 'object') {
46416 obj.arguments.push(arg.input.name);
46419 obj.arguments.push(arg.input);
46423 if (obj.arguments.length > 0) {
46428 command: this.name,
46429 workspace: this.workspace.fileId,
46433 .execute(JSON.stringify(execObj))
46434 .then(function (result) {
46435 console.log(result);
46438 CommandController.prototype.removeMySelf = function (index) {
46439 this.remove()(index, this.list);
46441 CommandController.prototype.reloadFiles = function () {
46443 var fileId = this.workspace.fileId;
46447 .then(function (result) {
46448 var status = result.status;
46449 if (status === 'success') {
46450 _this.files = result.info;
46453 console.log(result.message);
46457 CommandController.prototype.debug = function () {
46458 console.log(this.files);
46459 console.log(this.files);
46460 console.log(this.workspace);
46462 CommandController.$inject = ['APIEndPoint', '$scope'];
46463 return CommandController;
46465 directives.CommandController = CommandController;
46466 })(directives = app.directives || (app.directives = {}));
46467 })(app || (app = {}));
46471 (function (directives) {
46472 var HeaderMenu = (function () {
46473 function HeaderMenu() {
46474 this.restrict = 'E';
46475 this.replace = true;
46476 this.templateUrl = 'templates/header-menu.html';
46478 HeaderMenu.Factory = function () {
46479 var directive = function () {
46480 return new HeaderMenu();
46486 directives.HeaderMenu = HeaderMenu;
46487 })(directives = app.directives || (app.directives = {}));
46488 })(app || (app = {}));
46492 (function (directives) {
46493 var Option = (function () {
46494 function Option() {
46495 this.restrict = 'E';
46496 this.replace = true;
46497 this.controller = 'optionController';
46498 this.bindToController = {
46503 this.templateUrl = 'templates/option.html';
46504 this.controllerAs = 'ctrl';
46506 Option.Factory = function () {
46507 var directive = function () {
46508 return new Option();
46510 directive.$inject = [];
46515 directives.Option = Option;
46516 var OptionController = (function () {
46517 function OptionController() {
46518 var controller = this;
46519 angular.forEach(controller.info.arg, function (arg) {
46520 if (arg.initialValue) {
46521 if (arg.formType === 'number') {
46522 arg.input = parseInt(arg.initialValue);
46525 arg.input = arg.initialValue;
46530 OptionController.$inject = [];
46531 return OptionController;
46533 directives.OptionController = OptionController;
46534 })(directives = app.directives || (app.directives = {}));
46535 })(app || (app = {}));
46539 (function (directives) {
46540 var Directory = (function () {
46541 function Directory() {
46542 this.restrict = 'E';
46543 this.replace = true;
46544 this.controller = 'directoryController';
46545 this.controllerAs = 'ctrl';
46546 this.bindToController = {
46552 this.templateUrl = 'templates/directory.html';
46554 Directory.Factory = function () {
46555 var directive = function () {
46556 return new Directory();
46562 directives.Directory = Directory;
46563 var DirectoryController = (function () {
46564 function DirectoryController(APIEndPoint, $scope) {
46565 this.APIEndPoint = APIEndPoint;
46566 this.$scope = $scope;
46567 var controller = this;
46569 .getFiles(this.info.fileId)
46571 .then(function (result) {
46572 if (result.status === 'success') {
46573 controller.files = result.info;
46574 angular.forEach(result.info, function (file) {
46575 if (file.fileType === '0') {
46577 if (controller.info.path === '/') {
46578 o.path = '/' + file.name;
46581 o.path = controller.info.path + '/' + file.name;
46583 controller.add()(o, controller.list);
46590 DirectoryController.$inject = ['APIEndPoint', '$scope'];
46591 return DirectoryController;
46593 directives.DirectoryController = DirectoryController;
46594 })(directives = app.directives || (app.directives = {}));
46595 })(app || (app = {}));
46599 (function (controllers) {
46600 var Execution = (function () {
46601 function Execution(MyModal, $scope) {
46602 this.MyModal = MyModal;
46603 this.$scope = $scope;
46604 this.commandInfoList = [];
46607 Execution.prototype.add = function () {
46608 this.$scope.$broadcast('close');
46609 this.commandInfoList.push(new app.declares.CommandInfo('mrcImageNoiseAdd'));
46611 Execution.prototype.open = function () {
46612 var result = this.MyModal.open('SelectCommand');
46613 console.log(result);
46615 Execution.prototype.remove = function (index, list) {
46616 list.splice(index, 1);
46618 Execution.prototype.close = function () {
46619 console.log("close");
46621 Execution.$inject = ['MyModal', '$scope'];
46624 controllers.Execution = Execution;
46625 })(controllers = app.controllers || (app.controllers = {}));
46626 })(app || (app = {}));
46630 (function (controllers) {
46631 var Workspace = (function () {
46632 function Workspace($scope, APIEndPoint) {
46633 this.$scope = $scope;
46634 this.APIEndPoint = APIEndPoint;
46635 this.directoryList = [];
46636 var controller = this;
46637 var directoryList = this.directoryList;
46639 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
46647 directoryList.push(o);
46649 Workspace.prototype.addDirectory = function (info, directoryList) {
46650 directoryList.push(info);
46652 Workspace.$inject = ['$scope', 'APIEndPoint'];
46655 controllers.Workspace = Workspace;
46656 })(controllers = app.controllers || (app.controllers = {}));
46657 })(app || (app = {}));
46661 (function (controllers) {
46662 var History = (function () {
46663 function History($scope) {
46664 this.page = "History";
46666 History.$inject = ['$scope'];
46669 controllers.History = History;
46670 })(controllers = app.controllers || (app.controllers = {}));
46671 })(app || (app = {}));
46675 (function (controllers) {
46676 var SelectCommand = (function () {
46677 function SelectCommand($scope, APIEndPOint) {
46678 this.APIEndPOint = APIEndPOint;
46679 this.page = "History";
46681 SelectCommand.$inject = ['$scope', 'APIEndPoint'];
46682 return SelectCommand;
46684 controllers.SelectCommand = SelectCommand;
46685 })(controllers = app.controllers || (app.controllers = {}));
46686 })(app || (app = {}));
46690 var appName = 'zephyr';
46691 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
46692 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
46693 $urlRouterProvider.otherwise('/execution');
46694 $locationProvider.html5Mode({
46699 .state('execution', {
46701 templateUrl: 'templates/execution.html',
46702 controller: 'executionController',
46705 .state('workspace', {
46707 templateUrl: 'templates/workspace.html',
46708 controller: 'workspaceController',
46711 .state('history', {
46713 templateUrl: 'templates/history.html',
46714 controller: 'historyController',
46718 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
46719 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
46720 app.zephyr.service('MyModal', app.services.MyModal);
46721 app.zephyr.controller('executionController', app.controllers.Execution);
46722 app.zephyr.controller('workspaceController', app.controllers.Workspace);
46723 app.zephyr.controller('historyController', app.controllers.History);
46724 app.zephyr.controller('commandController', app.directives.CommandController);
46725 app.zephyr.controller('optionController', app.directives.OptionController);
46726 app.zephyr.controller('directoryController', app.directives.DirectoryController);
46727 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
46728 app.zephyr.directive('command', app.directives.Command.Factory());
46729 app.zephyr.directive('option', app.directives.Option.Factory());
46730 app.zephyr.directive('directory', app.directives.Directory.Factory());
46731 })(app || (app = {}));