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);
63 __webpack_require__(20);
64 __webpack_require__(21);
69 /***/ function(module, exports, __webpack_require__) {
71 __webpack_require__(2);
72 module.exports = angular;
77 /***/ function(module, exports) {
80 * @license AngularJS v1.4.8
81 * (c) 2010-2015 Google, Inc. http://angularjs.org
84 (function(window, document, undefined) {'use strict';
89 * This object provides a utility for producing rich Error messages within
90 * Angular. It can be called as follows:
92 * var exampleMinErr = minErr('example');
93 * throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
95 * The above creates an instance of minErr in the example namespace. The
96 * resulting error will have a namespaced error code of example.one. The
97 * resulting error will replace {0} with the value of foo, and {1} with the
98 * value of bar. The object is not restricted in the number of arguments it can
101 * If fewer arguments are specified than necessary for interpolation, the extra
102 * interpolation markers will be preserved in the final string.
104 * Since data will be parsed statically during a build step, some restrictions
105 * are applied with respect to how minErr instances are created and called.
106 * Instances should have names of the form namespaceMinErr for a minErr created
107 * using minErr('namespace') . Error codes, namespaces and template strings
108 * should all be static strings, not variables or general expressions.
110 * @param {string} module The namespace to use for the new minErr instance.
111 * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning
112 * error from returned function, for cases when a particular type of error is useful.
113 * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance
116 function minErr(module, ErrorConstructor) {
117 ErrorConstructor = ErrorConstructor || Error;
119 var SKIP_INDEXES = 2;
121 var templateArgs = arguments,
122 code = templateArgs[0],
123 message = '[' + (module ? module + ':' : '') + code + '] ',
124 template = templateArgs[1],
127 message += template.replace(/\{\d+\}/g, function(match) {
128 var index = +match.slice(1, -1),
129 shiftedIndex = index + SKIP_INDEXES;
131 if (shiftedIndex < templateArgs.length) {
132 return toDebugString(templateArgs[shiftedIndex]);
138 message += '\nhttp://errors.angularjs.org/1.4.8/' +
139 (module ? module + '/' : '') + code;
141 for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
142 message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' +
143 encodeURIComponent(toDebugString(templateArgs[i]));
146 return new ErrorConstructor(message);
150 /* We need to tell jshint what variables are being exported */
151 /* global angular: true,
162 REGEX_STRING_REGEXP: true,
163 VALIDITY_STATE_PROPERTY: true,
167 manualLowercase: true,
168 manualUppercase: true,
201 escapeForRegexp: true,
214 toJsonReplacer: true,
217 convertTimezoneToLocal: true,
218 timezoneToOffset: true,
220 tryDecodeURIComponent: true,
223 encodeUriSegment: true,
224 encodeUriQuery: true,
227 getTestability: true,
232 assertNotHasOwnProperty: true,
235 hasOwnProperty: true,
238 NODE_TYPE_ELEMENT: true,
239 NODE_TYPE_ATTRIBUTE: true,
240 NODE_TYPE_TEXT: true,
241 NODE_TYPE_COMMENT: true,
242 NODE_TYPE_DOCUMENT: true,
243 NODE_TYPE_DOCUMENT_FRAGMENT: true,
246 ////////////////////////////////////
255 * The ng module is loaded by default when an AngularJS application is started. The module itself
256 * contains the essential components for an AngularJS application to function. The table below
257 * lists a high level breakdown of each of the services/factories, filters, directives and testing
258 * components available within this core module.
260 * <div doc-module-components="ng"></div>
263 var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/;
265 // The name of a form control's ValidityState property.
266 // This is used so that it's possible for internal tests to create mock ValidityStates.
267 var VALIDITY_STATE_PROPERTY = 'validity';
271 * @name angular.lowercase
275 * @description Converts the specified string to lowercase.
276 * @param {string} string String to be converted to lowercase.
277 * @returns {string} Lowercased string.
279 var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;};
280 var hasOwnProperty = Object.prototype.hasOwnProperty;
284 * @name angular.uppercase
288 * @description Converts the specified string to uppercase.
289 * @param {string} string String to be converted to uppercase.
290 * @returns {string} Uppercased string.
292 var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;};
295 var manualLowercase = function(s) {
296 /* jshint bitwise: false */
298 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);})
301 var manualUppercase = function(s) {
302 /* jshint bitwise: false */
304 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);})
309 // String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
310 // locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
311 // with correct but slower alternatives.
312 if ('i' !== 'I'.toLowerCase()) {
313 lowercase = manualLowercase;
314 uppercase = manualUppercase;
319 msie, // holds major version number for IE, or NaN if UA is not IE.
320 jqLite, // delay binding since jQuery could be loaded after us.
321 jQuery, // delay binding
325 toString = Object.prototype.toString,
326 getPrototypeOf = Object.getPrototypeOf,
327 ngMinErr = minErr('ng'),
330 angular = window.angular || (window.angular = {}),
335 * documentMode is an IE-only property
336 * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
338 msie = document.documentMode;
344 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments,
347 function isArrayLike(obj) {
349 // `null`, `undefined` and `window` are not array-like
350 if (obj == null || isWindow(obj)) return false;
352 // arrays, strings and jQuery/jqLite objects are array like
353 // * jqLite is either the jQuery or jqLite constructor function
354 // * we have to check the existance of jqLite first as this method is called
355 // via the forEach method when constructing the jqLite object in the first place
356 if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true;
358 // Support: iOS 8.2 (not reproducible in simulator)
359 // "length" in obj used to prevent JIT error (gh-11508)
360 var length = "length" in Object(obj) && obj.length;
362 // NodeList objects (with `item` method) and
363 // other objects with suitable length characteristics are array-like
364 return isNumber(length) &&
365 (length >= 0 && (length - 1) in obj || typeof obj.item == 'function');
370 * @name angular.forEach
375 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an
376 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value`
377 * is the value of an object property or an array element, `key` is the object property key or
378 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional.
380 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters
381 * using the `hasOwnProperty` method.
384 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),
385 * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
386 * return the value provided.
389 var values = {name: 'misko', gender: 'male'};
391 angular.forEach(values, function(value, key) {
392 this.push(key + ': ' + value);
394 expect(log).toEqual(['name: misko', 'gender: male']);
397 * @param {Object|Array} obj Object to iterate over.
398 * @param {Function} iterator Iterator function.
399 * @param {Object=} context Object to become context (`this`) for the iterator function.
400 * @returns {Object|Array} Reference to `obj`.
403 function forEach(obj, iterator, context) {
406 if (isFunction(obj)) {
408 // Need to check if hasOwnProperty exists,
409 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
410 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {
411 iterator.call(context, obj[key], key, obj);
414 } else if (isArray(obj) || isArrayLike(obj)) {
415 var isPrimitive = typeof obj !== 'object';
416 for (key = 0, length = obj.length; key < length; key++) {
417 if (isPrimitive || key in obj) {
418 iterator.call(context, obj[key], key, obj);
421 } else if (obj.forEach && obj.forEach !== forEach) {
422 obj.forEach(iterator, context, obj);
423 } else if (isBlankObject(obj)) {
424 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
426 iterator.call(context, obj[key], key, obj);
428 } else if (typeof obj.hasOwnProperty === 'function') {
429 // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed
431 if (obj.hasOwnProperty(key)) {
432 iterator.call(context, obj[key], key, obj);
436 // Slow path for objects which do not have a method `hasOwnProperty`
438 if (hasOwnProperty.call(obj, key)) {
439 iterator.call(context, obj[key], key, obj);
447 function forEachSorted(obj, iterator, context) {
448 var keys = Object.keys(obj).sort();
449 for (var i = 0; i < keys.length; i++) {
450 iterator.call(context, obj[keys[i]], keys[i]);
457 * when using forEach the params are value, key, but it is often useful to have key, value.
458 * @param {function(string, *)} iteratorFn
459 * @returns {function(*, string)}
461 function reverseParams(iteratorFn) {
462 return function(value, key) { iteratorFn(key, value); };
466 * A consistent way of creating unique IDs in angular.
468 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before
469 * we hit number precision issues in JavaScript.
471 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M
473 * @returns {number} an unique alpha-numeric string
481 * Set or clear the hashkey for an object.
483 * @param h the hashkey (!truthy to delete the hashkey)
485 function setHashKey(obj, h) {
489 delete obj.$$hashKey;
494 function baseExtend(dst, objs, deep) {
495 var h = dst.$$hashKey;
497 for (var i = 0, ii = objs.length; i < ii; ++i) {
499 if (!isObject(obj) && !isFunction(obj)) continue;
500 var keys = Object.keys(obj);
501 for (var j = 0, jj = keys.length; j < jj; j++) {
505 if (deep && isObject(src)) {
507 dst[key] = new Date(src.valueOf());
508 } else if (isRegExp(src)) {
509 dst[key] = new RegExp(src);
510 } else if (src.nodeName) {
511 dst[key] = src.cloneNode(true);
512 } else if (isElement(src)) {
513 dst[key] = src.clone();
515 if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
516 baseExtend(dst[key], [src], true);
530 * @name angular.extend
535 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
536 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
537 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
539 * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use
540 * {@link angular.merge} for this.
542 * @param {Object} dst Destination object.
543 * @param {...Object} src Source object(s).
544 * @returns {Object} Reference to `dst`.
546 function extend(dst) {
547 return baseExtend(dst, slice.call(arguments, 1), false);
553 * @name angular.merge
558 * Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
559 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
560 * by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`.
562 * Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source
563 * objects, performing a deep copy.
565 * @param {Object} dst Destination object.
566 * @param {...Object} src Source object(s).
567 * @returns {Object} Reference to `dst`.
569 function merge(dst) {
570 return baseExtend(dst, slice.call(arguments, 1), true);
575 function toInt(str) {
576 return parseInt(str, 10);
580 function inherit(parent, extra) {
581 return extend(Object.create(parent), extra);
591 * A function that performs no operations. This function can be useful when writing code in the
594 function foo(callback) {
595 var result = calculateResult();
596 (callback || angular.noop)(result);
606 * @name angular.identity
611 * A function that returns its first argument. This function is useful when writing code in the
615 function transformer(transformationFn, value) {
616 return (transformationFn || angular.identity)(value);
619 * @param {*} value to be returned.
620 * @returns {*} the value passed in.
622 function identity($) {return $;}
623 identity.$inject = [];
626 function valueFn(value) {return function() {return value;};}
628 function hasCustomToString(obj) {
629 return isFunction(obj.toString) && obj.toString !== toString;
635 * @name angular.isUndefined
640 * Determines if a reference is undefined.
642 * @param {*} value Reference to check.
643 * @returns {boolean} True if `value` is undefined.
645 function isUndefined(value) {return typeof value === 'undefined';}
650 * @name angular.isDefined
655 * Determines if a reference is defined.
657 * @param {*} value Reference to check.
658 * @returns {boolean} True if `value` is defined.
660 function isDefined(value) {return typeof value !== 'undefined';}
665 * @name angular.isObject
670 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
671 * considered to be objects. Note that JavaScript arrays are objects.
673 * @param {*} value Reference to check.
674 * @returns {boolean} True if `value` is an `Object` but not `null`.
676 function isObject(value) {
677 // http://jsperf.com/isobject4
678 return value !== null && typeof value === 'object';
683 * Determine if a value is an object with a null prototype
685 * @returns {boolean} True if `value` is an `Object` with a null prototype
687 function isBlankObject(value) {
688 return value !== null && typeof value === 'object' && !getPrototypeOf(value);
694 * @name angular.isString
699 * Determines if a reference is a `String`.
701 * @param {*} value Reference to check.
702 * @returns {boolean} True if `value` is a `String`.
704 function isString(value) {return typeof value === 'string';}
709 * @name angular.isNumber
714 * Determines if a reference is a `Number`.
716 * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`.
718 * If you wish to exclude these then you can use the native
719 * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite)
722 * @param {*} value Reference to check.
723 * @returns {boolean} True if `value` is a `Number`.
725 function isNumber(value) {return typeof value === 'number';}
730 * @name angular.isDate
735 * Determines if a value is a date.
737 * @param {*} value Reference to check.
738 * @returns {boolean} True if `value` is a `Date`.
740 function isDate(value) {
741 return toString.call(value) === '[object Date]';
747 * @name angular.isArray
752 * Determines if a reference is an `Array`.
754 * @param {*} value Reference to check.
755 * @returns {boolean} True if `value` is an `Array`.
757 var isArray = Array.isArray;
761 * @name angular.isFunction
766 * Determines if a reference is a `Function`.
768 * @param {*} value Reference to check.
769 * @returns {boolean} True if `value` is a `Function`.
771 function isFunction(value) {return typeof value === 'function';}
775 * Determines if a value is a regular expression object.
778 * @param {*} value Reference to check.
779 * @returns {boolean} True if `value` is a `RegExp`.
781 function isRegExp(value) {
782 return toString.call(value) === '[object RegExp]';
787 * Checks if `obj` is a window object.
790 * @param {*} obj Object to check
791 * @returns {boolean} True if `obj` is a window obj.
793 function isWindow(obj) {
794 return obj && obj.window === obj;
798 function isScope(obj) {
799 return obj && obj.$evalAsync && obj.$watch;
803 function isFile(obj) {
804 return toString.call(obj) === '[object File]';
808 function isFormData(obj) {
809 return toString.call(obj) === '[object FormData]';
813 function isBlob(obj) {
814 return toString.call(obj) === '[object Blob]';
818 function isBoolean(value) {
819 return typeof value === 'boolean';
823 function isPromiseLike(obj) {
824 return obj && isFunction(obj.then);
828 var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/;
829 function isTypedArray(value) {
830 return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value));
834 var trim = function(value) {
835 return isString(value) ? value.trim() : value;
839 // http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021
840 // Prereq: s is a string.
841 var escapeForRegexp = function(s) {
842 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
843 replace(/\x08/g, '\\x08');
849 * @name angular.isElement
854 * Determines if a reference is a DOM element (or wrapped jQuery element).
856 * @param {*} value Reference to check.
857 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
859 function isElement(node) {
861 (node.nodeName // we are a direct element
862 || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API
866 * @param str 'key1,key2,...'
867 * @returns {object} in the form of {key1:true, key2:true, ...}
869 function makeMap(str) {
870 var obj = {}, items = str.split(","), i;
871 for (i = 0; i < items.length; i++) {
872 obj[items[i]] = true;
878 function nodeName_(element) {
879 return lowercase(element.nodeName || (element[0] && element[0].nodeName));
882 function includes(array, obj) {
883 return Array.prototype.indexOf.call(array, obj) != -1;
886 function arrayRemove(array, value) {
887 var index = array.indexOf(value);
889 array.splice(index, 1);
901 * Creates a deep copy of `source`, which should be an object or an array.
903 * * If no destination is supplied, a copy of the object or array is created.
904 * * If a destination is provided, all of its elements (for arrays) or properties (for objects)
905 * are deleted and then all elements/properties from the source are copied to it.
906 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
907 * * If `source` is identical to 'destination' an exception will be thrown.
909 * @param {*} source The source that will be used to make a copy.
910 * Can be any type, including primitives, `null`, and `undefined`.
911 * @param {(Object|Array)=} destination Destination into which the source is copied. If
912 * provided, must be of the same type as `source`.
913 * @returns {*} The copy or updated `destination`, if `destination` was specified.
916 <example module="copyExample">
917 <file name="index.html">
918 <div ng-controller="ExampleController">
919 <form novalidate class="simple-form">
920 Name: <input type="text" ng-model="user.name" /><br />
921 E-mail: <input type="email" ng-model="user.email" /><br />
922 Gender: <input type="radio" ng-model="user.gender" value="male" />male
923 <input type="radio" ng-model="user.gender" value="female" />female<br />
924 <button ng-click="reset()">RESET</button>
925 <button ng-click="update(user)">SAVE</button>
927 <pre>form = {{user | json}}</pre>
928 <pre>master = {{master | json}}</pre>
932 angular.module('copyExample', [])
933 .controller('ExampleController', ['$scope', function($scope) {
936 $scope.update = function(user) {
937 // Example with 1 argument
938 $scope.master= angular.copy(user);
941 $scope.reset = function() {
942 // Example with 2 arguments
943 angular.copy($scope.master, $scope.user);
952 function copy(source, destination) {
953 var stackSource = [];
957 if (isTypedArray(destination)) {
958 throw ngMinErr('cpta', "Can't copy! TypedArray destination cannot be mutated.");
960 if (source === destination) {
961 throw ngMinErr('cpi', "Can't copy! Source and destination are identical.");
964 // Empty the destination object
965 if (isArray(destination)) {
966 destination.length = 0;
968 forEach(destination, function(value, key) {
969 if (key !== '$$hashKey') {
970 delete destination[key];
975 stackSource.push(source);
976 stackDest.push(destination);
977 return copyRecurse(source, destination);
980 return copyElement(source);
982 function copyRecurse(source, destination) {
983 var h = destination.$$hashKey;
985 if (isArray(source)) {
986 for (var i = 0, ii = source.length; i < ii; i++) {
987 destination.push(copyElement(source[i]));
989 } else if (isBlankObject(source)) {
990 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
991 for (key in source) {
992 destination[key] = copyElement(source[key]);
994 } else if (source && typeof source.hasOwnProperty === 'function') {
995 // Slow path, which must rely on hasOwnProperty
996 for (key in source) {
997 if (source.hasOwnProperty(key)) {
998 destination[key] = copyElement(source[key]);
1002 // Slowest path --- hasOwnProperty can't be called as a method
1003 for (key in source) {
1004 if (hasOwnProperty.call(source, key)) {
1005 destination[key] = copyElement(source[key]);
1009 setHashKey(destination, h);
1013 function copyElement(source) {
1015 if (!isObject(source)) {
1019 // Already copied values
1020 var index = stackSource.indexOf(source);
1022 return stackDest[index];
1025 if (isWindow(source) || isScope(source)) {
1026 throw ngMinErr('cpws',
1027 "Can't copy! Making copies of Window or Scope instances is not supported.");
1030 var needsRecurse = false;
1033 if (isArray(source)) {
1035 needsRecurse = true;
1036 } else if (isTypedArray(source)) {
1037 destination = new source.constructor(source);
1038 } else if (isDate(source)) {
1039 destination = new Date(source.getTime());
1040 } else if (isRegExp(source)) {
1041 destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
1042 destination.lastIndex = source.lastIndex;
1043 } else if (isFunction(source.cloneNode)) {
1044 destination = source.cloneNode(true);
1046 destination = Object.create(getPrototypeOf(source));
1047 needsRecurse = true;
1050 stackSource.push(source);
1051 stackDest.push(destination);
1054 ? copyRecurse(source, destination)
1060 * Creates a shallow copy of an object, an array or a primitive.
1062 * Assumes that there are no proto properties for objects.
1064 function shallowCopy(src, dst) {
1068 for (var i = 0, ii = src.length; i < ii; i++) {
1071 } else if (isObject(src)) {
1074 for (var key in src) {
1075 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
1076 dst[key] = src[key];
1087 * @name angular.equals
1092 * Determines if two objects or two values are equivalent. Supports value types, regular
1093 * expressions, arrays and objects.
1095 * Two objects or values are considered equivalent if at least one of the following is true:
1097 * * Both objects or values pass `===` comparison.
1098 * * Both objects or values are of the same type and all of their properties are equal by
1099 * comparing them with `angular.equals`.
1100 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal)
1101 * * Both values represent the same regular expression (In JavaScript,
1102 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual
1103 * representation matches).
1105 * During a property comparison, properties of `function` type and properties with names
1106 * that begin with `$` are ignored.
1108 * Scope and DOMWindow objects are being compared only by identify (`===`).
1110 * @param {*} o1 Object or value to compare.
1111 * @param {*} o2 Object or value to compare.
1112 * @returns {boolean} True if arguments are equal.
1114 function equals(o1, o2) {
1115 if (o1 === o2) return true;
1116 if (o1 === null || o2 === null) return false;
1117 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
1118 var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
1120 if (t1 == 'object') {
1122 if (!isArray(o2)) return false;
1123 if ((length = o1.length) == o2.length) {
1124 for (key = 0; key < length; key++) {
1125 if (!equals(o1[key], o2[key])) return false;
1129 } else if (isDate(o1)) {
1130 if (!isDate(o2)) return false;
1131 return equals(o1.getTime(), o2.getTime());
1132 } else if (isRegExp(o1)) {
1133 return isRegExp(o2) ? o1.toString() == o2.toString() : false;
1135 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
1136 isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
1137 keySet = createMap();
1139 if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
1140 if (!equals(o1[key], o2[key])) return false;
1144 if (!(key in keySet) &&
1145 key.charAt(0) !== '$' &&
1146 isDefined(o2[key]) &&
1147 !isFunction(o2[key])) return false;
1156 var csp = function() {
1157 if (!isDefined(csp.rules)) {
1160 var ngCspElement = (document.querySelector('[ng-csp]') ||
1161 document.querySelector('[data-ng-csp]'));
1164 var ngCspAttribute = ngCspElement.getAttribute('ng-csp') ||
1165 ngCspElement.getAttribute('data-ng-csp');
1167 noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1),
1168 noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1)
1172 noUnsafeEval: noUnsafeEval(),
1173 noInlineStyle: false
1180 function noUnsafeEval() {
1182 /* jshint -W031, -W054 */
1184 /* jshint +W031, +W054 */
1198 * @param {string=} ngJq the name of the library available under `window`
1199 * to be used for angular.element
1201 * Use this directive to force the angular.element library. This should be
1202 * used to force either jqLite by leaving ng-jq blank or setting the name of
1203 * the jquery variable under window (eg. jQuery).
1205 * Since angular looks for this directive when it is loaded (doesn't wait for the
1206 * DOMContentLoaded event), it must be placed on an element that comes before the script
1207 * which loads angular. Also, only the first instance of `ng-jq` will be used and all
1211 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag.
1220 * This example shows how to use a jQuery based library of a different name.
1221 * The library name must be available at the top most 'window'.
1224 <html ng-app ng-jq="jQueryLib">
1230 var jq = function() {
1231 if (isDefined(jq.name_)) return jq.name_;
1233 var i, ii = ngAttrPrefixes.length, prefix, name;
1234 for (i = 0; i < ii; ++i) {
1235 prefix = ngAttrPrefixes[i];
1236 if (el = document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) {
1237 name = el.getAttribute(prefix + 'jq');
1242 return (jq.name_ = name);
1245 function concat(array1, array2, index) {
1246 return array1.concat(slice.call(array2, index));
1249 function sliceArgs(args, startIndex) {
1250 return slice.call(args, startIndex || 0);
1257 * @name angular.bind
1262 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for
1263 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also
1264 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as
1265 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application).
1267 * @param {Object} self Context which `fn` should be evaluated in.
1268 * @param {function()} fn Function to be bound.
1269 * @param {...*} args Optional arguments to be prebound to the `fn` function call.
1270 * @returns {function()} Function that wraps the `fn` with all the specified bindings.
1273 function bind(self, fn) {
1274 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : [];
1275 if (isFunction(fn) && !(fn instanceof RegExp)) {
1276 return curryArgs.length
1278 return arguments.length
1279 ? fn.apply(self, concat(curryArgs, arguments, 0))
1280 : fn.apply(self, curryArgs);
1283 return arguments.length
1284 ? fn.apply(self, arguments)
1288 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be)
1294 function toJsonReplacer(key, value) {
1297 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') {
1299 } else if (isWindow(value)) {
1301 } else if (value && document === value) {
1303 } else if (isScope(value)) {
1313 * @name angular.toJson
1318 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be
1319 * stripped since angular uses this notation internally.
1321 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
1322 * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace.
1323 * If set to an integer, the JSON output will contain that many spaces per indentation.
1324 * @returns {string|undefined} JSON-ified string representing `obj`.
1326 function toJson(obj, pretty) {
1327 if (typeof obj === 'undefined') return undefined;
1328 if (!isNumber(pretty)) {
1329 pretty = pretty ? 2 : null;
1331 return JSON.stringify(obj, toJsonReplacer, pretty);
1337 * @name angular.fromJson
1342 * Deserializes a JSON string.
1344 * @param {string} json JSON string to deserialize.
1345 * @returns {Object|Array|string|number} Deserialized JSON string.
1347 function fromJson(json) {
1348 return isString(json)
1354 function timezoneToOffset(timezone, fallback) {
1355 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
1356 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
1360 function addDateMinutes(date, minutes) {
1361 date = new Date(date.getTime());
1362 date.setMinutes(date.getMinutes() + minutes);
1367 function convertTimezoneToLocal(date, timezone, reverse) {
1368 reverse = reverse ? -1 : 1;
1369 var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
1370 return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
1375 * @returns {string} Returns the string representation of the element.
1377 function startingTag(element) {
1378 element = jqLite(element).clone();
1380 // turns out IE does not let you set .html() on elements which
1381 // are not allowed to have children. So we just ignore it.
1384 var elemHtml = jqLite('<div>').append(element).html();
1386 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
1388 match(/^(<[^>]+>)/)[1].
1389 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
1391 return lowercase(elemHtml);
1397 /////////////////////////////////////////////////
1400 * Tries to decode the URI component without throwing an exception.
1403 * @param str value potential URI component to check.
1404 * @returns {boolean} True if `value` can be decoded
1405 * with the decodeURIComponent function.
1407 function tryDecodeURIComponent(value) {
1409 return decodeURIComponent(value);
1411 // Ignore any invalid uri component
1417 * Parses an escaped url query string into key-value pairs.
1418 * @returns {Object.<string,boolean|Array>}
1420 function parseKeyValue(/**string*/keyValue) {
1422 forEach((keyValue || "").split('&'), function(keyValue) {
1423 var splitPoint, key, val;
1425 key = keyValue = keyValue.replace(/\+/g,'%20');
1426 splitPoint = keyValue.indexOf('=');
1427 if (splitPoint !== -1) {
1428 key = keyValue.substring(0, splitPoint);
1429 val = keyValue.substring(splitPoint + 1);
1431 key = tryDecodeURIComponent(key);
1432 if (isDefined(key)) {
1433 val = isDefined(val) ? tryDecodeURIComponent(val) : true;
1434 if (!hasOwnProperty.call(obj, key)) {
1436 } else if (isArray(obj[key])) {
1439 obj[key] = [obj[key],val];
1447 function toKeyValue(obj) {
1449 forEach(obj, function(value, key) {
1450 if (isArray(value)) {
1451 forEach(value, function(arrayValue) {
1452 parts.push(encodeUriQuery(key, true) +
1453 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true)));
1456 parts.push(encodeUriQuery(key, true) +
1457 (value === true ? '' : '=' + encodeUriQuery(value, true)));
1460 return parts.length ? parts.join('&') : '';
1465 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
1466 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
1469 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1470 * pct-encoded = "%" HEXDIG HEXDIG
1471 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1472 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1473 * / "*" / "+" / "," / ";" / "="
1475 function encodeUriSegment(val) {
1476 return encodeUriQuery(val, true).
1477 replace(/%26/gi, '&').
1478 replace(/%3D/gi, '=').
1479 replace(/%2B/gi, '+');
1484 * This method is intended for encoding *key* or *value* parts of query component. We need a custom
1485 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
1486 * encoded per http://tools.ietf.org/html/rfc3986:
1487 * query = *( pchar / "/" / "?" )
1488 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1489 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1490 * pct-encoded = "%" HEXDIG HEXDIG
1491 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1492 * / "*" / "+" / "," / ";" / "="
1494 function encodeUriQuery(val, pctEncodeSpaces) {
1495 return encodeURIComponent(val).
1496 replace(/%40/gi, '@').
1497 replace(/%3A/gi, ':').
1498 replace(/%24/g, '$').
1499 replace(/%2C/gi, ',').
1500 replace(/%3B/gi, ';').
1501 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
1504 var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];
1506 function getNgAttribute(element, ngAttr) {
1507 var attr, i, ii = ngAttrPrefixes.length;
1508 for (i = 0; i < ii; ++i) {
1509 attr = ngAttrPrefixes[i] + ngAttr;
1510 if (isString(attr = element.getAttribute(attr))) {
1523 * @param {angular.Module} ngApp an optional application
1524 * {@link angular.module module} name to load.
1525 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be
1526 * created in "strict-di" mode. This means that the application will fail to invoke functions which
1527 * do not use explicit function annotation (and are thus unsuitable for minification), as described
1528 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in
1529 * tracking down the root of these bugs.
1533 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive
1534 * designates the **root element** of the application and is typically placed near the root element
1535 * of the page - e.g. on the `<body>` or `<html>` tags.
1537 * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp`
1538 * found in the document will be used to define the root element to auto-bootstrap as an
1539 * application. To run multiple applications in an HTML document you must manually bootstrap them using
1540 * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other.
1542 * You can specify an **AngularJS module** to be used as the root module for the application. This
1543 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It
1544 * should contain the application code needed or have dependencies on other modules that will
1545 * contain the code. See {@link angular.module} for more information.
1547 * In the example below if the `ngApp` directive were not placed on the `html` element then the
1548 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
1549 * would not be resolved to `3`.
1551 * `ngApp` is the easiest, and most common way to bootstrap an application.
1553 <example module="ngAppDemo">
1554 <file name="index.html">
1555 <div ng-controller="ngAppDemoController">
1556 I can add: {{a}} + {{b}} = {{ a+b }}
1559 <file name="script.js">
1560 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
1567 * Using `ngStrictDi`, you would see something like this:
1569 <example ng-app-included="true">
1570 <file name="index.html">
1571 <div ng-app="ngAppStrictDemo" ng-strict-di>
1572 <div ng-controller="GoodController1">
1573 I can add: {{a}} + {{b}} = {{ a+b }}
1575 <p>This renders because the controller does not fail to
1576 instantiate, by using explicit annotation style (see
1577 script.js for details)
1581 <div ng-controller="GoodController2">
1582 Name: <input ng-model="name"><br />
1585 <p>This renders because the controller does not fail to
1586 instantiate, by using explicit annotation style
1587 (see script.js for details)
1591 <div ng-controller="BadController">
1592 I can add: {{a}} + {{b}} = {{ a+b }}
1594 <p>The controller could not be instantiated, due to relying
1595 on automatic function annotations (which are disabled in
1596 strict mode). As such, the content of this section is not
1597 interpolated, and there should be an error in your web console.
1602 <file name="script.js">
1603 angular.module('ngAppStrictDemo', [])
1604 // BadController will fail to instantiate, due to relying on automatic function annotation,
1605 // rather than an explicit annotation
1606 .controller('BadController', function($scope) {
1610 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated,
1611 // due to using explicit annotations using the array style and $inject property, respectively.
1612 .controller('GoodController1', ['$scope', function($scope) {
1616 .controller('GoodController2', GoodController2);
1617 function GoodController2($scope) {
1618 $scope.name = "World";
1620 GoodController2.$inject = ['$scope'];
1622 <file name="style.css">
1623 div[ng-controller] {
1625 -webkit-border-radius: 4px;
1630 div[ng-controller^=Good] {
1631 border-color: #d6e9c6;
1632 background-color: #dff0d8;
1635 div[ng-controller^=Bad] {
1636 border-color: #ebccd1;
1637 background-color: #f2dede;
1644 function angularInit(element, bootstrap) {
1649 // The element `element` has priority over any other element
1650 forEach(ngAttrPrefixes, function(prefix) {
1651 var name = prefix + 'app';
1653 if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
1654 appElement = element;
1655 module = element.getAttribute(name);
1658 forEach(ngAttrPrefixes, function(prefix) {
1659 var name = prefix + 'app';
1662 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) {
1663 appElement = candidate;
1664 module = candidate.getAttribute(name);
1668 config.strictDi = getNgAttribute(appElement, "strict-di") !== null;
1669 bootstrap(appElement, module ? [module] : [], config);
1675 * @name angular.bootstrap
1678 * Use this function to manually start up angular application.
1680 * See: {@link guide/bootstrap Bootstrap}
1682 * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually.
1683 * They must use {@link ng.directive:ngApp ngApp}.
1685 * Angular will detect if it has been loaded into the browser more than once and only allow the
1686 * first loaded script to be bootstrapped and will report a warning to the browser console for
1687 * each of the subsequent scripts. This prevents strange results in applications, where otherwise
1688 * multiple instances of Angular try to work on the DOM.
1694 * <div ng-controller="WelcomeController">
1698 * <script src="angular.js"></script>
1700 * var app = angular.module('demo', [])
1701 * .controller('WelcomeController', function($scope) {
1702 * $scope.greeting = 'Welcome!';
1704 * angular.bootstrap(document, ['demo']);
1710 * @param {DOMElement} element DOM element which is the root of angular application.
1711 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application.
1712 * Each item in the array should be the name of a predefined module or a (DI annotated)
1713 * function that will be invoked by the injector as a `config` block.
1714 * See: {@link angular.module modules}
1715 * @param {Object=} config an object for defining configuration options for the application. The
1716 * following keys are supported:
1718 * * `strictDi` - disable automatic function annotation for the application. This is meant to
1719 * assist in finding bugs which break minified code. Defaults to `false`.
1721 * @returns {auto.$injector} Returns the newly created injector for this app.
1723 function bootstrap(element, modules, config) {
1724 if (!isObject(config)) config = {};
1725 var defaultConfig = {
1728 config = extend(defaultConfig, config);
1729 var doBootstrap = function() {
1730 element = jqLite(element);
1732 if (element.injector()) {
1733 var tag = (element[0] === document) ? 'document' : startingTag(element);
1734 //Encode angle brackets to prevent input from being sanitized to empty string #8683
1737 "App Already Bootstrapped with this Element '{0}'",
1738 tag.replace(/</,'<').replace(/>/,'>'));
1741 modules = modules || [];
1742 modules.unshift(['$provide', function($provide) {
1743 $provide.value('$rootElement', element);
1746 if (config.debugInfoEnabled) {
1747 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
1748 modules.push(['$compileProvider', function($compileProvider) {
1749 $compileProvider.debugInfoEnabled(true);
1753 modules.unshift('ng');
1754 var injector = createInjector(modules, config.strictDi);
1755 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
1756 function bootstrapApply(scope, element, compile, injector) {
1757 scope.$apply(function() {
1758 element.data('$injector', injector);
1759 compile(element)(scope);
1766 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/;
1767 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
1769 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) {
1770 config.debugInfoEnabled = true;
1771 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, '');
1774 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
1775 return doBootstrap();
1778 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
1779 angular.resumeBootstrap = function(extraModules) {
1780 forEach(extraModules, function(module) {
1781 modules.push(module);
1783 return doBootstrap();
1786 if (isFunction(angular.resumeDeferredBootstrap)) {
1787 angular.resumeDeferredBootstrap();
1793 * @name angular.reloadWithDebugInfo
1796 * Use this function to reload the current application with debug information turned on.
1797 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`.
1799 * See {@link ng.$compileProvider#debugInfoEnabled} for more.
1801 function reloadWithDebugInfo() {
1802 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name;
1803 window.location.reload();
1807 * @name angular.getTestability
1810 * Get the testability service for the instance of Angular on the given
1812 * @param {DOMElement} element DOM element which is the root of angular application.
1814 function getTestability(rootElement) {
1815 var injector = angular.element(rootElement).injector();
1817 throw ngMinErr('test',
1818 'no injector found for element argument to getTestability');
1820 return injector.get('$$testability');
1823 var SNAKE_CASE_REGEXP = /[A-Z]/g;
1824 function snake_case(name, separator) {
1825 separator = separator || '_';
1826 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
1827 return (pos ? separator : '') + letter.toLowerCase();
1831 var bindJQueryFired = false;
1832 var skipDestroyOnNextJQueryCleanData;
1833 function bindJQuery() {
1834 var originalCleanData;
1836 if (bindJQueryFired) {
1840 // bind to jQuery if present;
1842 jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present)
1843 !jqName ? undefined : // use jqLite
1844 window[jqName]; // use jQuery specified by `ngJq`
1846 // Use jQuery if it exists with proper functionality, otherwise default to us.
1847 // Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
1848 // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older
1849 // versions. It will not work for sure with jQuery <1.7, though.
1850 if (jQuery && jQuery.fn.on) {
1853 scope: JQLitePrototype.scope,
1854 isolateScope: JQLitePrototype.isolateScope,
1855 controller: JQLitePrototype.controller,
1856 injector: JQLitePrototype.injector,
1857 inheritedData: JQLitePrototype.inheritedData
1860 // All nodes removed from the DOM via various jQuery APIs like .remove()
1861 // are passed through jQuery.cleanData. Monkey-patch this method to fire
1862 // the $destroy event on all removed nodes.
1863 originalCleanData = jQuery.cleanData;
1864 jQuery.cleanData = function(elems) {
1866 if (!skipDestroyOnNextJQueryCleanData) {
1867 for (var i = 0, elem; (elem = elems[i]) != null; i++) {
1868 events = jQuery._data(elem, "events");
1869 if (events && events.$destroy) {
1870 jQuery(elem).triggerHandler('$destroy');
1874 skipDestroyOnNextJQueryCleanData = false;
1876 originalCleanData(elems);
1882 angular.element = jqLite;
1884 // Prevent double-proxying.
1885 bindJQueryFired = true;
1889 * throw error if the argument is falsy.
1891 function assertArg(arg, name, reason) {
1893 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
1898 function assertArgFn(arg, name, acceptArrayAnnotation) {
1899 if (acceptArrayAnnotation && isArray(arg)) {
1900 arg = arg[arg.length - 1];
1903 assertArg(isFunction(arg), name, 'not a function, got ' +
1904 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg));
1909 * throw error if the name given is hasOwnProperty
1910 * @param {String} name the name to test
1911 * @param {String} context the context in which the name is used, such as module or directive
1913 function assertNotHasOwnProperty(name, context) {
1914 if (name === 'hasOwnProperty') {
1915 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context);
1920 * Return the value accessible from the object by path. Any undefined traversals are ignored
1921 * @param {Object} obj starting object
1922 * @param {String} path path to traverse
1923 * @param {boolean} [bindFnToScope=true]
1924 * @returns {Object} value as accessible by path
1926 //TODO(misko): this function needs to be removed
1927 function getter(obj, path, bindFnToScope) {
1928 if (!path) return obj;
1929 var keys = path.split('.');
1931 var lastInstance = obj;
1932 var len = keys.length;
1934 for (var i = 0; i < len; i++) {
1937 obj = (lastInstance = obj)[key];
1940 if (!bindFnToScope && isFunction(obj)) {
1941 return bind(lastInstance, obj);
1947 * Return the DOM siblings between the first and last node in the given array.
1948 * @param {Array} array like object
1949 * @returns {Array} the inputted object or a jqLite collection containing the nodes
1951 function getBlockNodes(nodes) {
1952 // TODO(perf): update `nodes` instead of creating a new object?
1953 var node = nodes[0];
1954 var endNode = nodes[nodes.length - 1];
1957 for (var i = 1; node !== endNode && (node = node.nextSibling); i++) {
1958 if (blockNodes || nodes[i] !== node) {
1960 blockNodes = jqLite(slice.call(nodes, 0, i));
1962 blockNodes.push(node);
1966 return blockNodes || nodes;
1971 * Creates a new object without a prototype. This object is useful for lookup without having to
1972 * guard against prototypically inherited properties via hasOwnProperty.
1974 * Related micro-benchmarks:
1975 * - http://jsperf.com/object-create2
1976 * - http://jsperf.com/proto-map-lookup/2
1977 * - http://jsperf.com/for-in-vs-object-keys2
1981 function createMap() {
1982 return Object.create(null);
1985 var NODE_TYPE_ELEMENT = 1;
1986 var NODE_TYPE_ATTRIBUTE = 2;
1987 var NODE_TYPE_TEXT = 3;
1988 var NODE_TYPE_COMMENT = 8;
1989 var NODE_TYPE_DOCUMENT = 9;
1990 var NODE_TYPE_DOCUMENT_FRAGMENT = 11;
1994 * @name angular.Module
1998 * Interface for configuring angular {@link angular.module modules}.
2001 function setupModuleLoader(window) {
2003 var $injectorMinErr = minErr('$injector');
2004 var ngMinErr = minErr('ng');
2006 function ensure(obj, name, factory) {
2007 return obj[name] || (obj[name] = factory());
2010 var angular = ensure(window, 'angular', Object);
2012 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
2013 angular.$$minErr = angular.$$minErr || minErr;
2015 return ensure(angular, 'module', function() {
2016 /** @type {Object.<string, angular.Module>} */
2021 * @name angular.module
2025 * The `angular.module` is a global place for creating, registering and retrieving Angular
2027 * All modules (angular core or 3rd party) that should be available to an application must be
2028 * registered using this mechanism.
2030 * Passing one argument retrieves an existing {@link angular.Module},
2031 * whereas passing more than one argument creates a new {@link angular.Module}
2036 * A module is a collection of services, directives, controllers, filters, and configuration information.
2037 * `angular.module` is used to configure the {@link auto.$injector $injector}.
2040 * // Create a new module
2041 * var myModule = angular.module('myModule', []);
2043 * // register a new service
2044 * myModule.value('appName', 'MyCoolApp');
2046 * // configure existing services inside initialization blocks.
2047 * myModule.config(['$locationProvider', function($locationProvider) {
2048 * // Configure existing providers
2049 * $locationProvider.hashPrefix('!');
2053 * Then you can create an injector and load your modules like this:
2056 * var injector = angular.injector(['ng', 'myModule'])
2059 * However it's more likely that you'll just use
2060 * {@link ng.directive:ngApp ngApp} or
2061 * {@link angular.bootstrap} to simplify this process for you.
2063 * @param {!string} name The name of the module to create or retrieve.
2064 * @param {!Array.<string>=} requires If specified then new module is being created. If
2065 * unspecified then the module is being retrieved for further configuration.
2066 * @param {Function=} configFn Optional configuration function for the module. Same as
2067 * {@link angular.Module#config Module#config()}.
2068 * @returns {module} new module with the {@link angular.Module} api.
2070 return function module(name, requires, configFn) {
2071 var assertNotHasOwnProperty = function(name, context) {
2072 if (name === 'hasOwnProperty') {
2073 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
2077 assertNotHasOwnProperty(name, 'module');
2078 if (requires && modules.hasOwnProperty(name)) {
2079 modules[name] = null;
2081 return ensure(modules, name, function() {
2083 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
2084 "the module name or forgot to load it. If registering a module ensure that you " +
2085 "specify the dependencies as the second argument.", name);
2088 /** @type {!Array.<Array.<*>>} */
2089 var invokeQueue = [];
2091 /** @type {!Array.<Function>} */
2092 var configBlocks = [];
2094 /** @type {!Array.<Function>} */
2097 var config = invokeLater('$injector', 'invoke', 'push', configBlocks);
2099 /** @type {angular.Module} */
2100 var moduleInstance = {
2102 _invokeQueue: invokeQueue,
2103 _configBlocks: configBlocks,
2104 _runBlocks: runBlocks,
2108 * @name angular.Module#requires
2112 * Holds the list of modules which the injector will load before the current module is
2119 * @name angular.Module#name
2123 * Name of the module.
2130 * @name angular.Module#provider
2132 * @param {string} name service name
2133 * @param {Function} providerType Construction function for creating new instance of the
2136 * See {@link auto.$provide#provider $provide.provider()}.
2138 provider: invokeLaterAndSetModuleName('$provide', 'provider'),
2142 * @name angular.Module#factory
2144 * @param {string} name service name
2145 * @param {Function} providerFunction Function for creating new instance of the service.
2147 * See {@link auto.$provide#factory $provide.factory()}.
2149 factory: invokeLaterAndSetModuleName('$provide', 'factory'),
2153 * @name angular.Module#service
2155 * @param {string} name service name
2156 * @param {Function} constructor A constructor function that will be instantiated.
2158 * See {@link auto.$provide#service $provide.service()}.
2160 service: invokeLaterAndSetModuleName('$provide', 'service'),
2164 * @name angular.Module#value
2166 * @param {string} name service name
2167 * @param {*} object Service instance object.
2169 * See {@link auto.$provide#value $provide.value()}.
2171 value: invokeLater('$provide', 'value'),
2175 * @name angular.Module#constant
2177 * @param {string} name constant name
2178 * @param {*} object Constant value.
2180 * Because the constants are fixed, they get applied before other provide methods.
2181 * See {@link auto.$provide#constant $provide.constant()}.
2183 constant: invokeLater('$provide', 'constant', 'unshift'),
2187 * @name angular.Module#decorator
2189 * @param {string} The name of the service to decorate.
2190 * @param {Function} This function will be invoked when the service needs to be
2191 * instantiated and should return the decorated service instance.
2193 * See {@link auto.$provide#decorator $provide.decorator()}.
2195 decorator: invokeLaterAndSetModuleName('$provide', 'decorator'),
2199 * @name angular.Module#animation
2201 * @param {string} name animation name
2202 * @param {Function} animationFactory Factory function for creating new instance of an
2206 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
2209 * Defines an animation hook that can be later used with
2210 * {@link $animate $animate} service and directives that use this service.
2213 * module.animation('.animation-name', function($inject1, $inject2) {
2215 * eventName : function(element, done) {
2216 * //code to run the animation
2217 * //once complete, then run done()
2218 * return function cancellationFunction(element) {
2219 * //code to cancel the animation
2226 * See {@link ng.$animateProvider#register $animateProvider.register()} and
2227 * {@link ngAnimate ngAnimate module} for more information.
2229 animation: invokeLaterAndSetModuleName('$animateProvider', 'register'),
2233 * @name angular.Module#filter
2235 * @param {string} name Filter name - this must be a valid angular expression identifier
2236 * @param {Function} filterFactory Factory function for creating new instance of filter.
2238 * See {@link ng.$filterProvider#register $filterProvider.register()}.
2240 * <div class="alert alert-warning">
2241 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
2242 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
2243 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
2244 * (`myapp_subsection_filterx`).
2247 filter: invokeLaterAndSetModuleName('$filterProvider', 'register'),
2251 * @name angular.Module#controller
2253 * @param {string|Object} name Controller name, or an object map of controllers where the
2254 * keys are the names and the values are the constructors.
2255 * @param {Function} constructor Controller constructor function.
2257 * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
2259 controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'),
2263 * @name angular.Module#directive
2265 * @param {string|Object} name Directive name, or an object map of directives where the
2266 * keys are the names and the values are the factories.
2267 * @param {Function} directiveFactory Factory function for creating new instance of
2270 * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
2272 directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
2276 * @name angular.Module#config
2278 * @param {Function} configFn Execute this function on module load. Useful for service
2281 * Use this method to register work which needs to be performed on module loading.
2282 * For more about how to configure services, see
2283 * {@link providers#provider-recipe Provider Recipe}.
2289 * @name angular.Module#run
2291 * @param {Function} initializationFn Execute this function after injector creation.
2292 * Useful for application initialization.
2294 * Use this method to register work which should be performed when the injector is done
2295 * loading all modules.
2297 run: function(block) {
2298 runBlocks.push(block);
2307 return moduleInstance;
2310 * @param {string} provider
2311 * @param {string} method
2312 * @param {String=} insertMethod
2313 * @returns {angular.Module}
2315 function invokeLater(provider, method, insertMethod, queue) {
2316 if (!queue) queue = invokeQueue;
2318 queue[insertMethod || 'push']([provider, method, arguments]);
2319 return moduleInstance;
2324 * @param {string} provider
2325 * @param {string} method
2326 * @returns {angular.Module}
2328 function invokeLaterAndSetModuleName(provider, method) {
2329 return function(recipeName, factoryFunction) {
2330 if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
2331 invokeQueue.push([provider, method, arguments]);
2332 return moduleInstance;
2341 /* global: toDebugString: true */
2343 function serializeObject(obj) {
2346 return JSON.stringify(obj, function(key, val) {
2347 val = toJsonReplacer(key, val);
2348 if (isObject(val)) {
2350 if (seen.indexOf(val) >= 0) return '...';
2358 function toDebugString(obj) {
2359 if (typeof obj === 'function') {
2360 return obj.toString().replace(/ \{[\s\S]*$/, '');
2361 } else if (isUndefined(obj)) {
2363 } else if (typeof obj !== 'string') {
2364 return serializeObject(obj);
2369 /* global angularModule: true,
2374 htmlAnchorDirective,
2383 ngBindHtmlDirective,
2384 ngBindTemplateDirective,
2386 ngClassEvenDirective,
2387 ngClassOddDirective,
2389 ngControllerDirective,
2394 ngIncludeFillContentDirective,
2396 ngNonBindableDirective,
2397 ngPluralizeDirective,
2402 ngSwitchWhenDirective,
2403 ngSwitchDefaultDirective,
2405 ngTranscludeDirective,
2418 ngModelOptionsDirective,
2419 ngAttributeAliasDirectives,
2422 $AnchorScrollProvider,
2424 $CoreAnimateCssProvider,
2425 $$CoreAnimateQueueProvider,
2426 $$CoreAnimateRunnerProvider,
2428 $CacheFactoryProvider,
2429 $ControllerProvider,
2431 $ExceptionHandlerProvider,
2433 $$ForceReflowProvider,
2434 $InterpolateProvider,
2438 $HttpParamSerializerProvider,
2439 $HttpParamSerializerJQLikeProvider,
2440 $HttpBackendProvider,
2441 $xhrFactoryProvider,
2448 $$SanitizeUriProvider,
2450 $SceDelegateProvider,
2452 $TemplateCacheProvider,
2453 $TemplateRequestProvider,
2454 $$TestabilityProvider,
2459 $$CookieReaderProvider
2465 * @name angular.version
2468 * An object that contains information about the current AngularJS version.
2470 * This object has the following properties:
2472 * - `full` – `{string}` – Full version string, such as "0.9.18".
2473 * - `major` – `{number}` – Major version number, such as "0".
2474 * - `minor` – `{number}` – Minor version number, such as "9".
2475 * - `dot` – `{number}` – Dot version number, such as "18".
2476 * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
2479 full: '1.4.8', // all of these placeholder strings will be replaced by grunt's
2480 major: 1, // package task
2483 codeName: 'ice-manipulation'
2487 function publishExternalAPI(angular) {
2489 'bootstrap': bootstrap,
2496 'injector': createInjector,
2500 'fromJson': fromJson,
2501 'identity': identity,
2502 'isUndefined': isUndefined,
2503 'isDefined': isDefined,
2504 'isString': isString,
2505 'isFunction': isFunction,
2506 'isObject': isObject,
2507 'isNumber': isNumber,
2508 'isElement': isElement,
2512 'lowercase': lowercase,
2513 'uppercase': uppercase,
2514 'callbacks': {counter: 0},
2515 'getTestability': getTestability,
2518 'reloadWithDebugInfo': reloadWithDebugInfo
2521 angularModule = setupModuleLoader(window);
2523 angularModule('ng', ['ngLocale'], ['$provide',
2524 function ngModule($provide) {
2525 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
2527 $$sanitizeUri: $$SanitizeUriProvider
2529 $provide.provider('$compile', $CompileProvider).
2531 a: htmlAnchorDirective,
2532 input: inputDirective,
2533 textarea: inputDirective,
2534 form: formDirective,
2535 script: scriptDirective,
2536 select: selectDirective,
2537 style: styleDirective,
2538 option: optionDirective,
2539 ngBind: ngBindDirective,
2540 ngBindHtml: ngBindHtmlDirective,
2541 ngBindTemplate: ngBindTemplateDirective,
2542 ngClass: ngClassDirective,
2543 ngClassEven: ngClassEvenDirective,
2544 ngClassOdd: ngClassOddDirective,
2545 ngCloak: ngCloakDirective,
2546 ngController: ngControllerDirective,
2547 ngForm: ngFormDirective,
2548 ngHide: ngHideDirective,
2549 ngIf: ngIfDirective,
2550 ngInclude: ngIncludeDirective,
2551 ngInit: ngInitDirective,
2552 ngNonBindable: ngNonBindableDirective,
2553 ngPluralize: ngPluralizeDirective,
2554 ngRepeat: ngRepeatDirective,
2555 ngShow: ngShowDirective,
2556 ngStyle: ngStyleDirective,
2557 ngSwitch: ngSwitchDirective,
2558 ngSwitchWhen: ngSwitchWhenDirective,
2559 ngSwitchDefault: ngSwitchDefaultDirective,
2560 ngOptions: ngOptionsDirective,
2561 ngTransclude: ngTranscludeDirective,
2562 ngModel: ngModelDirective,
2563 ngList: ngListDirective,
2564 ngChange: ngChangeDirective,
2565 pattern: patternDirective,
2566 ngPattern: patternDirective,
2567 required: requiredDirective,
2568 ngRequired: requiredDirective,
2569 minlength: minlengthDirective,
2570 ngMinlength: minlengthDirective,
2571 maxlength: maxlengthDirective,
2572 ngMaxlength: maxlengthDirective,
2573 ngValue: ngValueDirective,
2574 ngModelOptions: ngModelOptionsDirective
2577 ngInclude: ngIncludeFillContentDirective
2579 directive(ngAttributeAliasDirectives).
2580 directive(ngEventDirectives);
2582 $anchorScroll: $AnchorScrollProvider,
2583 $animate: $AnimateProvider,
2584 $animateCss: $CoreAnimateCssProvider,
2585 $$animateQueue: $$CoreAnimateQueueProvider,
2586 $$AnimateRunner: $$CoreAnimateRunnerProvider,
2587 $browser: $BrowserProvider,
2588 $cacheFactory: $CacheFactoryProvider,
2589 $controller: $ControllerProvider,
2590 $document: $DocumentProvider,
2591 $exceptionHandler: $ExceptionHandlerProvider,
2592 $filter: $FilterProvider,
2593 $$forceReflow: $$ForceReflowProvider,
2594 $interpolate: $InterpolateProvider,
2595 $interval: $IntervalProvider,
2596 $http: $HttpProvider,
2597 $httpParamSerializer: $HttpParamSerializerProvider,
2598 $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
2599 $httpBackend: $HttpBackendProvider,
2600 $xhrFactory: $xhrFactoryProvider,
2601 $location: $LocationProvider,
2603 $parse: $ParseProvider,
2604 $rootScope: $RootScopeProvider,
2608 $sceDelegate: $SceDelegateProvider,
2609 $sniffer: $SnifferProvider,
2610 $templateCache: $TemplateCacheProvider,
2611 $templateRequest: $TemplateRequestProvider,
2612 $$testability: $$TestabilityProvider,
2613 $timeout: $TimeoutProvider,
2614 $window: $WindowProvider,
2615 $$rAF: $$RAFProvider,
2616 $$jqLite: $$jqLiteProvider,
2617 $$HashMap: $$HashMapProvider,
2618 $$cookieReader: $$CookieReaderProvider
2624 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2625 * Any commits to this file should be reviewed with security in mind. *
2626 * Changes to this file can potentially create security vulnerabilities. *
2627 * An approval from 2 Core members with history of modifying *
2628 * this file is required. *
2630 * Does the change somehow allow for arbitrary javascript to be executed? *
2631 * Or allows for someone to change the prototype of built-in objects? *
2632 * Or gives undesired access to variables likes document or window? *
2633 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2635 /* global JQLitePrototype: true,
2636 addEventListenerFn: true,
2637 removeEventListenerFn: true,
2642 //////////////////////////////////
2644 //////////////////////////////////
2648 * @name angular.element
2653 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
2655 * If jQuery is available, `angular.element` is an alias for the
2656 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element`
2657 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite."
2659 * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows
2660 * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most
2661 * commonly needed functionality with the goal of having a very small footprint.</div>
2663 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file.
2665 * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or
2666 * jqLite; they are never raw DOM references.</div>
2668 * ## Angular's jqLite
2669 * jqLite provides only the following jQuery methods:
2671 * - [`addClass()`](http://api.jquery.com/addClass/)
2672 * - [`after()`](http://api.jquery.com/after/)
2673 * - [`append()`](http://api.jquery.com/append/)
2674 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
2675 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData
2676 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
2677 * - [`clone()`](http://api.jquery.com/clone/)
2678 * - [`contents()`](http://api.jquery.com/contents/)
2679 * - [`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'.
2680 * - [`data()`](http://api.jquery.com/data/)
2681 * - [`detach()`](http://api.jquery.com/detach/)
2682 * - [`empty()`](http://api.jquery.com/empty/)
2683 * - [`eq()`](http://api.jquery.com/eq/)
2684 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name
2685 * - [`hasClass()`](http://api.jquery.com/hasClass/)
2686 * - [`html()`](http://api.jquery.com/html/)
2687 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors
2688 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
2689 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter
2690 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors
2691 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors
2692 * - [`prepend()`](http://api.jquery.com/prepend/)
2693 * - [`prop()`](http://api.jquery.com/prop/)
2694 * - [`ready()`](http://api.jquery.com/ready/)
2695 * - [`remove()`](http://api.jquery.com/remove/)
2696 * - [`removeAttr()`](http://api.jquery.com/removeAttr/)
2697 * - [`removeClass()`](http://api.jquery.com/removeClass/)
2698 * - [`removeData()`](http://api.jquery.com/removeData/)
2699 * - [`replaceWith()`](http://api.jquery.com/replaceWith/)
2700 * - [`text()`](http://api.jquery.com/text/)
2701 * - [`toggleClass()`](http://api.jquery.com/toggleClass/)
2702 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers.
2703 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter
2704 * - [`val()`](http://api.jquery.com/val/)
2705 * - [`wrap()`](http://api.jquery.com/wrap/)
2707 * ## jQuery/jqLite Extras
2708 * Angular also provides the following additional methods and events to both jQuery and jqLite:
2711 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event
2712 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM
2713 * element before it is removed.
2716 * - `controller(name)` - retrieves the controller of the current element or its parent. By default
2717 * retrieves controller associated with the `ngController` directive. If `name` is provided as
2718 * camelCase directive name, then the controller for this directive will be retrieved (e.g.
2720 * - `injector()` - retrieves the injector of the current element or its parent.
2721 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current
2722 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to
2724 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the
2725 * current element. This getter should be used only on elements that contain a directive which starts a new isolate
2726 * scope. Calling `scope()` on this element always returns the original non-isolate scope.
2727 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled.
2728 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
2729 * parent element is reached.
2731 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
2732 * @returns {Object} jQuery object.
2735 JQLite.expando = 'ng339';
2737 var jqCache = JQLite.cache = {},
2739 addEventListenerFn = function(element, type, fn) {
2740 element.addEventListener(type, fn, false);
2742 removeEventListenerFn = function(element, type, fn) {
2743 element.removeEventListener(type, fn, false);
2747 * !!! This is an undocumented "private" function !!!
2749 JQLite._data = function(node) {
2750 //jQuery always returns an object on cache miss
2751 return this.cache[node[this.expando]] || {};
2754 function jqNextId() { return ++jqId; }
2757 var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
2758 var MOZ_HACK_REGEXP = /^moz([A-Z])/;
2759 var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"};
2760 var jqLiteMinErr = minErr('jqLite');
2763 * Converts snake_case to camelCase.
2764 * Also there is special case for Moz prefix starting with upper case letter.
2765 * @param name Name to normalize
2767 function camelCase(name) {
2769 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
2770 return offset ? letter.toUpperCase() : letter;
2772 replace(MOZ_HACK_REGEXP, 'Moz$1');
2775 var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/;
2776 var HTML_REGEXP = /<|&#?\w+;/;
2777 var TAG_NAME_REGEXP = /<([\w:-]+)/;
2778 var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi;
2781 'option': [1, '<select multiple="multiple">', '</select>'],
2783 'thead': [1, '<table>', '</table>'],
2784 'col': [2, '<table><colgroup>', '</colgroup></table>'],
2785 'tr': [2, '<table><tbody>', '</tbody></table>'],
2786 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'],
2787 '_default': [0, "", ""]
2790 wrapMap.optgroup = wrapMap.option;
2791 wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
2792 wrapMap.th = wrapMap.td;
2795 function jqLiteIsTextNode(html) {
2796 return !HTML_REGEXP.test(html);
2799 function jqLiteAcceptsData(node) {
2800 // The window object can accept data but has no nodeType
2801 // Otherwise we are only interested in elements (1) and documents (9)
2802 var nodeType = node.nodeType;
2803 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
2806 function jqLiteHasData(node) {
2807 for (var key in jqCache[node.ng339]) {
2813 function jqLiteBuildFragment(html, context) {
2815 fragment = context.createDocumentFragment(),
2818 if (jqLiteIsTextNode(html)) {
2819 // Convert non-html into a text node
2820 nodes.push(context.createTextNode(html));
2822 // Convert html into DOM nodes
2823 tmp = tmp || fragment.appendChild(context.createElement("div"));
2824 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase();
2825 wrap = wrapMap[tag] || wrapMap._default;
2826 tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2];
2828 // Descend through wrappers to the right content
2831 tmp = tmp.lastChild;
2834 nodes = concat(nodes, tmp.childNodes);
2836 tmp = fragment.firstChild;
2837 tmp.textContent = "";
2840 // Remove wrapper from fragment
2841 fragment.textContent = "";
2842 fragment.innerHTML = ""; // Clear inner HTML
2843 forEach(nodes, function(node) {
2844 fragment.appendChild(node);
2850 function jqLiteParseHTML(html, context) {
2851 context = context || document;
2854 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) {
2855 return [context.createElement(parsed[1])];
2858 if ((parsed = jqLiteBuildFragment(html, context))) {
2859 return parsed.childNodes;
2866 // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
2867 var jqLiteContains = Node.prototype.contains || function(arg) {
2868 // jshint bitwise: false
2869 return !!(this.compareDocumentPosition(arg) & 16);
2870 // jshint bitwise: true
2873 /////////////////////////////////////////////
2874 function JQLite(element) {
2875 if (element instanceof JQLite) {
2881 if (isString(element)) {
2882 element = trim(element);
2885 if (!(this instanceof JQLite)) {
2886 if (argIsString && element.charAt(0) != '<') {
2887 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
2889 return new JQLite(element);
2893 jqLiteAddNodes(this, jqLiteParseHTML(element));
2895 jqLiteAddNodes(this, element);
2899 function jqLiteClone(element) {
2900 return element.cloneNode(true);
2903 function jqLiteDealoc(element, onlyDescendants) {
2904 if (!onlyDescendants) jqLiteRemoveData(element);
2906 if (element.querySelectorAll) {
2907 var descendants = element.querySelectorAll('*');
2908 for (var i = 0, l = descendants.length; i < l; i++) {
2909 jqLiteRemoveData(descendants[i]);
2914 function jqLiteOff(element, type, fn, unsupported) {
2915 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument');
2917 var expandoStore = jqLiteExpandoStore(element);
2918 var events = expandoStore && expandoStore.events;
2919 var handle = expandoStore && expandoStore.handle;
2921 if (!handle) return; //no listeners registered
2924 for (type in events) {
2925 if (type !== '$destroy') {
2926 removeEventListenerFn(element, type, handle);
2928 delete events[type];
2932 var removeHandler = function(type) {
2933 var listenerFns = events[type];
2934 if (isDefined(fn)) {
2935 arrayRemove(listenerFns || [], fn);
2937 if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) {
2938 removeEventListenerFn(element, type, handle);
2939 delete events[type];
2943 forEach(type.split(' '), function(type) {
2944 removeHandler(type);
2945 if (MOUSE_EVENT_MAP[type]) {
2946 removeHandler(MOUSE_EVENT_MAP[type]);
2952 function jqLiteRemoveData(element, name) {
2953 var expandoId = element.ng339;
2954 var expandoStore = expandoId && jqCache[expandoId];
2958 delete expandoStore.data[name];
2962 if (expandoStore.handle) {
2963 if (expandoStore.events.$destroy) {
2964 expandoStore.handle({}, '$destroy');
2968 delete jqCache[expandoId];
2969 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
2974 function jqLiteExpandoStore(element, createIfNecessary) {
2975 var expandoId = element.ng339,
2976 expandoStore = expandoId && jqCache[expandoId];
2978 if (createIfNecessary && !expandoStore) {
2979 element.ng339 = expandoId = jqNextId();
2980 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined};
2983 return expandoStore;
2987 function jqLiteData(element, key, value) {
2988 if (jqLiteAcceptsData(element)) {
2990 var isSimpleSetter = isDefined(value);
2991 var isSimpleGetter = !isSimpleSetter && key && !isObject(key);
2992 var massGetter = !key;
2993 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter);
2994 var data = expandoStore && expandoStore.data;
2996 if (isSimpleSetter) { // data('key', value)
2999 if (massGetter) { // data()
3002 if (isSimpleGetter) { // data('key')
3003 // don't force creation of expandoStore if it doesn't exist yet
3004 return data && data[key];
3005 } else { // mass-setter: data({key1: val1, key2: val2})
3013 function jqLiteHasClass(element, selector) {
3014 if (!element.getAttribute) return false;
3015 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " ").
3016 indexOf(" " + selector + " ") > -1);
3019 function jqLiteRemoveClass(element, cssClasses) {
3020 if (cssClasses && element.setAttribute) {
3021 forEach(cssClasses.split(' '), function(cssClass) {
3022 element.setAttribute('class', trim(
3023 (" " + (element.getAttribute('class') || '') + " ")
3024 .replace(/[\n\t]/g, " ")
3025 .replace(" " + trim(cssClass) + " ", " "))
3031 function jqLiteAddClass(element, cssClasses) {
3032 if (cssClasses && element.setAttribute) {
3033 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ')
3034 .replace(/[\n\t]/g, " ");
3036 forEach(cssClasses.split(' '), function(cssClass) {
3037 cssClass = trim(cssClass);
3038 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
3039 existingClasses += cssClass + ' ';
3043 element.setAttribute('class', trim(existingClasses));
3048 function jqLiteAddNodes(root, elements) {
3049 // THIS CODE IS VERY HOT. Don't make changes without benchmarking.
3053 // if a Node (the most common case)
3054 if (elements.nodeType) {
3055 root[root.length++] = elements;
3057 var length = elements.length;
3059 // if an Array or NodeList and not a Window
3060 if (typeof length === 'number' && elements.window !== elements) {
3062 for (var i = 0; i < length; i++) {
3063 root[root.length++] = elements[i];
3067 root[root.length++] = elements;
3074 function jqLiteController(element, name) {
3075 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller');
3078 function jqLiteInheritedData(element, name, value) {
3079 // if element is the document object work with the html element instead
3080 // this makes $(document).scope() possible
3081 if (element.nodeType == NODE_TYPE_DOCUMENT) {
3082 element = element.documentElement;
3084 var names = isArray(name) ? name : [name];
3087 for (var i = 0, ii = names.length; i < ii; i++) {
3088 if (isDefined(value = jqLite.data(element, names[i]))) return value;
3091 // If dealing with a document fragment node with a host element, and no parent, use the host
3092 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
3093 // to lookup parent controllers.
3094 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host);
3098 function jqLiteEmpty(element) {
3099 jqLiteDealoc(element, true);
3100 while (element.firstChild) {
3101 element.removeChild(element.firstChild);
3105 function jqLiteRemove(element, keepData) {
3106 if (!keepData) jqLiteDealoc(element);
3107 var parent = element.parentNode;
3108 if (parent) parent.removeChild(element);
3112 function jqLiteDocumentLoaded(action, win) {
3113 win = win || window;
3114 if (win.document.readyState === 'complete') {
3115 // Force the action to be run async for consistent behaviour
3116 // from the action's point of view
3117 // i.e. it will definitely not be in a $apply
3118 win.setTimeout(action);
3120 // No need to unbind this handler as load is only ever called once
3121 jqLite(win).on('load', action);
3125 //////////////////////////////////////////
3126 // Functions which are declared directly.
3127 //////////////////////////////////////////
3128 var JQLitePrototype = JQLite.prototype = {
3129 ready: function(fn) {
3132 function trigger() {
3138 // check if document is already loaded
3139 if (document.readyState === 'complete') {
3140 setTimeout(trigger);
3142 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9
3143 // we can not use jqLite since we are not done loading and jQuery could be loaded later.
3145 JQLite(window).on('load', trigger); // fallback to window.onload for others
3149 toString: function() {
3151 forEach(this, function(e) { value.push('' + e);});
3152 return '[' + value.join(', ') + ']';
3155 eq: function(index) {
3156 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
3165 //////////////////////////////////////////
3166 // Functions iterating getter/setters.
3167 // these functions return self on setter and
3169 //////////////////////////////////////////
3170 var BOOLEAN_ATTR = {};
3171 forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) {
3172 BOOLEAN_ATTR[lowercase(value)] = value;
3174 var BOOLEAN_ELEMENTS = {};
3175 forEach('input,select,option,textarea,button,form,details'.split(','), function(value) {
3176 BOOLEAN_ELEMENTS[value] = true;
3178 var ALIASED_ATTR = {
3179 'ngMinlength': 'minlength',
3180 'ngMaxlength': 'maxlength',
3183 'ngPattern': 'pattern'
3186 function getBooleanAttrName(element, name) {
3187 // check dom last since we will most likely fail on name
3188 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
3190 // booleanAttr is here twice to minimize DOM access
3191 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
3194 function getAliasedAttrName(name) {
3195 return ALIASED_ATTR[name];
3200 removeData: jqLiteRemoveData,
3201 hasData: jqLiteHasData
3202 }, function(fn, name) {
3208 inheritedData: jqLiteInheritedData,
3210 scope: function(element) {
3211 // Can't use jqLiteData here directly so we stay compatible with jQuery!
3212 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']);
3215 isolateScope: function(element) {
3216 // Can't use jqLiteData here directly so we stay compatible with jQuery!
3217 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate');
3220 controller: jqLiteController,
3222 injector: function(element) {
3223 return jqLiteInheritedData(element, '$injector');
3226 removeAttr: function(element, name) {
3227 element.removeAttribute(name);
3230 hasClass: jqLiteHasClass,
3232 css: function(element, name, value) {
3233 name = camelCase(name);
3235 if (isDefined(value)) {
3236 element.style[name] = value;
3238 return element.style[name];
3242 attr: function(element, name, value) {
3243 var nodeType = element.nodeType;
3244 if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) {
3247 var lowercasedName = lowercase(name);
3248 if (BOOLEAN_ATTR[lowercasedName]) {
3249 if (isDefined(value)) {
3251 element[name] = true;
3252 element.setAttribute(name, lowercasedName);
3254 element[name] = false;
3255 element.removeAttribute(lowercasedName);
3258 return (element[name] ||
3259 (element.attributes.getNamedItem(name) || noop).specified)
3263 } else if (isDefined(value)) {
3264 element.setAttribute(name, value);
3265 } else if (element.getAttribute) {
3266 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code
3267 // some elements (e.g. Document) don't have get attribute, so return undefined
3268 var ret = element.getAttribute(name, 2);
3269 // normalize non-existing attributes to undefined (as jQuery)
3270 return ret === null ? undefined : ret;
3274 prop: function(element, name, value) {
3275 if (isDefined(value)) {
3276 element[name] = value;
3278 return element[name];
3286 function getText(element, value) {
3287 if (isUndefined(value)) {
3288 var nodeType = element.nodeType;
3289 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : '';
3291 element.textContent = value;
3295 val: function(element, value) {
3296 if (isUndefined(value)) {
3297 if (element.multiple && nodeName_(element) === 'select') {
3299 forEach(element.options, function(option) {
3300 if (option.selected) {
3301 result.push(option.value || option.text);
3304 return result.length === 0 ? null : result;
3306 return element.value;
3308 element.value = value;
3311 html: function(element, value) {
3312 if (isUndefined(value)) {
3313 return element.innerHTML;
3315 jqLiteDealoc(element, true);
3316 element.innerHTML = value;
3320 }, function(fn, name) {
3322 * Properties: writes return selection, reads return first value
3324 JQLite.prototype[name] = function(arg1, arg2) {
3326 var nodeCount = this.length;
3328 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
3329 // in a way that survives minification.
3330 // jqLiteEmpty takes no arguments but is a setter.
3331 if (fn !== jqLiteEmpty &&
3332 (isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) {
3333 if (isObject(arg1)) {
3335 // we are a write, but the object properties are the key/values
3336 for (i = 0; i < nodeCount; i++) {
3337 if (fn === jqLiteData) {
3338 // data() takes the whole object in jQuery
3342 fn(this[i], key, arg1[key]);
3346 // return self for chaining
3349 // we are a read, so read the first child.
3350 // TODO: do we still need this?
3352 // Only if we have $dv do we iterate over all, otherwise it is just the first element.
3353 var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount;
3354 for (var j = 0; j < jj; j++) {
3355 var nodeValue = fn(this[j], arg1, arg2);
3356 value = value ? value + nodeValue : nodeValue;
3361 // we are a write, so apply to all children
3362 for (i = 0; i < nodeCount; i++) {
3363 fn(this[i], arg1, arg2);
3365 // return self for chaining
3371 function createEventHandler(element, events) {
3372 var eventHandler = function(event, type) {
3373 // jQuery specific api
3374 event.isDefaultPrevented = function() {
3375 return event.defaultPrevented;
3378 var eventFns = events[type || event.type];
3379 var eventFnsLength = eventFns ? eventFns.length : 0;
3381 if (!eventFnsLength) return;
3383 if (isUndefined(event.immediatePropagationStopped)) {
3384 var originalStopImmediatePropagation = event.stopImmediatePropagation;
3385 event.stopImmediatePropagation = function() {
3386 event.immediatePropagationStopped = true;
3388 if (event.stopPropagation) {
3389 event.stopPropagation();
3392 if (originalStopImmediatePropagation) {
3393 originalStopImmediatePropagation.call(event);
3398 event.isImmediatePropagationStopped = function() {
3399 return event.immediatePropagationStopped === true;
3402 // Some events have special handlers that wrap the real handler
3403 var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper;
3405 // Copy event handlers in case event handlers array is modified during execution.
3406 if ((eventFnsLength > 1)) {
3407 eventFns = shallowCopy(eventFns);
3410 for (var i = 0; i < eventFnsLength; i++) {
3411 if (!event.isImmediatePropagationStopped()) {
3412 handlerWrapper(element, event, eventFns[i]);
3417 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all
3418 // events on `element`
3419 eventHandler.elem = element;
3420 return eventHandler;
3423 function defaultHandlerWrapper(element, event, handler) {
3424 handler.call(element, event);
3427 function specialMouseHandlerWrapper(target, event, handler) {
3428 // Refer to jQuery's implementation of mouseenter & mouseleave
3429 // Read about mouseenter and mouseleave:
3430 // http://www.quirksmode.org/js/events_mouse.html#link8
3431 var related = event.relatedTarget;
3432 // For mousenter/leave call the handler if related is outside the target.
3433 // NB: No relatedTarget if the mouse left/entered the browser window
3434 if (!related || (related !== target && !jqLiteContains.call(target, related))) {
3435 handler.call(target, event);
3439 //////////////////////////////////////////
3440 // Functions iterating traversal.
3441 // These functions chain results into a single
3443 //////////////////////////////////////////
3445 removeData: jqLiteRemoveData,
3447 on: function jqLiteOn(element, type, fn, unsupported) {
3448 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters');
3450 // Do not add event handlers to non-elements because they will not be cleaned up.
3451 if (!jqLiteAcceptsData(element)) {
3455 var expandoStore = jqLiteExpandoStore(element, true);
3456 var events = expandoStore.events;
3457 var handle = expandoStore.handle;
3460 handle = expandoStore.handle = createEventHandler(element, events);
3463 // http://jsperf.com/string-indexof-vs-split
3464 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type];
3465 var i = types.length;
3467 var addHandler = function(type, specialHandlerWrapper, noEventListener) {
3468 var eventFns = events[type];
3471 eventFns = events[type] = [];
3472 eventFns.specialHandlerWrapper = specialHandlerWrapper;
3473 if (type !== '$destroy' && !noEventListener) {
3474 addEventListenerFn(element, type, handle);
3483 if (MOUSE_EVENT_MAP[type]) {
3484 addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper);
3485 addHandler(type, undefined, true);
3494 one: function(element, type, fn) {
3495 element = jqLite(element);
3497 //add the listener twice so that when it is called
3498 //you can remove the original function and still be
3499 //able to call element.off(ev, fn) normally
3500 element.on(type, function onFn() {
3501 element.off(type, fn);
3502 element.off(type, onFn);
3504 element.on(type, fn);
3507 replaceWith: function(element, replaceNode) {
3508 var index, parent = element.parentNode;
3509 jqLiteDealoc(element);
3510 forEach(new JQLite(replaceNode), function(node) {
3512 parent.insertBefore(node, index.nextSibling);
3514 parent.replaceChild(node, element);
3520 children: function(element) {
3522 forEach(element.childNodes, function(element) {
3523 if (element.nodeType === NODE_TYPE_ELEMENT) {
3524 children.push(element);
3530 contents: function(element) {
3531 return element.contentDocument || element.childNodes || [];
3534 append: function(element, node) {
3535 var nodeType = element.nodeType;
3536 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return;
3538 node = new JQLite(node);
3540 for (var i = 0, ii = node.length; i < ii; i++) {
3541 var child = node[i];
3542 element.appendChild(child);
3546 prepend: function(element, node) {
3547 if (element.nodeType === NODE_TYPE_ELEMENT) {
3548 var index = element.firstChild;
3549 forEach(new JQLite(node), function(child) {
3550 element.insertBefore(child, index);
3555 wrap: function(element, wrapNode) {
3556 wrapNode = jqLite(wrapNode).eq(0).clone()[0];
3557 var parent = element.parentNode;
3559 parent.replaceChild(wrapNode, element);
3561 wrapNode.appendChild(element);
3564 remove: jqLiteRemove,
3566 detach: function(element) {
3567 jqLiteRemove(element, true);
3570 after: function(element, newElement) {
3571 var index = element, parent = element.parentNode;
3572 newElement = new JQLite(newElement);
3574 for (var i = 0, ii = newElement.length; i < ii; i++) {
3575 var node = newElement[i];
3576 parent.insertBefore(node, index.nextSibling);
3581 addClass: jqLiteAddClass,
3582 removeClass: jqLiteRemoveClass,
3584 toggleClass: function(element, selector, condition) {
3586 forEach(selector.split(' '), function(className) {
3587 var classCondition = condition;
3588 if (isUndefined(classCondition)) {
3589 classCondition = !jqLiteHasClass(element, className);
3591 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className);
3596 parent: function(element) {
3597 var parent = element.parentNode;
3598 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null;
3601 next: function(element) {
3602 return element.nextElementSibling;
3605 find: function(element, selector) {
3606 if (element.getElementsByTagName) {
3607 return element.getElementsByTagName(selector);
3615 triggerHandler: function(element, event, extraParameters) {
3617 var dummyEvent, eventFnsCopy, handlerArgs;
3618 var eventName = event.type || event;
3619 var expandoStore = jqLiteExpandoStore(element);
3620 var events = expandoStore && expandoStore.events;
3621 var eventFns = events && events[eventName];
3624 // Create a dummy event to pass to the handlers
3626 preventDefault: function() { this.defaultPrevented = true; },
3627 isDefaultPrevented: function() { return this.defaultPrevented === true; },
3628 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; },
3629 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; },
3630 stopPropagation: noop,
3635 // If a custom event was provided then extend our dummy event with it
3637 dummyEvent = extend(dummyEvent, event);
3640 // Copy event handlers in case event handlers array is modified during execution.
3641 eventFnsCopy = shallowCopy(eventFns);
3642 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent];
3644 forEach(eventFnsCopy, function(fn) {
3645 if (!dummyEvent.isImmediatePropagationStopped()) {
3646 fn.apply(element, handlerArgs);
3651 }, function(fn, name) {
3653 * chaining functions
3655 JQLite.prototype[name] = function(arg1, arg2, arg3) {
3658 for (var i = 0, ii = this.length; i < ii; i++) {
3659 if (isUndefined(value)) {
3660 value = fn(this[i], arg1, arg2, arg3);
3661 if (isDefined(value)) {
3662 // any function which returns a value needs to be wrapped
3663 value = jqLite(value);
3666 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3));
3669 return isDefined(value) ? value : this;
3672 // bind legacy bind/unbind to on/off
3673 JQLite.prototype.bind = JQLite.prototype.on;
3674 JQLite.prototype.unbind = JQLite.prototype.off;
3678 // Provider for private $$jqLite service
3679 function $$jqLiteProvider() {
3680 this.$get = function $$jqLite() {
3681 return extend(JQLite, {
3682 hasClass: function(node, classes) {
3683 if (node.attr) node = node[0];
3684 return jqLiteHasClass(node, classes);
3686 addClass: function(node, classes) {
3687 if (node.attr) node = node[0];
3688 return jqLiteAddClass(node, classes);
3690 removeClass: function(node, classes) {
3691 if (node.attr) node = node[0];
3692 return jqLiteRemoveClass(node, classes);
3699 * Computes a hash of an 'obj'.
3702 * number is number as string
3703 * object is either result of calling $$hashKey function on the object or uniquely generated id,
3704 * that is also assigned to the $$hashKey property of the object.
3707 * @returns {string} hash string such that the same input will have the same hash string.
3708 * The resulting string key is in 'type:hashKey' format.
3710 function hashKey(obj, nextUidFn) {
3711 var key = obj && obj.$$hashKey;
3714 if (typeof key === 'function') {
3715 key = obj.$$hashKey();
3720 var objType = typeof obj;
3721 if (objType == 'function' || (objType == 'object' && obj !== null)) {
3722 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)();
3724 key = objType + ':' + obj;
3731 * HashMap which can use objects as keys
3733 function HashMap(array, isolatedUid) {
3736 this.nextUid = function() {
3740 forEach(array, this.put, this);
3742 HashMap.prototype = {
3744 * Store key value pair
3745 * @param key key to store can be any type
3746 * @param value value to store can be any type
3748 put: function(key, value) {
3749 this[hashKey(key, this.nextUid)] = value;
3754 * @returns {Object} the value for the key
3756 get: function(key) {
3757 return this[hashKey(key, this.nextUid)];
3761 * Remove the key/value pair
3764 remove: function(key) {
3765 var value = this[key = hashKey(key, this.nextUid)];
3771 var $$HashMapProvider = [function() {
3772 this.$get = [function() {
3780 * @name angular.injector
3784 * Creates an injector object that can be used for retrieving services as well as for
3785 * dependency injection (see {@link guide/di dependency injection}).
3787 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
3788 * {@link angular.module}. The `ng` module must be explicitly added.
3789 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which
3790 * disallows argument name annotation inference.
3791 * @returns {injector} Injector object. See {@link auto.$injector $injector}.
3796 * // create an injector
3797 * var $injector = angular.injector(['ng']);
3799 * // use the injector to kick off your application
3800 * // use the type inference to auto inject arguments, or use implicit injection
3801 * $injector.invoke(function($rootScope, $compile, $document) {
3802 * $compile($document)($rootScope);
3803 * $rootScope.$digest();
3807 * Sometimes you want to get access to the injector of a currently running Angular app
3808 * from outside Angular. Perhaps, you want to inject and compile some markup after the
3809 * application has been bootstrapped. You can do this using the extra `injector()` added
3810 * to JQuery/jqLite elements. See {@link angular.element}.
3812 * *This is fairly rare but could be the case if a third party library is injecting the
3815 * In the following example a new block of HTML containing a `ng-controller`
3816 * directive is added to the end of the document body by JQuery. We then compile and link
3817 * it into the current AngularJS scope.
3820 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>');
3821 * $(document.body).append($div);
3823 * angular.element(document).injector().invoke(function($compile) {
3824 * var scope = angular.element($div).scope();
3825 * $compile($div)(scope);
3836 * Implicit module which gets automatically added to each {@link auto.$injector $injector}.
3839 var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m;
3840 var FN_ARG_SPLIT = /,/;
3841 var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
3842 var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
3843 var $injectorMinErr = minErr('$injector');
3845 function anonFn(fn) {
3846 // For anonymous functions, showing at the very least the function signature can help in
3848 var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
3849 args = fnText.match(FN_ARGS);
3851 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
3856 function annotate(fn, strictDi, name) {
3862 if (typeof fn === 'function') {
3863 if (!($inject = fn.$inject)) {
3867 if (!isString(name) || !name) {
3868 name = fn.name || anonFn(fn);
3870 throw $injectorMinErr('strictdi',
3871 '{0} is not using explicit annotation and cannot be invoked in strict mode', name);
3873 fnText = fn.toString().replace(STRIP_COMMENTS, '');
3874 argDecl = fnText.match(FN_ARGS);
3875 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
3876 arg.replace(FN_ARG, function(all, underscore, name) {
3881 fn.$inject = $inject;
3883 } else if (isArray(fn)) {
3884 last = fn.length - 1;
3885 assertArgFn(fn[last], 'fn');
3886 $inject = fn.slice(0, last);
3888 assertArgFn(fn, 'fn', true);
3893 ///////////////////////////////////////
3901 * `$injector` is used to retrieve object instances as defined by
3902 * {@link auto.$provide provider}, instantiate types, invoke methods,
3905 * The following always holds true:
3908 * var $injector = angular.injector();
3909 * expect($injector.get('$injector')).toBe($injector);
3910 * expect($injector.invoke(function($injector) {
3912 * })).toBe($injector);
3915 * # Injection Function Annotation
3917 * JavaScript does not have annotations, and annotations are needed for dependency injection. The
3918 * following are all valid ways of annotating function with injection arguments and are equivalent.
3921 * // inferred (only works if code not minified/obfuscated)
3922 * $injector.invoke(function(serviceA){});
3925 * function explicit(serviceA) {};
3926 * explicit.$inject = ['serviceA'];
3927 * $injector.invoke(explicit);
3930 * $injector.invoke(['serviceA', function(serviceA){}]);
3935 * In JavaScript calling `toString()` on a function returns the function definition. The definition
3936 * can then be parsed and the function arguments can be extracted. This method of discovering
3937 * annotations is disallowed when the injector is in strict mode.
3938 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the
3941 * ## `$inject` Annotation
3942 * By adding an `$inject` property onto a function the injection parameters can be specified.
3945 * As an array of injection names, where the last item in the array is the function to call.
3950 * @name $injector#get
3953 * Return an instance of the service.
3955 * @param {string} name The name of the instance to retrieve.
3956 * @param {string=} caller An optional string to provide the origin of the function call for error messages.
3957 * @return {*} The instance.
3962 * @name $injector#invoke
3965 * Invoke the method and supply the method arguments from the `$injector`.
3967 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are
3968 * injected according to the {@link guide/di $inject Annotation} rules.
3969 * @param {Object=} self The `this` for the invoked method.
3970 * @param {Object=} locals Optional object. If preset then any argument names are read from this
3971 * object first, before the `$injector` is consulted.
3972 * @returns {*} the value returned by the invoked `fn` function.
3977 * @name $injector#has
3980 * Allows the user to query if the particular service exists.
3982 * @param {string} name Name of the service to query.
3983 * @returns {boolean} `true` if injector has given service.
3988 * @name $injector#instantiate
3990 * Create a new instance of JS type. The method takes a constructor function, invokes the new
3991 * operator, and supplies all of the arguments to the constructor function as specified by the
3992 * constructor annotation.
3994 * @param {Function} Type Annotated constructor function.
3995 * @param {Object=} locals Optional object. If preset then any argument names are read from this
3996 * object first, before the `$injector` is consulted.
3997 * @returns {Object} new instance of `Type`.
4002 * @name $injector#annotate
4005 * Returns an array of service names which the function is requesting for injection. This API is
4006 * used by the injector to determine which services need to be injected into the function when the
4007 * function is invoked. There are three ways in which the function can be annotated with the needed
4012 * The simplest form is to extract the dependencies from the arguments of the function. This is done
4013 * by converting the function into a string using `toString()` method and extracting the argument
4017 * function MyController($scope, $route) {
4022 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
4025 * You can disallow this method by using strict injection mode.
4027 * This method does not work with code minification / obfuscation. For this reason the following
4028 * annotation strategies are supported.
4030 * # The `$inject` property
4032 * If a function has an `$inject` property and its value is an array of strings, then the strings
4033 * represent names of services to be injected into the function.
4036 * var MyController = function(obfuscatedScope, obfuscatedRoute) {
4039 * // Define function dependencies
4040 * MyController['$inject'] = ['$scope', '$route'];
4043 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
4046 * # The array notation
4048 * It is often desirable to inline Injected functions and that's when setting the `$inject` property
4049 * is very inconvenient. In these situations using the array notation to specify the dependencies in
4050 * a way that survives minification is a better choice:
4053 * // We wish to write this (not minification / obfuscation safe)
4054 * injector.invoke(function($compile, $rootScope) {
4058 * // We are forced to write break inlining
4059 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
4062 * tmpFn.$inject = ['$compile', '$rootScope'];
4063 * injector.invoke(tmpFn);
4065 * // To better support inline function the inline annotation is supported
4066 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
4071 * expect(injector.annotate(
4072 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
4073 * ).toEqual(['$compile', '$rootScope']);
4076 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to
4077 * be retrieved as described above.
4079 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference.
4081 * @returns {Array.<string>} The names of the services which the function requires.
4093 * The {@link auto.$provide $provide} service has a number of methods for registering components
4094 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on
4095 * {@link angular.Module}.
4097 * An Angular **service** is a singleton object created by a **service factory**. These **service
4098 * factories** are functions which, in turn, are created by a **service provider**.
4099 * The **service providers** are constructor functions. When instantiated they must contain a
4100 * property called `$get`, which holds the **service factory** function.
4102 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the
4103 * correct **service provider**, instantiating it and then calling its `$get` **service factory**
4104 * function to get the instance of the **service**.
4106 * Often services have no configuration options and there is no need to add methods to the service
4107 * provider. The provider will be no more than a constructor function with a `$get` property. For
4108 * these cases the {@link auto.$provide $provide} service has additional helper methods to register
4109 * services without specifying a provider.
4111 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the
4112 * {@link auto.$injector $injector}
4113 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by
4114 * providers and services.
4115 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by
4116 * services, not providers.
4117 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`,
4118 * that will be wrapped in a **service provider** object, whose `$get` property will contain the
4119 * given factory function.
4120 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class`
4121 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate
4122 * a new object using the given constructor function.
4124 * See the individual methods for more information and examples.
4129 * @name $provide#provider
4132 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions
4133 * are constructor functions, whose instances are responsible for "providing" a factory for a
4136 * Service provider names start with the name of the service they provide followed by `Provider`.
4137 * For example, the {@link ng.$log $log} service has a provider called
4138 * {@link ng.$logProvider $logProvider}.
4140 * Service provider objects can have additional methods which allow configuration of the provider
4141 * and its service. Importantly, you can configure what kind of service is created by the `$get`
4142 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a
4143 * method {@link ng.$logProvider#debugEnabled debugEnabled}
4144 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the
4147 * @param {string} name The name of the instance. NOTE: the provider will be available under `name +
4149 * @param {(Object|function())} provider If the provider is:
4151 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using
4152 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created.
4153 * - `Constructor`: a new instance of the provider will be created using
4154 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`.
4156 * @returns {Object} registered provider instance
4160 * The following example shows how to create a simple event tracking service and register it using
4161 * {@link auto.$provide#provider $provide.provider()}.
4164 * // Define the eventTracker provider
4165 * function EventTrackerProvider() {
4166 * var trackingUrl = '/track';
4168 * // A provider method for configuring where the tracked events should been saved
4169 * this.setTrackingUrl = function(url) {
4170 * trackingUrl = url;
4173 * // The service factory function
4174 * this.$get = ['$http', function($http) {
4175 * var trackedEvents = {};
4177 * // Call this to track an event
4178 * event: function(event) {
4179 * var count = trackedEvents[event] || 0;
4181 * trackedEvents[event] = count;
4184 * // Call this to save the tracked events to the trackingUrl
4185 * save: function() {
4186 * $http.post(trackingUrl, trackedEvents);
4192 * describe('eventTracker', function() {
4195 * beforeEach(module(function($provide) {
4196 * // Register the eventTracker provider
4197 * $provide.provider('eventTracker', EventTrackerProvider);
4200 * beforeEach(module(function(eventTrackerProvider) {
4201 * // Configure eventTracker provider
4202 * eventTrackerProvider.setTrackingUrl('/custom-track');
4205 * it('tracks events', inject(function(eventTracker) {
4206 * expect(eventTracker.event('login')).toEqual(1);
4207 * expect(eventTracker.event('login')).toEqual(2);
4210 * it('saves to the tracking url', inject(function(eventTracker, $http) {
4211 * postSpy = spyOn($http, 'post');
4212 * eventTracker.event('login');
4213 * eventTracker.save();
4214 * expect(postSpy).toHaveBeenCalled();
4215 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track');
4216 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track');
4217 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 });
4225 * @name $provide#factory
4228 * Register a **service factory**, which will be called to return the service instance.
4229 * This is short for registering a service where its provider consists of only a `$get` property,
4230 * which is the given service factory function.
4231 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to
4232 * configure your service in a provider.
4234 * @param {string} name The name of the instance.
4235 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation.
4236 * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`.
4237 * @returns {Object} registered provider instance
4240 * Here is an example of registering a service
4242 * $provide.factory('ping', ['$http', function($http) {
4243 * return function ping() {
4244 * return $http.send('/ping');
4248 * You would then inject and use this service like this:
4250 * someModule.controller('Ctrl', ['ping', function(ping) {
4259 * @name $provide#service
4262 * Register a **service constructor**, which will be invoked with `new` to create the service
4264 * This is short for registering a service where its provider's `$get` property is the service
4265 * constructor function that will be used to instantiate the service instance.
4267 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service
4270 * @param {string} name The name of the instance.
4271 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function)
4272 * that will be instantiated.
4273 * @returns {Object} registered provider instance
4276 * Here is an example of registering a service using
4277 * {@link auto.$provide#service $provide.service(class)}.
4279 * var Ping = function($http) {
4280 * this.$http = $http;
4283 * Ping.$inject = ['$http'];
4285 * Ping.prototype.send = function() {
4286 * return this.$http.get('/ping');
4288 * $provide.service('ping', Ping);
4290 * You would then inject and use this service like this:
4292 * someModule.controller('Ctrl', ['ping', function(ping) {
4301 * @name $provide#value
4304 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a
4305 * number, an array, an object or a function. This is short for registering a service where its
4306 * provider's `$get` property is a factory function that takes no arguments and returns the **value
4309 * Value services are similar to constant services, except that they cannot be injected into a
4310 * module configuration function (see {@link angular.Module#config}) but they can be overridden by
4312 * {@link auto.$provide#decorator decorator}.
4314 * @param {string} name The name of the instance.
4315 * @param {*} value The value.
4316 * @returns {Object} registered provider instance
4319 * Here are some examples of creating value services.
4321 * $provide.value('ADMIN_USER', 'admin');
4323 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 });
4325 * $provide.value('halfOf', function(value) {
4334 * @name $provide#constant
4337 * Register a **constant service**, such as a string, a number, an array, an object or a function,
4338 * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be
4339 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot
4340 * be overridden by an Angular {@link auto.$provide#decorator decorator}.
4342 * @param {string} name The name of the constant.
4343 * @param {*} value The constant value.
4344 * @returns {Object} registered instance
4347 * Here a some examples of creating constants:
4349 * $provide.constant('SHARD_HEIGHT', 306);
4351 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']);
4353 * $provide.constant('double', function(value) {
4362 * @name $provide#decorator
4365 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator
4366 * intercepts the creation of a service, allowing it to override or modify the behaviour of the
4367 * service. The object returned by the decorator may be the original service, or a new service
4368 * object which replaces or wraps and delegates to the original service.
4370 * @param {string} name The name of the service to decorate.
4371 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be
4372 * instantiated and should return the decorated service instance. The function is called using
4373 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable.
4374 * Local injection arguments:
4376 * * `$delegate` - The original service instance, which can be monkey patched, configured,
4377 * decorated or delegated to.
4380 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting
4381 * calls to {@link ng.$log#error $log.warn()}.
4383 * $provide.decorator('$log', ['$delegate', function($delegate) {
4384 * $delegate.warn = $delegate.error;
4391 function createInjector(modulesToLoad, strictDi) {
4392 strictDi = (strictDi === true);
4393 var INSTANTIATING = {},
4394 providerSuffix = 'Provider',
4396 loadedModules = new HashMap([], true),
4399 provider: supportObject(provider),
4400 factory: supportObject(factory),
4401 service: supportObject(service),
4402 value: supportObject(value),
4403 constant: supportObject(constant),
4404 decorator: decorator
4407 providerInjector = (providerCache.$injector =
4408 createInternalInjector(providerCache, function(serviceName, caller) {
4409 if (angular.isString(caller)) {
4412 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
4415 instanceInjector = (instanceCache.$injector =
4416 createInternalInjector(instanceCache, function(serviceName, caller) {
4417 var provider = providerInjector.get(serviceName + providerSuffix, caller);
4418 return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
4422 forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });
4424 return instanceInjector;
4426 ////////////////////////////////////
4428 ////////////////////////////////////
4430 function supportObject(delegate) {
4431 return function(key, value) {
4432 if (isObject(key)) {
4433 forEach(key, reverseParams(delegate));
4435 return delegate(key, value);
4440 function provider(name, provider_) {
4441 assertNotHasOwnProperty(name, 'service');
4442 if (isFunction(provider_) || isArray(provider_)) {
4443 provider_ = providerInjector.instantiate(provider_);
4445 if (!provider_.$get) {
4446 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name);
4448 return providerCache[name + providerSuffix] = provider_;
4451 function enforceReturnValue(name, factory) {
4452 return function enforcedReturnValue() {
4453 var result = instanceInjector.invoke(factory, this);
4454 if (isUndefined(result)) {
4455 throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
4461 function factory(name, factoryFn, enforce) {
4462 return provider(name, {
4463 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
4467 function service(name, constructor) {
4468 return factory(name, ['$injector', function($injector) {
4469 return $injector.instantiate(constructor);
4473 function value(name, val) { return factory(name, valueFn(val), false); }
4475 function constant(name, value) {
4476 assertNotHasOwnProperty(name, 'constant');
4477 providerCache[name] = value;
4478 instanceCache[name] = value;
4481 function decorator(serviceName, decorFn) {
4482 var origProvider = providerInjector.get(serviceName + providerSuffix),
4483 orig$get = origProvider.$get;
4485 origProvider.$get = function() {
4486 var origInstance = instanceInjector.invoke(orig$get, origProvider);
4487 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
4491 ////////////////////////////////////
4493 ////////////////////////////////////
4494 function loadModules(modulesToLoad) {
4495 assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array');
4496 var runBlocks = [], moduleFn;
4497 forEach(modulesToLoad, function(module) {
4498 if (loadedModules.get(module)) return;
4499 loadedModules.put(module, true);
4501 function runInvokeQueue(queue) {
4503 for (i = 0, ii = queue.length; i < ii; i++) {
4504 var invokeArgs = queue[i],
4505 provider = providerInjector.get(invokeArgs[0]);
4507 provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
4512 if (isString(module)) {
4513 moduleFn = angularModule(module);
4514 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
4515 runInvokeQueue(moduleFn._invokeQueue);
4516 runInvokeQueue(moduleFn._configBlocks);
4517 } else if (isFunction(module)) {
4518 runBlocks.push(providerInjector.invoke(module));
4519 } else if (isArray(module)) {
4520 runBlocks.push(providerInjector.invoke(module));
4522 assertArgFn(module, 'module');
4525 if (isArray(module)) {
4526 module = module[module.length - 1];
4528 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) {
4529 // Safari & FF's stack traces don't contain error.message content
4530 // unlike those of Chrome and IE
4531 // So if stack doesn't contain message, we create a new string that contains both.
4532 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
4534 e = e.message + '\n' + e.stack;
4536 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}",
4537 module, e.stack || e.message || e);
4543 ////////////////////////////////////
4544 // internal Injector
4545 ////////////////////////////////////
4547 function createInternalInjector(cache, factory) {
4549 function getService(serviceName, caller) {
4550 if (cache.hasOwnProperty(serviceName)) {
4551 if (cache[serviceName] === INSTANTIATING) {
4552 throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
4553 serviceName + ' <- ' + path.join(' <- '));
4555 return cache[serviceName];
4558 path.unshift(serviceName);
4559 cache[serviceName] = INSTANTIATING;
4560 return cache[serviceName] = factory(serviceName, caller);
4562 if (cache[serviceName] === INSTANTIATING) {
4563 delete cache[serviceName];
4572 function invoke(fn, self, locals, serviceName) {
4573 if (typeof locals === 'string') {
4574 serviceName = locals;
4579 $inject = createInjector.$$annotate(fn, strictDi, serviceName),
4583 for (i = 0, length = $inject.length; i < length; i++) {
4585 if (typeof key !== 'string') {
4586 throw $injectorMinErr('itkn',
4587 'Incorrect injection token! Expected service name as string, got {0}', key);
4590 locals && locals.hasOwnProperty(key)
4592 : getService(key, serviceName)
4599 // http://jsperf.com/angularjs-invoke-apply-vs-switch
4601 return fn.apply(self, args);
4604 function instantiate(Type, locals, serviceName) {
4605 // Check if Type is annotated and use just the given function at n-1 as parameter
4606 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
4607 // Object creation: http://jsperf.com/create-constructor/2
4608 var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
4609 var returnedValue = invoke(Type, instance, locals, serviceName);
4611 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
4616 instantiate: instantiate,
4618 annotate: createInjector.$$annotate,
4619 has: function(name) {
4620 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
4626 createInjector.$$annotate = annotate;
4630 * @name $anchorScrollProvider
4633 * Use `$anchorScrollProvider` to disable automatic scrolling whenever
4634 * {@link ng.$location#hash $location.hash()} changes.
4636 function $AnchorScrollProvider() {
4638 var autoScrollingEnabled = true;
4642 * @name $anchorScrollProvider#disableAutoScrolling
4645 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to
4646 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br />
4647 * Use this method to disable automatic scrolling.
4649 * If automatic scrolling is disabled, one must explicitly call
4650 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the
4653 this.disableAutoScrolling = function() {
4654 autoScrollingEnabled = false;
4659 * @name $anchorScroll
4662 * @requires $location
4663 * @requires $rootScope
4666 * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the
4667 * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified
4669 * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#the-indicated-part-of-the-document).
4671 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
4672 * match any anchor whenever it changes. This can be disabled by calling
4673 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}.
4675 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a
4676 * vertical scroll-offset (either fixed or dynamic).
4678 * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of
4679 * {@link ng.$location#hash $location.hash()} will be used.
4681 * @property {(number|function|jqLite)} yOffset
4682 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed
4683 * positioned elements at the top of the page, such as navbars, headers etc.
4685 * `yOffset` can be specified in various ways:
4686 * - **number**: A fixed number of pixels to be used as offset.<br /><br />
4687 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return
4688 * a number representing the offset (in pixels).<br /><br />
4689 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from
4690 * the top of the page to the element's bottom will be used as offset.<br />
4691 * **Note**: The element will be taken into account only as long as its `position` is set to
4692 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust
4693 * their height and/or positioning according to the viewport's size.
4696 * <div class="alert alert-warning">
4697 * In order for `yOffset` to work properly, scrolling should take place on the document's root and
4698 * not some child element.
4702 <example module="anchorScrollExample">
4703 <file name="index.html">
4704 <div id="scrollArea" ng-controller="ScrollController">
4705 <a ng-click="gotoBottom()">Go to bottom</a>
4706 <a id="bottom"></a> You're at the bottom!
4709 <file name="script.js">
4710 angular.module('anchorScrollExample', [])
4711 .controller('ScrollController', ['$scope', '$location', '$anchorScroll',
4712 function ($scope, $location, $anchorScroll) {
4713 $scope.gotoBottom = function() {
4714 // set the location.hash to the id of
4715 // the element you wish to scroll to.
4716 $location.hash('bottom');
4718 // call $anchorScroll()
4723 <file name="style.css">
4737 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value).
4738 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details.
4741 <example module="anchorScrollOffsetExample">
4742 <file name="index.html">
4743 <div class="fixed-header" ng-controller="headerCtrl">
4744 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]">
4748 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]">
4752 <file name="script.js">
4753 angular.module('anchorScrollOffsetExample', [])
4754 .run(['$anchorScroll', function($anchorScroll) {
4755 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels
4757 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope',
4758 function ($anchorScroll, $location, $scope) {
4759 $scope.gotoAnchor = function(x) {
4760 var newHash = 'anchor' + x;
4761 if ($location.hash() !== newHash) {
4762 // set the $location.hash to `newHash` and
4763 // $anchorScroll will automatically scroll to it
4764 $location.hash('anchor' + x);
4766 // call $anchorScroll() explicitly,
4767 // since $location.hash hasn't changed
4774 <file name="style.css">
4780 border: 2px dashed DarkOrchid;
4781 padding: 10px 10px 200px 10px;
4785 background-color: rgba(0, 0, 0, 0.2);
4788 top: 0; left: 0; right: 0;
4792 display: inline-block;
4798 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
4799 var document = $window.document;
4801 // Helper function to get first anchor from a NodeList
4802 // (using `Array#some()` instead of `angular#forEach()` since it's more performant
4803 // and working in all supported browsers.)
4804 function getFirstAnchor(list) {
4806 Array.prototype.some.call(list, function(element) {
4807 if (nodeName_(element) === 'a') {
4815 function getYOffset() {
4817 var offset = scroll.yOffset;
4819 if (isFunction(offset)) {
4821 } else if (isElement(offset)) {
4822 var elem = offset[0];
4823 var style = $window.getComputedStyle(elem);
4824 if (style.position !== 'fixed') {
4827 offset = elem.getBoundingClientRect().bottom;
4829 } else if (!isNumber(offset)) {
4836 function scrollTo(elem) {
4838 elem.scrollIntoView();
4840 var offset = getYOffset();
4843 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly.
4844 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the
4845 // top of the viewport.
4847 // IF the number of pixels from the top of `elem` to the end of the page's content is less
4848 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some
4849 // way down the page.
4851 // This is often the case for elements near the bottom of the page.
4853 // In such cases we do not need to scroll the whole `offset` up, just the difference between
4854 // the top of the element and the offset, which is enough to align the top of `elem` at the
4855 // desired position.
4856 var elemTop = elem.getBoundingClientRect().top;
4857 $window.scrollBy(0, elemTop - offset);
4860 $window.scrollTo(0, 0);
4864 function scroll(hash) {
4865 hash = isString(hash) ? hash : $location.hash();
4868 // empty hash, scroll to the top of the page
4869 if (!hash) scrollTo(null);
4871 // element with given id
4872 else if ((elm = document.getElementById(hash))) scrollTo(elm);
4874 // first anchor with given name :-D
4875 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm);
4877 // no element and hash == 'top', scroll to the top of the page
4878 else if (hash === 'top') scrollTo(null);
4881 // does not scroll when user clicks on anchor link that is currently on
4882 // (no url change, no $location.hash() change), browser native does scroll
4883 if (autoScrollingEnabled) {
4884 $rootScope.$watch(function autoScrollWatch() {return $location.hash();},
4885 function autoScrollWatchAction(newVal, oldVal) {
4886 // skip the initial scroll if $location.hash is empty
4887 if (newVal === oldVal && newVal === '') return;
4889 jqLiteDocumentLoaded(function() {
4890 $rootScope.$evalAsync(scroll);
4899 var $animateMinErr = minErr('$animate');
4900 var ELEMENT_NODE = 1;
4901 var NG_ANIMATE_CLASSNAME = 'ng-animate';
4903 function mergeClasses(a,b) {
4904 if (!a && !b) return '';
4907 if (isArray(a)) a = a.join(' ');
4908 if (isArray(b)) b = b.join(' ');
4912 function extractElementNode(element) {
4913 for (var i = 0; i < element.length; i++) {
4914 var elm = element[i];
4915 if (elm.nodeType === ELEMENT_NODE) {
4921 function splitClasses(classes) {
4922 if (isString(classes)) {
4923 classes = classes.split(' ');
4926 // Use createMap() to prevent class assumptions involving property names in
4928 var obj = createMap();
4929 forEach(classes, function(klass) {
4930 // sometimes the split leaves empty string values
4931 // incase extra spaces were applied to the options
4939 // if any other type of options value besides an Object value is
4940 // passed into the $animate.method() animation then this helper code
4941 // will be run which will ignore it. While this patch is not the
4942 // greatest solution to this, a lot of existing plugins depend on
4943 // $animate to either call the callback (< 1.2) or return a promise
4944 // that can be changed. This helper function ensures that the options
4945 // are wiped clean incase a callback function is provided.
4946 function prepareAnimateOptions(options) {
4947 return isObject(options)
4952 var $$CoreAnimateRunnerProvider = function() {
4953 this.$get = ['$q', '$$rAF', function($q, $$rAF) {
4954 function AnimateRunner() {}
4955 AnimateRunner.all = noop;
4956 AnimateRunner.chain = noop;
4957 AnimateRunner.prototype = {
4963 then: function(pass, fail) {
4964 return $q(function(resolve) {
4968 }).then(pass, fail);
4971 return AnimateRunner;
4975 // this is prefixed with Core since it conflicts with
4976 // the animateQueueProvider defined in ngAnimate/animateQueue.js
4977 var $$CoreAnimateQueueProvider = function() {
4978 var postDigestQueue = new HashMap();
4979 var postDigestElements = [];
4981 this.$get = ['$$AnimateRunner', '$rootScope',
4982 function($$AnimateRunner, $rootScope) {
4989 push: function(element, event, options, domOperation) {
4990 domOperation && domOperation();
4992 options = options || {};
4993 options.from && element.css(options.from);
4994 options.to && element.css(options.to);
4996 if (options.addClass || options.removeClass) {
4997 addRemoveClassesPostDigest(element, options.addClass, options.removeClass);
5000 return new $$AnimateRunner(); // jshint ignore:line
5005 function updateData(data, classes, value) {
5006 var changed = false;
5008 classes = isString(classes) ? classes.split(' ') :
5009 isArray(classes) ? classes : [];
5010 forEach(classes, function(className) {
5013 data[className] = value;
5020 function handleCSSClassChanges() {
5021 forEach(postDigestElements, function(element) {
5022 var data = postDigestQueue.get(element);
5024 var existing = splitClasses(element.attr('class'));
5027 forEach(data, function(status, className) {
5028 var hasClass = !!existing[className];
5029 if (status !== hasClass) {
5031 toAdd += (toAdd.length ? ' ' : '') + className;
5033 toRemove += (toRemove.length ? ' ' : '') + className;
5038 forEach(element, function(elm) {
5039 toAdd && jqLiteAddClass(elm, toAdd);
5040 toRemove && jqLiteRemoveClass(elm, toRemove);
5042 postDigestQueue.remove(element);
5045 postDigestElements.length = 0;
5049 function addRemoveClassesPostDigest(element, add, remove) {
5050 var data = postDigestQueue.get(element) || {};
5052 var classesAdded = updateData(data, add, true);
5053 var classesRemoved = updateData(data, remove, false);
5055 if (classesAdded || classesRemoved) {
5057 postDigestQueue.put(element, data);
5058 postDigestElements.push(element);
5060 if (postDigestElements.length === 1) {
5061 $rootScope.$$postDigest(handleCSSClassChanges);
5070 * @name $animateProvider
5073 * Default implementation of $animate that doesn't perform any animations, instead just
5074 * synchronously performs DOM updates and resolves the returned runner promise.
5076 * In order to enable animations the `ngAnimate` module has to be loaded.
5078 * To see the functional implementation check out `src/ngAnimate/animate.js`.
5080 var $AnimateProvider = ['$provide', function($provide) {
5081 var provider = this;
5083 this.$$registeredAnimations = Object.create(null);
5087 * @name $animateProvider#register
5090 * Registers a new injectable animation factory function. The factory function produces the
5091 * animation object which contains callback functions for each event that is expected to be
5094 * * `eventFn`: `function(element, ... , doneFunction, options)`
5095 * The element to animate, the `doneFunction` and the options fed into the animation. Depending
5096 * on the type of animation additional arguments will be injected into the animation function. The
5097 * list below explains the function signatures for the different animation methods:
5099 * - setClass: function(element, addedClasses, removedClasses, doneFunction, options)
5100 * - addClass: function(element, addedClasses, doneFunction, options)
5101 * - removeClass: function(element, removedClasses, doneFunction, options)
5102 * - enter, leave, move: function(element, doneFunction, options)
5103 * - animate: function(element, fromStyles, toStyles, doneFunction, options)
5105 * Make sure to trigger the `doneFunction` once the animation is fully complete.
5109 * //enter, leave, move signature
5110 * eventFn : function(element, done, options) {
5111 * //code to run the animation
5112 * //once complete, then run done()
5113 * return function endFunction(wasCancelled) {
5114 * //code to cancel the animation
5120 * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to).
5121 * @param {Function} factory The factory function that will be executed to return the animation
5124 this.register = function(name, factory) {
5125 if (name && name.charAt(0) !== '.') {
5126 throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name);
5129 var key = name + '-animation';
5130 provider.$$registeredAnimations[name.substr(1)] = key;
5131 $provide.factory(key, factory);
5136 * @name $animateProvider#classNameFilter
5139 * Sets and/or returns the CSS class regular expression that is checked when performing
5140 * an animation. Upon bootstrap the classNameFilter value is not set at all and will
5141 * therefore enable $animate to attempt to perform an animation on any element that is triggered.
5142 * When setting the `classNameFilter` value, animations will only be performed on elements
5143 * that successfully match the filter expression. This in turn can boost performance
5144 * for low-powered devices as well as applications containing a lot of structural operations.
5145 * @param {RegExp=} expression The className expression which will be checked against all animations
5146 * @return {RegExp} The current CSS className expression value. If null then there is no expression value
5148 this.classNameFilter = function(expression) {
5149 if (arguments.length === 1) {
5150 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null;
5151 if (this.$$classNameFilter) {
5152 var reservedRegex = new RegExp("(\\s+|\\/)" + NG_ANIMATE_CLASSNAME + "(\\s+|\\/)");
5153 if (reservedRegex.test(this.$$classNameFilter.toString())) {
5154 throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME);
5159 return this.$$classNameFilter;
5162 this.$get = ['$$animateQueue', function($$animateQueue) {
5163 function domInsert(element, parentElement, afterElement) {
5164 // if for some reason the previous element was removed
5165 // from the dom sometime before this code runs then let's
5166 // just stick to using the parent element as the anchor
5168 var afterNode = extractElementNode(afterElement);
5169 if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) {
5170 afterElement = null;
5173 afterElement ? afterElement.after(element) : parentElement.prepend(element);
5179 * @description The $animate service exposes a series of DOM utility methods that provide support
5180 * for animation hooks. The default behavior is the application of DOM operations, however,
5181 * when an animation is detected (and animations are enabled), $animate will do the heavy lifting
5182 * to ensure that animation runs with the triggered DOM operation.
5184 * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't
5185 * included and only when it is active then the animation hooks that `$animate` triggers will be
5186 * functional. Once active then all structural `ng-` directives will trigger animations as they perform
5187 * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`,
5188 * `ngShow`, `ngHide` and `ngMessages` also provide support for animations.
5190 * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives.
5192 * To learn more about enabling animation support, click here to visit the
5193 * {@link ngAnimate ngAnimate module page}.
5196 // we don't call it directly since non-existant arguments may
5197 // be interpreted as null within the sub enabled function
5204 * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...)
5205 * has fired on the given element or among any of its children. Once the listener is fired, the provided callback
5206 * is fired with the following params:
5209 * $animate.on('enter', container,
5210 * function callback(element, phase) {
5211 * // cool we detected an enter animation within the container
5216 * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...)
5217 * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself
5218 * as well as among its children
5219 * @param {Function} callback the callback function that will be fired when the listener is triggered
5221 * The arguments present in the callback function are:
5222 * * `element` - The captured DOM element that the animation was fired on.
5223 * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends).
5225 on: $$animateQueue.on,
5230 * @name $animate#off
5232 * @description Deregisters an event listener based on the event which has been associated with the provided element. This method
5233 * can be used in three different ways depending on the arguments:
5236 * // remove all the animation event listeners listening for `enter`
5237 * $animate.off('enter');
5239 * // remove all the animation event listeners listening for `enter` on the given element and its children
5240 * $animate.off('enter', container);
5242 * // remove the event listener function provided by `listenerFn` that is set
5243 * // to listen for `enter` on the given `element` as well as its children
5244 * $animate.off('enter', container, callback);
5247 * @param {string} event the animation event (e.g. enter, leave, move, addClass, removeClass, etc...)
5248 * @param {DOMElement=} container the container element the event listener was placed on
5249 * @param {Function=} callback the callback function that was registered as the listener
5251 off: $$animateQueue.off,
5255 * @name $animate#pin
5257 * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists
5258 * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the
5259 * element despite being outside the realm of the application or within another application. Say for example if the application
5260 * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated
5261 * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind
5262 * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association.
5264 * Note that this feature is only active when the `ngAnimate` module is used.
5266 * @param {DOMElement} element the external element that will be pinned
5267 * @param {DOMElement} parentElement the host parent element that will be associated with the external element
5269 pin: $$animateQueue.pin,
5274 * @name $animate#enabled
5276 * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This
5277 * function can be called in four ways:
5280 * // returns true or false
5281 * $animate.enabled();
5283 * // changes the enabled state for all animations
5284 * $animate.enabled(false);
5285 * $animate.enabled(true);
5287 * // returns true or false if animations are enabled for an element
5288 * $animate.enabled(element);
5290 * // changes the enabled state for an element and its children
5291 * $animate.enabled(element, true);
5292 * $animate.enabled(element, false);
5295 * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state
5296 * @param {boolean=} enabled whether or not the animations will be enabled for the element
5298 * @return {boolean} whether or not animations are enabled
5300 enabled: $$animateQueue.enabled,
5304 * @name $animate#cancel
5306 * @description Cancels the provided animation.
5308 * @param {Promise} animationPromise The animation promise that is returned when an animation is started.
5310 cancel: function(runner) {
5311 runner.end && runner.end();
5317 * @name $animate#enter
5319 * @description Inserts the element into the DOM either after the `after` element (if provided) or
5320 * as the first child within the `parent` element and then triggers an animation.
5321 * A promise is returned that will be resolved during the next digest once the animation
5324 * @param {DOMElement} element the element which will be inserted into the DOM
5325 * @param {DOMElement} parent the parent element which will append the element as
5326 * a child (so long as the after element is not present)
5327 * @param {DOMElement=} after the sibling element after which the element will be appended
5328 * @param {object=} options an optional collection of options/styles that will be applied to the element
5330 * @return {Promise} the animation callback promise
5332 enter: function(element, parent, after, options) {
5333 parent = parent && jqLite(parent);
5334 after = after && jqLite(after);
5335 parent = parent || after.parent();
5336 domInsert(element, parent, after);
5337 return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options));
5343 * @name $animate#move
5345 * @description Inserts (moves) the element into its new position in the DOM either after
5346 * the `after` element (if provided) or as the first child within the `parent` element
5347 * and then triggers an animation. A promise is returned that will be resolved
5348 * during the next digest once the animation has completed.
5350 * @param {DOMElement} element the element which will be moved into the new DOM position
5351 * @param {DOMElement} parent the parent element which will append the element as
5352 * a child (so long as the after element is not present)
5353 * @param {DOMElement=} after the sibling element after which the element will be appended
5354 * @param {object=} options an optional collection of options/styles that will be applied to the element
5356 * @return {Promise} the animation callback promise
5358 move: function(element, parent, after, options) {
5359 parent = parent && jqLite(parent);
5360 after = after && jqLite(after);
5361 parent = parent || after.parent();
5362 domInsert(element, parent, after);
5363 return $$animateQueue.push(element, 'move', prepareAnimateOptions(options));
5368 * @name $animate#leave
5370 * @description Triggers an animation and then removes the element from the DOM.
5371 * When the function is called a promise is returned that will be resolved during the next
5372 * digest once the animation has completed.
5374 * @param {DOMElement} element the element which will be removed from the DOM
5375 * @param {object=} options an optional collection of options/styles that will be applied to the element
5377 * @return {Promise} the animation callback promise
5379 leave: function(element, options) {
5380 return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() {
5387 * @name $animate#addClass
5390 * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon
5391 * execution, the addClass operation will only be handled after the next digest and it will not trigger an
5392 * animation if element already contains the CSS class or if the class is removed at a later step.
5393 * Note that class-based animations are treated differently compared to structural animations
5394 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5395 * depending if CSS or JavaScript animations are used.
5397 * @param {DOMElement} element the element which the CSS classes will be applied to
5398 * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces)
5399 * @param {object=} options an optional collection of options/styles that will be applied to the element
5401 * @return {Promise} the animation callback promise
5403 addClass: function(element, className, options) {
5404 options = prepareAnimateOptions(options);
5405 options.addClass = mergeClasses(options.addclass, className);
5406 return $$animateQueue.push(element, 'addClass', options);
5411 * @name $animate#removeClass
5414 * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon
5415 * execution, the removeClass operation will only be handled after the next digest and it will not trigger an
5416 * animation if element does not contain the CSS class or if the class is added at a later step.
5417 * Note that class-based animations are treated differently compared to structural animations
5418 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5419 * depending if CSS or JavaScript animations are used.
5421 * @param {DOMElement} element the element which the CSS classes will be applied to
5422 * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces)
5423 * @param {object=} options an optional collection of options/styles that will be applied to the element
5425 * @return {Promise} the animation callback promise
5427 removeClass: function(element, className, options) {
5428 options = prepareAnimateOptions(options);
5429 options.removeClass = mergeClasses(options.removeClass, className);
5430 return $$animateQueue.push(element, 'removeClass', options);
5435 * @name $animate#setClass
5438 * @description Performs both the addition and removal of a CSS classes on an element and (during the process)
5439 * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and
5440 * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has
5441 * passed. Note that class-based animations are treated differently compared to structural animations
5442 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5443 * depending if CSS or JavaScript animations are used.
5445 * @param {DOMElement} element the element which the CSS classes will be applied to
5446 * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces)
5447 * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces)
5448 * @param {object=} options an optional collection of options/styles that will be applied to the element
5450 * @return {Promise} the animation callback promise
5452 setClass: function(element, add, remove, options) {
5453 options = prepareAnimateOptions(options);
5454 options.addClass = mergeClasses(options.addClass, add);
5455 options.removeClass = mergeClasses(options.removeClass, remove);
5456 return $$animateQueue.push(element, 'setClass', options);
5461 * @name $animate#animate
5464 * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element.
5465 * If any detected CSS transition, keyframe or JavaScript matches the provided className value then the animation will take
5466 * on the provided styles. For example, if a transition animation is set for the given className then the provided from and
5467 * to styles will be applied alongside the given transition. If a JavaScript animation is detected then the provided styles
5468 * will be given in as function paramters into the `animate` method (or as apart of the `options` parameter).
5470 * @param {DOMElement} element the element which the CSS styles will be applied to
5471 * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation.
5472 * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation.
5473 * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If
5474 * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element.
5475 * (Note that if no animation is detected then this value will not be appplied to the element.)
5476 * @param {object=} options an optional collection of options/styles that will be applied to the element
5478 * @return {Promise} the animation callback promise
5480 animate: function(element, from, to, className, options) {
5481 options = prepareAnimateOptions(options);
5482 options.from = options.from ? extend(options.from, from) : from;
5483 options.to = options.to ? extend(options.to, to) : to;
5485 className = className || 'ng-inline-animate';
5486 options.tempClasses = mergeClasses(options.tempClasses, className);
5487 return $$animateQueue.push(element, 'animate', options);
5499 * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included,
5500 * then the `$animateCss` service will actually perform animations.
5502 * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}.
5504 var $CoreAnimateCssProvider = function() {
5505 this.$get = ['$$rAF', '$q', function($$rAF, $q) {
5507 var RAFPromise = function() {};
5508 RAFPromise.prototype = {
5509 done: function(cancel) {
5510 this.defer && this.defer[cancel === true ? 'reject' : 'resolve']();
5515 cancel: function() {
5518 getPromise: function() {
5520 this.defer = $q.defer();
5522 return this.defer.promise;
5524 then: function(f1,f2) {
5525 return this.getPromise().then(f1,f2);
5527 'catch': function(f1) {
5528 return this.getPromise()['catch'](f1);
5530 'finally': function(f1) {
5531 return this.getPromise()['finally'](f1);
5535 return function(element, options) {
5536 // there is no point in applying the styles since
5537 // there is no animation that goes on at all in
5538 // this version of $animateCss.
5539 if (options.cleanupStyles) {
5540 options.from = options.to = null;
5544 element.css(options.from);
5545 options.from = null;
5548 var closed, runner = new RAFPromise();
5566 if (options.addClass) {
5567 element.addClass(options.addClass);
5568 options.addClass = null;
5570 if (options.removeClass) {
5571 element.removeClass(options.removeClass);
5572 options.removeClass = null;
5575 element.css(options.to);
5583 /* global stripHash: true */
5586 * ! This is a private undocumented service !
5591 * This object has two goals:
5593 * - hide all the global state in the browser caused by the window object
5594 * - abstract away all the browser specific features and inconsistencies
5596 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser`
5597 * service, which can be used for convenient testing of the application without the interaction with
5598 * the real browser apis.
5601 * @param {object} window The global window object.
5602 * @param {object} document jQuery wrapped document.
5603 * @param {object} $log window.console or an object with the same interface.
5604 * @param {object} $sniffer $sniffer service
5606 function Browser(window, document, $log, $sniffer) {
5608 rawDocument = document[0],
5609 location = window.location,
5610 history = window.history,
5611 setTimeout = window.setTimeout,
5612 clearTimeout = window.clearTimeout,
5613 pendingDeferIds = {};
5615 self.isMock = false;
5617 var outstandingRequestCount = 0;
5618 var outstandingRequestCallbacks = [];
5620 // TODO(vojta): remove this temporary api
5621 self.$$completeOutstandingRequest = completeOutstandingRequest;
5622 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
5625 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
5626 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
5628 function completeOutstandingRequest(fn) {
5630 fn.apply(null, sliceArgs(arguments, 1));
5632 outstandingRequestCount--;
5633 if (outstandingRequestCount === 0) {
5634 while (outstandingRequestCallbacks.length) {
5636 outstandingRequestCallbacks.pop()();
5645 function getHash(url) {
5646 var index = url.indexOf('#');
5647 return index === -1 ? '' : url.substr(index);
5652 * Note: this method is used only by scenario runner
5653 * TODO(vojta): prefix this method with $$ ?
5654 * @param {function()} callback Function that will be called when no outstanding request
5656 self.notifyWhenNoOutstandingRequests = function(callback) {
5657 if (outstandingRequestCount === 0) {
5660 outstandingRequestCallbacks.push(callback);
5664 //////////////////////////////////////////////////////////////
5666 //////////////////////////////////////////////////////////////
5668 var cachedState, lastHistoryState,
5669 lastBrowserUrl = location.href,
5670 baseElement = document.find('base'),
5671 pendingLocation = null;
5674 lastHistoryState = cachedState;
5677 * @name $browser#url
5681 * Without any argument, this method just returns current value of location.href.
5684 * With at least one argument, this method sets url to new value.
5685 * If html5 history api supported, pushState/replaceState is used, otherwise
5686 * location.href/location.replace is used.
5687 * Returns its own instance to allow chaining
5689 * NOTE: this api is intended for use only by the $location service. Please use the
5690 * {@link ng.$location $location service} to change url.
5692 * @param {string} url New url (when used as setter)
5693 * @param {boolean=} replace Should new url replace current history record?
5694 * @param {object=} state object to use with pushState/replaceState
5696 self.url = function(url, replace, state) {
5697 // In modern browsers `history.state` is `null` by default; treating it separately
5698 // from `undefined` would cause `$browser.url('/foo')` to change `history.state`
5699 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here.
5700 if (isUndefined(state)) {
5704 // Android Browser BFCache causes location, history reference to become stale.
5705 if (location !== window.location) location = window.location;
5706 if (history !== window.history) history = window.history;
5710 var sameState = lastHistoryState === state;
5712 // Don't change anything if previous and current URLs and states match. This also prevents
5713 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
5714 // See https://github.com/angular/angular.js/commit/ffb2701
5715 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) {
5718 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
5719 lastBrowserUrl = url;
5720 lastHistoryState = state;
5721 // Don't use history API if only the hash changed
5722 // due to a bug in IE10/IE11 which leads
5723 // to not firing a `hashchange` nor `popstate` event
5724 // in some cases (see #9143).
5725 if ($sniffer.history && (!sameBase || !sameState)) {
5726 history[replace ? 'replaceState' : 'pushState'](state, '', url);
5728 // Do the assignment again so that those two variables are referentially identical.
5729 lastHistoryState = cachedState;
5731 if (!sameBase || pendingLocation) {
5732 pendingLocation = url;
5735 location.replace(url);
5736 } else if (!sameBase) {
5737 location.href = url;
5739 location.hash = getHash(url);
5741 if (location.href !== url) {
5742 pendingLocation = url;
5748 // - pendingLocation is needed as browsers don't allow to read out
5749 // the new location.href if a reload happened or if there is a bug like in iOS 9 (see
5750 // https://openradar.appspot.com/22186109).
5751 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
5752 return pendingLocation || location.href.replace(/%27/g,"'");
5757 * @name $browser#state
5760 * This method is a getter.
5762 * Return history.state or null if history.state is undefined.
5764 * @returns {object} state
5766 self.state = function() {
5770 var urlChangeListeners = [],
5771 urlChangeInit = false;
5773 function cacheStateAndFireUrlChange() {
5774 pendingLocation = null;
5779 function getCurrentState() {
5781 return history.state;
5783 // MSIE can reportedly throw when there is no state (UNCONFIRMED).
5787 // This variable should be used *only* inside the cacheState function.
5788 var lastCachedState = null;
5789 function cacheState() {
5790 // This should be the only place in $browser where `history.state` is read.
5791 cachedState = getCurrentState();
5792 cachedState = isUndefined(cachedState) ? null : cachedState;
5794 // Prevent callbacks fo fire twice if both hashchange & popstate were fired.
5795 if (equals(cachedState, lastCachedState)) {
5796 cachedState = lastCachedState;
5798 lastCachedState = cachedState;
5801 function fireUrlChange() {
5802 if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) {
5806 lastBrowserUrl = self.url();
5807 lastHistoryState = cachedState;
5808 forEach(urlChangeListeners, function(listener) {
5809 listener(self.url(), cachedState);
5814 * @name $browser#onUrlChange
5817 * Register callback function that will be called, when url changes.
5819 * It's only called when the url is changed from outside of angular:
5820 * - user types different url into address bar
5821 * - user clicks on history (forward/back) button
5822 * - user clicks on a link
5824 * It's not called when url is changed by $browser.url() method
5826 * The listener gets called with new url as parameter.
5828 * NOTE: this api is intended for use only by the $location service. Please use the
5829 * {@link ng.$location $location service} to monitor url changes in angular apps.
5831 * @param {function(string)} listener Listener function to be called when url changes.
5832 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous.
5834 self.onUrlChange = function(callback) {
5835 // TODO(vojta): refactor to use node's syntax for events
5836 if (!urlChangeInit) {
5837 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera)
5838 // don't fire popstate when user change the address bar and don't fire hashchange when url
5839 // changed by push/replaceState
5841 // html5 history api - popstate event
5842 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange);
5844 jqLite(window).on('hashchange', cacheStateAndFireUrlChange);
5846 urlChangeInit = true;
5849 urlChangeListeners.push(callback);
5855 * Remove popstate and hashchange handler from window.
5857 * NOTE: this api is intended for use only by $rootScope.
5859 self.$$applicationDestroyed = function() {
5860 jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange);
5864 * Checks whether the url has changed outside of Angular.
5865 * Needs to be exported to be able to check for changes that have been done in sync,
5866 * as hashchange/popstate events fire in async.
5868 self.$$checkUrlChange = fireUrlChange;
5870 //////////////////////////////////////////////////////////////
5872 //////////////////////////////////////////////////////////////
5875 * @name $browser#baseHref
5878 * Returns current <base href>
5879 * (always relative - without domain)
5881 * @returns {string} The current base href
5883 self.baseHref = function() {
5884 var href = baseElement.attr('href');
5885 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : '';
5889 * @name $browser#defer
5890 * @param {function()} fn A function, who's execution should be deferred.
5891 * @param {number=} [delay=0] of milliseconds to defer the function execution.
5892 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
5895 * Executes a fn asynchronously via `setTimeout(fn, delay)`.
5897 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
5898 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed
5899 * via `$browser.defer.flush()`.
5902 self.defer = function(fn, delay) {
5904 outstandingRequestCount++;
5905 timeoutId = setTimeout(function() {
5906 delete pendingDeferIds[timeoutId];
5907 completeOutstandingRequest(fn);
5909 pendingDeferIds[timeoutId] = true;
5915 * @name $browser#defer.cancel
5918 * Cancels a deferred task identified with `deferId`.
5920 * @param {*} deferId Token returned by the `$browser.defer` function.
5921 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
5924 self.defer.cancel = function(deferId) {
5925 if (pendingDeferIds[deferId]) {
5926 delete pendingDeferIds[deferId];
5927 clearTimeout(deferId);
5928 completeOutstandingRequest(noop);
5936 function $BrowserProvider() {
5937 this.$get = ['$window', '$log', '$sniffer', '$document',
5938 function($window, $log, $sniffer, $document) {
5939 return new Browser($window, $document, $log, $sniffer);
5945 * @name $cacheFactory
5948 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to
5953 * var cache = $cacheFactory('cacheId');
5954 * expect($cacheFactory.get('cacheId')).toBe(cache);
5955 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined();
5957 * cache.put("key", "value");
5958 * cache.put("another key", "another value");
5960 * // We've specified no options on creation
5961 * expect(cache.info()).toEqual({id: 'cacheId', size: 2});
5966 * @param {string} cacheId Name or id of the newly created cache.
5967 * @param {object=} options Options object that specifies the cache behavior. Properties:
5969 * - `{number=}` `capacity` — turns the cache into LRU cache.
5971 * @returns {object} Newly created cache object with the following set of methods:
5973 * - `{object}` `info()` — Returns id, size, and options of cache.
5974 * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns
5976 * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss.
5977 * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache.
5978 * - `{void}` `removeAll()` — Removes all cached values.
5979 * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory.
5982 <example module="cacheExampleApp">
5983 <file name="index.html">
5984 <div ng-controller="CacheController">
5985 <input ng-model="newCacheKey" placeholder="Key">
5986 <input ng-model="newCacheValue" placeholder="Value">
5987 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button>
5989 <p ng-if="keys.length">Cached Values</p>
5990 <div ng-repeat="key in keys">
5991 <span ng-bind="key"></span>
5993 <b ng-bind="cache.get(key)"></b>
5997 <div ng-repeat="(key, value) in cache.info()">
5998 <span ng-bind="key"></span>
6000 <b ng-bind="value"></b>
6004 <file name="script.js">
6005 angular.module('cacheExampleApp', []).
6006 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) {
6008 $scope.cache = $cacheFactory('cacheId');
6009 $scope.put = function(key, value) {
6010 if (angular.isUndefined($scope.cache.get(key))) {
6011 $scope.keys.push(key);
6013 $scope.cache.put(key, angular.isUndefined(value) ? null : value);
6017 <file name="style.css">
6024 function $CacheFactoryProvider() {
6026 this.$get = function() {
6029 function cacheFactory(cacheId, options) {
6030 if (cacheId in caches) {
6031 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId);
6035 stats = extend({}, options, {id: cacheId}),
6037 capacity = (options && options.capacity) || Number.MAX_VALUE,
6038 lruHash = createMap(),
6044 * @name $cacheFactory.Cache
6047 * A cache object used to store and retrieve data, primarily used by
6048 * {@link $http $http} and the {@link ng.directive:script script} directive to cache
6049 * templates and other data.
6052 * angular.module('superCache')
6053 * .factory('superCache', ['$cacheFactory', function($cacheFactory) {
6054 * return $cacheFactory('super-cache');
6061 * it('should behave like a cache', inject(function(superCache) {
6062 * superCache.put('key', 'value');
6063 * superCache.put('another key', 'another value');
6065 * expect(superCache.info()).toEqual({
6066 * id: 'super-cache',
6070 * superCache.remove('another key');
6071 * expect(superCache.get('another key')).toBeUndefined();
6073 * superCache.removeAll();
6074 * expect(superCache.info()).toEqual({
6075 * id: 'super-cache',
6081 return caches[cacheId] = {
6085 * @name $cacheFactory.Cache#put
6089 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be
6090 * retrieved later, and incrementing the size of the cache if the key was not already
6091 * present in the cache. If behaving like an LRU cache, it will also remove stale
6092 * entries from the set.
6094 * It will not insert undefined values into the cache.
6096 * @param {string} key the key under which the cached data is stored.
6097 * @param {*} value the value to store alongside the key. If it is undefined, the key
6098 * will not be stored.
6099 * @returns {*} the value stored.
6101 put: function(key, value) {
6102 if (isUndefined(value)) return;
6103 if (capacity < Number.MAX_VALUE) {
6104 var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
6109 if (!(key in data)) size++;
6112 if (size > capacity) {
6113 this.remove(staleEnd.key);
6121 * @name $cacheFactory.Cache#get
6125 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object.
6127 * @param {string} key the key of the data to be retrieved
6128 * @returns {*} the value stored.
6130 get: function(key) {
6131 if (capacity < Number.MAX_VALUE) {
6132 var lruEntry = lruHash[key];
6134 if (!lruEntry) return;
6145 * @name $cacheFactory.Cache#remove
6149 * Removes an entry from the {@link $cacheFactory.Cache Cache} object.
6151 * @param {string} key the key of the entry to be removed
6153 remove: function(key) {
6154 if (capacity < Number.MAX_VALUE) {
6155 var lruEntry = lruHash[key];
6157 if (!lruEntry) return;
6159 if (lruEntry == freshEnd) freshEnd = lruEntry.p;
6160 if (lruEntry == staleEnd) staleEnd = lruEntry.n;
6161 link(lruEntry.n,lruEntry.p);
6163 delete lruHash[key];
6166 if (!(key in data)) return;
6175 * @name $cacheFactory.Cache#removeAll
6179 * Clears the cache object of any entries.
6181 removeAll: function() {
6184 lruHash = createMap();
6185 freshEnd = staleEnd = null;
6191 * @name $cacheFactory.Cache#destroy
6195 * Destroys the {@link $cacheFactory.Cache Cache} object entirely,
6196 * removing it from the {@link $cacheFactory $cacheFactory} set.
6198 destroy: function() {
6202 delete caches[cacheId];
6208 * @name $cacheFactory.Cache#info
6212 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}.
6214 * @returns {object} an object with the following properties:
6216 * <li>**id**: the id of the cache instance</li>
6217 * <li>**size**: the number of entries kept in the cache instance</li>
6218 * <li>**...**: any additional properties from the options object when creating the
6223 return extend({}, stats, {size: size});
6229 * makes the `entry` the freshEnd of the LRU linked list
6231 function refresh(entry) {
6232 if (entry != freshEnd) {
6235 } else if (staleEnd == entry) {
6239 link(entry.n, entry.p);
6240 link(entry, freshEnd);
6248 * bidirectionally links two entries of the LRU linked list
6250 function link(nextEntry, prevEntry) {
6251 if (nextEntry != prevEntry) {
6252 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify
6253 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify
6261 * @name $cacheFactory#info
6264 * Get information about all the caches that have been created
6266 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info`
6268 cacheFactory.info = function() {
6270 forEach(caches, function(cache, cacheId) {
6271 info[cacheId] = cache.info();
6279 * @name $cacheFactory#get
6282 * Get access to a cache object by the `cacheId` used when it was created.
6284 * @param {string} cacheId Name or id of a cache to access.
6285 * @returns {object} Cache object identified by the cacheId or undefined if no such cache.
6287 cacheFactory.get = function(cacheId) {
6288 return caches[cacheId];
6292 return cacheFactory;
6298 * @name $templateCache
6301 * The first time a template is used, it is loaded in the template cache for quick retrieval. You
6302 * can load templates directly into the cache in a `script` tag, or by consuming the
6303 * `$templateCache` service directly.
6305 * Adding via the `script` tag:
6308 * <script type="text/ng-template" id="templateId.html">
6309 * <p>This is the content of the template</p>
6313 * **Note:** the `script` tag containing the template does not need to be included in the `head` of
6314 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE,
6315 * element with ng-app attribute), otherwise the template will be ignored.
6317 * Adding via the `$templateCache` service:
6320 * var myApp = angular.module('myApp', []);
6321 * myApp.run(function($templateCache) {
6322 * $templateCache.put('templateId.html', 'This is the content of the template');
6326 * To retrieve the template later, simply use it in your HTML:
6328 * <div ng-include=" 'templateId.html' "></div>
6331 * or get it via Javascript:
6333 * $templateCache.get('templateId.html')
6336 * See {@link ng.$cacheFactory $cacheFactory}.
6339 function $TemplateCacheProvider() {
6340 this.$get = ['$cacheFactory', function($cacheFactory) {
6341 return $cacheFactory('templates');
6345 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
6346 * Any commits to this file should be reviewed with security in mind. *
6347 * Changes to this file can potentially create security vulnerabilities. *
6348 * An approval from 2 Core members with history of modifying *
6349 * this file is required. *
6351 * Does the change somehow allow for arbitrary javascript to be executed? *
6352 * Or allows for someone to change the prototype of built-in objects? *
6353 * Or gives undesired access to variables likes document or window? *
6354 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
6356 /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
6358 * DOM-related variables:
6360 * - "node" - DOM Node
6361 * - "element" - DOM Element or Node
6362 * - "$node" or "$element" - jqLite-wrapped node or element
6365 * Compiler related stuff:
6367 * - "linkFn" - linking fn of a single directive
6368 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node
6369 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node
6370 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList)
6380 * Compiles an HTML string or DOM into a template and produces a template function, which
6381 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together.
6383 * The compilation is a process of walking the DOM tree and matching DOM elements to
6384 * {@link ng.$compileProvider#directive directives}.
6386 * <div class="alert alert-warning">
6387 * **Note:** This document is an in-depth reference of all directive options.
6388 * For a gentle introduction to directives with examples of common use cases,
6389 * see the {@link guide/directive directive guide}.
6392 * ## Comprehensive Directive API
6394 * There are many different options for a directive.
6396 * The difference resides in the return value of the factory function.
6397 * You can either return a "Directive Definition Object" (see below) that defines the directive properties,
6398 * or just the `postLink` function (all other properties will have the default values).
6400 * <div class="alert alert-success">
6401 * **Best Practice:** It's recommended to use the "directive definition object" form.
6404 * Here's an example directive declared with a Directive Definition Object:
6407 * var myModule = angular.module(...);
6409 * myModule.directive('directiveName', function factory(injectables) {
6410 * var directiveDefinitionObject = {
6412 * template: '<div></div>', // or // function(tElement, tAttrs) { ... },
6414 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... },
6415 * transclude: false,
6417 * templateNamespace: 'html',
6419 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
6420 * controllerAs: 'stringIdentifier',
6421 * bindToController: false,
6422 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
6423 * compile: function compile(tElement, tAttrs, transclude) {
6425 * pre: function preLink(scope, iElement, iAttrs, controller) { ... },
6426 * post: function postLink(scope, iElement, iAttrs, controller) { ... }
6429 * // return function postLink( ... ) { ... }
6433 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... },
6434 * // post: function postLink(scope, iElement, iAttrs, controller) { ... }
6437 * // link: function postLink( ... ) { ... }
6439 * return directiveDefinitionObject;
6443 * <div class="alert alert-warning">
6444 * **Note:** Any unspecified options will use the default value. You can see the default values below.
6447 * Therefore the above can be simplified as:
6450 * var myModule = angular.module(...);
6452 * myModule.directive('directiveName', function factory(injectables) {
6453 * var directiveDefinitionObject = {
6454 * link: function postLink(scope, iElement, iAttrs) { ... }
6456 * return directiveDefinitionObject;
6458 * // return function postLink(scope, iElement, iAttrs) { ... }
6464 * ### Directive Definition Object
6466 * The directive definition object provides instructions to the {@link ng.$compile
6467 * compiler}. The attributes are:
6469 * #### `multiElement`
6470 * When this property is set to true, the HTML compiler will collect DOM nodes between
6471 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
6472 * together as the directive elements. It is recommended that this feature be used on directives
6473 * which are not strictly behavioural (such as {@link ngClick}), and which
6474 * do not manipulate or replace child nodes (such as {@link ngInclude}).
6477 * When there are multiple directives defined on a single DOM element, sometimes it
6478 * is necessary to specify the order in which the directives are applied. The `priority` is used
6479 * to sort the directives before their `compile` functions get called. Priority is defined as a
6480 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions
6481 * are also run in priority order, but post-link functions are run in reverse order. The order
6482 * of directives with the same priority is undefined. The default priority is `0`.
6485 * If set to true then the current `priority` will be the last set of directives
6486 * which will execute (any directives at the current priority will still execute
6487 * as the order of execution on same `priority` is undefined). Note that expressions
6488 * and other directives used in the directive's template will also be excluded from execution.
6491 * The scope property can be `true`, an object or a falsy value:
6493 * * **falsy:** No scope will be created for the directive. The directive will use its parent's scope.
6495 * * **`true`:** A new child scope that prototypically inherits from its parent will be created for
6496 * the directive's element. If multiple directives on the same element request a new scope,
6497 * only one new scope is created. The new scope rule does not apply for the root of the template
6498 * since the root of the template always gets a new scope.
6500 * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's element. The
6501 * 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent
6502 * scope. This is useful when creating reusable components, which should not accidentally read or modify
6503 * data in the parent scope.
6505 * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the
6506 * directive's element. These local properties are useful for aliasing values for templates. The keys in
6507 * the object hash map to the name of the property on the isolate scope; the values define how the property
6508 * is bound to the parent scope, via matching attributes on the directive's element:
6510 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
6511 * always a string since DOM attributes are strings. If no `attr` name is specified then the
6512 * attribute name is assumed to be the same as the local name.
6513 * Given `<widget my-attr="hello {{name}}">` and widget definition
6514 * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect
6515 * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the
6516 * `localName` property on the widget scope. The `name` is read from the parent scope (not
6519 * * `=` or `=attr` - set up bi-directional binding between a local scope property and the
6520 * parent scope property of name defined via the value of the `attr` attribute. If no `attr`
6521 * name is specified then the attribute name is assumed to be the same as the local name.
6522 * Given `<widget my-attr="parentModel">` and widget definition of
6523 * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the
6524 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
6525 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent
6526 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You
6527 * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If
6528 * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use
6529 * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional).
6531 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope.
6532 * If no `attr` name is specified then the attribute name is assumed to be the same as the
6533 * local name. Given `<widget my-attr="count = count + value">` and widget definition of
6534 * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to
6535 * a function wrapper for the `count = count + value` expression. Often it's desirable to
6536 * pass data from the isolated scope via an expression to the parent scope, this can be
6537 * done by passing a map of local variable names and values into the expression wrapper fn.
6538 * For example, if the expression is `increment(amount)` then we can specify the amount value
6539 * by calling the `localFn` as `localFn({amount: 22})`.
6541 * In general it's possible to apply more than one directive to one element, but there might be limitations
6542 * depending on the type of scope required by the directives. The following points will help explain these limitations.
6543 * For simplicity only two directives are taken into account, but it is also applicable for several directives:
6545 * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope
6546 * * **child scope** + **no scope** => Both directives will share one single child scope
6547 * * **child scope** + **child scope** => Both directives will share one single child scope
6548 * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use
6549 * its parent's scope
6550 * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot
6551 * be applied to the same element.
6552 * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives
6553 * cannot be applied to the same element.
6556 * #### `bindToController`
6557 * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will
6558 * allow a component to have its properties bound to the controller, rather than to scope. When the controller
6559 * is instantiated, the initial values of the isolate scope bindings are already available.
6562 * Controller constructor function. The controller is instantiated before the
6563 * pre-linking phase and can be accessed by other directives (see
6564 * `require` attribute). This allows the directives to communicate with each other and augment
6565 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals:
6567 * * `$scope` - Current scope associated with the element
6568 * * `$element` - Current element
6569 * * `$attrs` - Current attributes object for the element
6570 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope:
6571 * `function([scope], cloneLinkingFn, futureParentElement)`.
6572 * * `scope`: optional argument to override the scope.
6573 * * `cloneLinkingFn`: optional argument to create clones of the original transcluded content.
6574 * * `futureParentElement`:
6575 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements.
6576 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`.
6577 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements)
6578 * and when the `cloneLinkinFn` is passed,
6579 * as those elements need to created and cloned in a special way when they are defined outside their
6580 * usual containers (e.g. like `<svg>`).
6581 * * See also the `directive.templateNamespace` property.
6585 * Require another directive and inject its controller as the fourth argument to the linking function. The
6586 * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the
6587 * injected argument will be an array in corresponding order. If no such directive can be
6588 * found, or if the directive does not have a controller, then an error is raised (unless no link function
6589 * is specified, in which case error checking is skipped). The name can be prefixed with:
6591 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
6592 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
6593 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found.
6594 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found.
6595 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass
6596 * `null` to the `link` fn if not found.
6597 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass
6598 * `null` to the `link` fn if not found.
6601 * #### `controllerAs`
6602 * Identifier name for a reference to the controller in the directive's scope.
6603 * This allows the controller to be referenced from the directive template. This is especially
6604 * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible
6605 * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the
6606 * `controllerAs` reference might overwrite a property that already exists on the parent scope.
6610 * String of subset of `EACM` which restricts the directive to a specific directive
6611 * declaration style. If omitted, the defaults (elements and attributes) are used.
6613 * * `E` - Element name (default): `<my-directive></my-directive>`
6614 * * `A` - Attribute (default): `<div my-directive="exp"></div>`
6615 * * `C` - Class: `<div class="my-directive: exp;"></div>`
6616 * * `M` - Comment: `<!-- directive: my-directive exp -->`
6619 * #### `templateNamespace`
6620 * String representing the document type used by the markup in the template.
6621 * AngularJS needs this information as those elements need to be created and cloned
6622 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`.
6624 * * `html` - All root nodes in the template are HTML. Root nodes may also be
6625 * top-level elements such as `<svg>` or `<math>`.
6626 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`).
6627 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`).
6629 * If no `templateNamespace` is specified, then the namespace is considered to be `html`.
6632 * HTML markup that may:
6633 * * Replace the contents of the directive's element (default).
6634 * * Replace the directive's element itself (if `replace` is true - DEPRECATED).
6635 * * Wrap the contents of the directive's element (if `transclude` is true).
6639 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`.
6640 * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile`
6641 * function api below) and returns a string value.
6644 * #### `templateUrl`
6645 * This is similar to `template` but the template is loaded from the specified URL, asynchronously.
6647 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element
6648 * for later when the template has been resolved. In the meantime it will continue to compile and link
6649 * sibling and parent elements as though this element had not contained any directives.
6651 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this
6652 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the
6653 * case when only one deeply nested directive has `templateUrl`.
6655 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache}
6657 * You can specify `templateUrl` as a string representing the URL or as a function which takes two
6658 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns
6659 * a string value representing the url. In either case, the template URL is passed through {@link
6660 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}.
6663 * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0)
6664 * specify what the template should replace. Defaults to `false`.
6666 * * `true` - the template will replace the directive's element.
6667 * * `false` - the template will replace the contents of the directive's element.
6669 * The replacement process migrates all of the attributes / classes from the old element to the new
6670 * one. See the {@link guide/directive#template-expanding-directive
6671 * Directives Guide} for an example.
6673 * There are very few scenarios where element replacement is required for the application function,
6674 * the main one being reusable custom components that are used within SVG contexts
6675 * (because SVG doesn't work with custom elements in the DOM tree).
6678 * Extract the contents of the element where the directive appears and make it available to the directive.
6679 * The contents are compiled and provided to the directive as a **transclusion function**. See the
6680 * {@link $compile#transclusion Transclusion} section below.
6682 * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the
6683 * directive's element or the entire element:
6685 * * `true` - transclude the content (i.e. the child nodes) of the directive's element.
6686 * * `'element'` - transclude the whole of the directive's element including any directives on this
6687 * element that defined at a lower priority than this directive. When used, the `template`
6688 * property is ignored.
6694 * function compile(tElement, tAttrs, transclude) { ... }
6697 * The compile function deals with transforming the template DOM. Since most directives do not do
6698 * template transformation, it is not used often. The compile function takes the following arguments:
6700 * * `tElement` - template element - The element where the directive has been declared. It is
6701 * safe to do template transformation on the element and child elements only.
6703 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared
6704 * between all directive compile functions.
6706 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)`
6708 * <div class="alert alert-warning">
6709 * **Note:** The template instance and the link instance may be different objects if the template has
6710 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that
6711 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
6712 * should be done in a linking function rather than in a compile function.
6715 * <div class="alert alert-warning">
6716 * **Note:** The compile function cannot handle directives that recursively use themselves in their
6717 * own templates or compile functions. Compiling these directives results in an infinite loop and a
6718 * stack overflow errors.
6720 * This can be avoided by manually using $compile in the postLink function to imperatively compile
6721 * a directive's template instead of relying on automatic template compilation via `template` or
6722 * `templateUrl` declaration or manual compilation inside the compile function.
6725 * <div class="alert alert-danger">
6726 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it
6727 * e.g. does not know about the right outer scope. Please use the transclude function that is passed
6728 * to the link function instead.
6731 * A compile function can have a return value which can be either a function or an object.
6733 * * returning a (post-link) function - is equivalent to registering the linking function via the
6734 * `link` property of the config object when the compile function is empty.
6736 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to
6737 * control when a linking function should be called during the linking phase. See info about
6738 * pre-linking and post-linking functions below.
6742 * This property is used only if the `compile` property is not defined.
6745 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }
6748 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is
6749 * executed after the template has been cloned. This is where most of the directive logic will be
6752 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the
6753 * directive for registering {@link ng.$rootScope.Scope#$watch watches}.
6755 * * `iElement` - instance element - The element where the directive is to be used. It is safe to
6756 * manipulate the children of the element only in `postLink` function since the children have
6757 * already been linked.
6759 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
6760 * between all directive linking functions.
6762 * * `controller` - the directive's required controller instance(s) - Instances are shared
6763 * among all directives, which allows the directives to use the controllers as a communication
6764 * channel. The exact value depends on the directive's `require` property:
6765 * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one
6766 * * `string`: the controller instance
6767 * * `array`: array of controller instances
6769 * If a required controller cannot be found, and it is optional, the instance is `null`,
6770 * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown.
6772 * Note that you can also require the directive's own controller - it will be made available like
6773 * any other controller.
6775 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
6776 * This is the same as the `$transclude`
6777 * parameter of directive controllers, see there for details.
6778 * `function([scope], cloneLinkingFn, futureParentElement)`.
6780 * #### Pre-linking function
6782 * Executed before the child elements are linked. Not safe to do DOM transformation since the
6783 * compiler linking function will fail to locate the correct elements for linking.
6785 * #### Post-linking function
6787 * Executed after the child elements are linked.
6789 * Note that child elements that contain `templateUrl` directives will not have been compiled
6790 * and linked since they are waiting for their template to load asynchronously and their own
6791 * compilation and linking has been suspended until that occurs.
6793 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting
6794 * for their async templates to be resolved.
6799 * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and
6800 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS
6801 * scope from where they were taken.
6803 * Transclusion is used (often with {@link ngTransclude}) to insert the
6804 * original contents of a directive's element into a specified place in the template of the directive.
6805 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded
6806 * content has access to the properties on the scope from which it was taken, even if the directive
6807 * has isolated scope.
6808 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}.
6810 * This makes it possible for the widget to have private state for its template, while the transcluded
6811 * content has access to its originating scope.
6813 * <div class="alert alert-warning">
6814 * **Note:** When testing an element transclude directive you must not place the directive at the root of the
6815 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
6816 * Testing Transclusion Directives}.
6819 * #### Transclusion Functions
6821 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion
6822 * function** to the directive's `link` function and `controller`. This transclusion function is a special
6823 * **linking function** that will return the compiled contents linked to a new transclusion scope.
6825 * <div class="alert alert-info">
6826 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since
6827 * ngTransclude will deal with it for us.
6830 * If you want to manually control the insertion and removal of the transcluded content in your directive
6831 * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery
6832 * object that contains the compiled DOM, which is linked to the correct transclusion scope.
6834 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts
6835 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded
6836 * content and the `scope` is the newly created transclusion scope, to which the clone is bound.
6838 * <div class="alert alert-info">
6839 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function
6840 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope.
6843 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone
6844 * attach function**:
6847 * var transcludedContent, transclusionScope;
6849 * $transclude(function(clone, scope) {
6850 * element.append(clone);
6851 * transcludedContent = clone;
6852 * transclusionScope = scope;
6856 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the
6857 * associated transclusion scope:
6860 * transcludedContent.remove();
6861 * transclusionScope.$destroy();
6864 * <div class="alert alert-info">
6865 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive
6866 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it),
6867 * then you are also responsible for calling `$destroy` on the transclusion scope.
6870 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat}
6871 * automatically destroy their transluded clones as necessary so you do not need to worry about this if
6872 * you are simply using {@link ngTransclude} to inject the transclusion into your directive.
6875 * #### Transclusion Scopes
6877 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion
6878 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed
6879 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it
6882 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look
6888 * <div transclusion>
6894 * The `$parent` scope hierarchy will look like this:
6902 * but the scopes will inherit prototypically from different scopes to their `$parent`.
6913 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
6914 * `link()` or `compile()` functions. It has a variety of uses.
6916 * accessing *Normalized attribute names:*
6917 * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'.
6918 * the attributes object allows for normalized access to
6921 * * *Directive inter-communication:* All directives share the same instance of the attributes
6922 * object which allows the directives to use the attributes object as inter directive
6925 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object
6926 * allowing other directives to read the interpolated value.
6928 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes
6929 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also
6930 * the only way to easily get the actual value because during the linking phase the interpolation
6931 * hasn't been evaluated yet and so the value is at this time set to `undefined`.
6934 * function linkingFn(scope, elm, attrs, ctrl) {
6935 * // get the attribute value
6936 * console.log(attrs.ngModel);
6938 * // change the attribute
6939 * attrs.$set('ngModel', 'new value');
6941 * // observe changes to interpolated attribute
6942 * attrs.$observe('ngModel', function(value) {
6943 * console.log('ngModel has changed value to ' + value);
6950 * <div class="alert alert-warning">
6951 * **Note**: Typically directives are registered with `module.directive`. The example below is
6952 * to illustrate how `$compile` works.
6955 <example module="compileExample">
6956 <file name="index.html">
6958 angular.module('compileExample', [], function($compileProvider) {
6959 // configure new 'compile' directive by passing a directive
6960 // factory function. The factory function injects the '$compile'
6961 $compileProvider.directive('compile', function($compile) {
6962 // directive factory creates a link function
6963 return function(scope, element, attrs) {
6966 // watch the 'compile' expression for changes
6967 return scope.$eval(attrs.compile);
6970 // when the 'compile' expression changes
6971 // assign it into the current DOM
6972 element.html(value);
6974 // compile the new DOM and link it to the current
6976 // NOTE: we only compile .childNodes so that
6977 // we don't get into infinite loop compiling ourselves
6978 $compile(element.contents())(scope);
6984 .controller('GreeterController', ['$scope', function($scope) {
6985 $scope.name = 'Angular';
6986 $scope.html = 'Hello {{name}}';
6989 <div ng-controller="GreeterController">
6990 <input ng-model="name"> <br/>
6991 <textarea ng-model="html"></textarea> <br/>
6992 <div compile="html"></div>
6995 <file name="protractor.js" type="protractor">
6996 it('should auto compile', function() {
6997 var textarea = $('textarea');
6998 var output = $('div[compile]');
6999 // The initial state reads 'Hello Angular'.
7000 expect(output.getText()).toBe('Hello Angular');
7002 textarea.sendKeys('{{name}}!');
7003 expect(output.getText()).toBe('Angular!');
7010 * @param {string|DOMElement} element Element or HTML string to compile into a template function.
7011 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
7013 * <div class="alert alert-danger">
7014 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
7015 * e.g. will not use the right outer scope. Please pass the transclude function as a
7016 * `parentBoundTranscludeFn` to the link function instead.
7019 * @param {number} maxPriority only apply directives lower than given priority (Only effects the
7020 * root element(s), not their children)
7021 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template
7022 * (a DOM element/tree) to a scope. Where:
7024 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
7025 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
7026 * `template` and call the `cloneAttachFn` function allowing the caller to attach the
7027 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
7028 * called as: <br/> `cloneAttachFn(clonedElement, scope)` where:
7030 * * `clonedElement` - is a clone of the original `element` passed into the compiler.
7031 * * `scope` - is the current scope with which the linking function is working with.
7033 * * `options` - An optional object hash with linking options. If `options` is provided, then the following
7034 * keys may be used to control linking behavior:
7036 * * `parentBoundTranscludeFn` - the transclude function made available to
7037 * directives; if given, it will be passed through to the link functions of
7038 * directives found in `element` during compilation.
7039 * * `transcludeControllers` - an object hash with keys that map controller names
7040 * to controller instances; if given, it will make the controllers
7041 * available to directives.
7042 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add
7043 * the cloned elements; only needed for transcludes that are allowed to contain non html
7044 * elements (e.g. SVG elements). See also the directive.controller property.
7046 * Calling the linking function returns the element of the template. It is either the original
7047 * element passed in, or the clone of the element if the `cloneAttachFn` is provided.
7049 * After linking the view is not updated until after a call to $digest which typically is done by
7050 * Angular automatically.
7052 * If you need access to the bound view, there are two ways to do it:
7054 * - If you are not asking the linking function to clone the template, create the DOM element(s)
7055 * before you send them to the compiler and keep this reference around.
7057 * var element = $compile('<p>{{total}}</p>')(scope);
7060 * - if on the other hand, you need the element to be cloned, the view reference from the original
7061 * example would not point to the clone, but rather to the original template that was cloned. In
7062 * this case, you can access the clone via the cloneAttachFn:
7064 * var templateElement = angular.element('<p>{{total}}</p>'),
7067 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) {
7068 * //attach the clone to DOM document at the right place
7071 * //now we have reference to the cloned DOM via `clonedElement`
7075 * For information on how the compiler works, see the
7076 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
7079 var $compileMinErr = minErr('$compile');
7083 * @name $compileProvider
7087 $CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider'];
7088 function $CompileProvider($provide, $$sanitizeUriProvider) {
7089 var hasDirectives = {},
7090 Suffix = 'Directive',
7091 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/,
7092 CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/,
7093 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
7094 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
7096 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
7097 // The assumption is that future DOM event attribute names will begin with
7098 // 'on' and be composed of only English letters.
7099 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
7101 function parseIsolateBindings(scope, directiveName, isController) {
7102 var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/;
7106 forEach(scope, function(definition, scopeName) {
7107 var match = definition.match(LOCAL_REGEXP);
7110 throw $compileMinErr('iscp',
7111 "Invalid {3} for directive '{0}'." +
7112 " Definition: {... {1}: '{2}' ...}",
7113 directiveName, scopeName, definition,
7114 (isController ? "controller bindings definition" :
7115 "isolate scope definition"));
7118 bindings[scopeName] = {
7120 collection: match[2] === '*',
7121 optional: match[3] === '?',
7122 attrName: match[4] || scopeName
7129 function parseDirectiveBindings(directive, directiveName) {
7132 bindToController: null
7134 if (isObject(directive.scope)) {
7135 if (directive.bindToController === true) {
7136 bindings.bindToController = parseIsolateBindings(directive.scope,
7137 directiveName, true);
7138 bindings.isolateScope = {};
7140 bindings.isolateScope = parseIsolateBindings(directive.scope,
7141 directiveName, false);
7144 if (isObject(directive.bindToController)) {
7145 bindings.bindToController =
7146 parseIsolateBindings(directive.bindToController, directiveName, true);
7148 if (isObject(bindings.bindToController)) {
7149 var controller = directive.controller;
7150 var controllerAs = directive.controllerAs;
7152 // There is no controller, there may or may not be a controllerAs property
7153 throw $compileMinErr('noctrl',
7154 "Cannot bind to controller without directive '{0}'s controller.",
7156 } else if (!identifierForController(controller, controllerAs)) {
7157 // There is a controller, but no identifier or controllerAs property
7158 throw $compileMinErr('noident',
7159 "Cannot bind to controller without identifier for directive '{0}'.",
7166 function assertValidDirectiveName(name) {
7167 var letter = name.charAt(0);
7168 if (!letter || letter !== lowercase(letter)) {
7169 throw $compileMinErr('baddir', "Directive name '{0}' is invalid. The first character must be a lowercase letter", name);
7171 if (name !== name.trim()) {
7172 throw $compileMinErr('baddir',
7173 "Directive name '{0}' is invalid. The name should not contain leading or trailing whitespaces",
7180 * @name $compileProvider#directive
7184 * Register a new directive with the compiler.
7186 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which
7187 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the
7188 * names and the values are the factories.
7189 * @param {Function|Array} directiveFactory An injectable directive factory function. See
7190 * {@link guide/directive} for more info.
7191 * @returns {ng.$compileProvider} Self for chaining.
7193 this.directive = function registerDirective(name, directiveFactory) {
7194 assertNotHasOwnProperty(name, 'directive');
7195 if (isString(name)) {
7196 assertValidDirectiveName(name);
7197 assertArg(directiveFactory, 'directiveFactory');
7198 if (!hasDirectives.hasOwnProperty(name)) {
7199 hasDirectives[name] = [];
7200 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
7201 function($injector, $exceptionHandler) {
7202 var directives = [];
7203 forEach(hasDirectives[name], function(directiveFactory, index) {
7205 var directive = $injector.invoke(directiveFactory);
7206 if (isFunction(directive)) {
7207 directive = { compile: valueFn(directive) };
7208 } else if (!directive.compile && directive.link) {
7209 directive.compile = valueFn(directive.link);
7211 directive.priority = directive.priority || 0;
7212 directive.index = index;
7213 directive.name = directive.name || name;
7214 directive.require = directive.require || (directive.controller && directive.name);
7215 directive.restrict = directive.restrict || 'EA';
7216 var bindings = directive.$$bindings =
7217 parseDirectiveBindings(directive, directive.name);
7218 if (isObject(bindings.isolateScope)) {
7219 directive.$$isolateBindings = bindings.isolateScope;
7221 directive.$$moduleName = directiveFactory.$$moduleName;
7222 directives.push(directive);
7224 $exceptionHandler(e);
7230 hasDirectives[name].push(directiveFactory);
7232 forEach(name, reverseParams(registerDirective));
7240 * @name $compileProvider#aHrefSanitizationWhitelist
7244 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
7245 * urls during a[href] sanitization.
7247 * The sanitization is a security measure aimed at preventing XSS attacks via html links.
7249 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
7250 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
7251 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
7252 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
7254 * @param {RegExp=} regexp New regexp to whitelist urls with.
7255 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
7256 * chaining otherwise.
7258 this.aHrefSanitizationWhitelist = function(regexp) {
7259 if (isDefined(regexp)) {
7260 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp);
7263 return $$sanitizeUriProvider.aHrefSanitizationWhitelist();
7270 * @name $compileProvider#imgSrcSanitizationWhitelist
7274 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
7275 * urls during img[src] sanitization.
7277 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
7279 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
7280 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
7281 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
7282 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
7284 * @param {RegExp=} regexp New regexp to whitelist urls with.
7285 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
7286 * chaining otherwise.
7288 this.imgSrcSanitizationWhitelist = function(regexp) {
7289 if (isDefined(regexp)) {
7290 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp);
7293 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist();
7299 * @name $compileProvider#debugInfoEnabled
7301 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the
7302 * current debugInfoEnabled state
7303 * @returns {*} current value if used as getter or itself (chaining) if used as setter
7308 * Call this method to enable/disable various debug runtime information in the compiler such as adding
7309 * binding information and a reference to the current scope on to DOM elements.
7310 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope
7311 * * `ng-binding` CSS class
7312 * * `$binding` data property containing an array of the binding expressions
7314 * You may want to disable this in production for a significant performance boost. See
7315 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more.
7317 * The default value is true.
7319 var debugInfoEnabled = true;
7320 this.debugInfoEnabled = function(enabled) {
7321 if (isDefined(enabled)) {
7322 debugInfoEnabled = enabled;
7325 return debugInfoEnabled;
7329 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
7330 '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri',
7331 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse,
7332 $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) {
7334 var Attributes = function(element, attributesToCopy) {
7335 if (attributesToCopy) {
7336 var keys = Object.keys(attributesToCopy);
7339 for (i = 0, l = keys.length; i < l; i++) {
7341 this[key] = attributesToCopy[key];
7347 this.$$element = element;
7350 Attributes.prototype = {
7353 * @name $compile.directive.Attributes#$normalize
7357 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
7358 * `data-`) to its normalized, camelCase form.
7360 * Also there is special case for Moz prefix starting with upper case letter.
7362 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
7364 * @param {string} name Name to normalize
7366 $normalize: directiveNormalize,
7371 * @name $compile.directive.Attributes#$addClass
7375 * Adds the CSS class value specified by the classVal parameter to the element. If animations
7376 * are enabled then an animation will be triggered for the class addition.
7378 * @param {string} classVal The className value that will be added to the element
7380 $addClass: function(classVal) {
7381 if (classVal && classVal.length > 0) {
7382 $animate.addClass(this.$$element, classVal);
7388 * @name $compile.directive.Attributes#$removeClass
7392 * Removes the CSS class value specified by the classVal parameter from the element. If
7393 * animations are enabled then an animation will be triggered for the class removal.
7395 * @param {string} classVal The className value that will be removed from the element
7397 $removeClass: function(classVal) {
7398 if (classVal && classVal.length > 0) {
7399 $animate.removeClass(this.$$element, classVal);
7405 * @name $compile.directive.Attributes#$updateClass
7409 * Adds and removes the appropriate CSS class values to the element based on the difference
7410 * between the new and old CSS class values (specified as newClasses and oldClasses).
7412 * @param {string} newClasses The current CSS className value
7413 * @param {string} oldClasses The former CSS className value
7415 $updateClass: function(newClasses, oldClasses) {
7416 var toAdd = tokenDifference(newClasses, oldClasses);
7417 if (toAdd && toAdd.length) {
7418 $animate.addClass(this.$$element, toAdd);
7421 var toRemove = tokenDifference(oldClasses, newClasses);
7422 if (toRemove && toRemove.length) {
7423 $animate.removeClass(this.$$element, toRemove);
7428 * Set a normalized attribute on the element in a way such that all directives
7429 * can share the attribute. This function properly handles boolean attributes.
7430 * @param {string} key Normalized key. (ie ngAttribute)
7431 * @param {string|boolean} value The value to set. If `null` attribute will be deleted.
7432 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
7434 * @param {string=} attrName Optional none normalized name. Defaults to key.
7436 $set: function(key, value, writeAttr, attrName) {
7437 // TODO: decide whether or not to throw an error if "class"
7438 //is set through this function since it may cause $updateClass to
7441 var node = this.$$element[0],
7442 booleanKey = getBooleanAttrName(node, key),
7443 aliasedKey = getAliasedAttrName(key),
7448 this.$$element.prop(key, value);
7449 attrName = booleanKey;
7450 } else if (aliasedKey) {
7451 this[aliasedKey] = value;
7452 observer = aliasedKey;
7457 // translate normalized key to actual key
7459 this.$attr[key] = attrName;
7461 attrName = this.$attr[key];
7463 this.$attr[key] = attrName = snake_case(key, '-');
7467 nodeName = nodeName_(this.$$element);
7469 if ((nodeName === 'a' && key === 'href') ||
7470 (nodeName === 'img' && key === 'src')) {
7471 // sanitize a[href] and img[src] values
7472 this[key] = value = $$sanitizeUri(value, key === 'src');
7473 } else if (nodeName === 'img' && key === 'srcset') {
7474 // sanitize img[srcset] values
7477 // first check if there are spaces because it's not the same pattern
7478 var trimmedSrcset = trim(value);
7479 // ( 999x ,| 999w ,| ,|, )
7480 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
7481 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
7483 // split srcset into tuple of uri and descriptor except for the last item
7484 var rawUris = trimmedSrcset.split(pattern);
7487 var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
7488 for (var i = 0; i < nbrUrisWith2parts; i++) {
7489 var innerIdx = i * 2;
7491 result += $$sanitizeUri(trim(rawUris[innerIdx]), true);
7492 // add the descriptor
7493 result += (" " + trim(rawUris[innerIdx + 1]));
7496 // split the last item into uri and descriptor
7497 var lastTuple = trim(rawUris[i * 2]).split(/\s/);
7499 // sanitize the last uri
7500 result += $$sanitizeUri(trim(lastTuple[0]), true);
7502 // and add the last descriptor if any
7503 if (lastTuple.length === 2) {
7504 result += (" " + trim(lastTuple[1]));
7506 this[key] = value = result;
7509 if (writeAttr !== false) {
7510 if (value === null || isUndefined(value)) {
7511 this.$$element.removeAttr(attrName);
7513 this.$$element.attr(attrName, value);
7518 var $$observers = this.$$observers;
7519 $$observers && forEach($$observers[observer], function(fn) {
7523 $exceptionHandler(e);
7531 * @name $compile.directive.Attributes#$observe
7535 * Observes an interpolated attribute.
7537 * The observer function will be invoked once during the next `$digest` following
7538 * compilation. The observer is then invoked whenever the interpolated value
7541 * @param {string} key Normalized key. (ie ngAttribute) .
7542 * @param {function(interpolatedValue)} fn Function that will be called whenever
7543 the interpolated value of the attribute changes.
7544 * See the {@link guide/directive#text-and-attribute-bindings Directives} guide for more info.
7545 * @returns {function()} Returns a deregistration function for this observer.
7547 $observe: function(key, fn) {
7549 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())),
7550 listeners = ($$observers[key] || ($$observers[key] = []));
7553 $rootScope.$evalAsync(function() {
7554 if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) {
7555 // no one registered attribute interpolation function, so lets call it manually
7561 arrayRemove(listeners, fn);
7567 function safeAddClass($element, className) {
7569 $element.addClass(className);
7571 // ignore, since it means that we are trying to set class on
7572 // SVG element, where class name is read-only.
7577 var startSymbol = $interpolate.startSymbol(),
7578 endSymbol = $interpolate.endSymbol(),
7579 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
7581 : function denormalizeTemplate(template) {
7582 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
7584 NG_ATTR_BINDING = /^ngAttr[A-Z]/;
7585 var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/;
7587 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) {
7588 var bindings = $element.data('$binding') || [];
7590 if (isArray(binding)) {
7591 bindings = bindings.concat(binding);
7593 bindings.push(binding);
7596 $element.data('$binding', bindings);
7599 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) {
7600 safeAddClass($element, 'ng-binding');
7603 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) {
7604 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope';
7605 $element.data(dataName, scope);
7608 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) {
7609 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope');
7614 //================================
7616 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective,
7617 previousCompileContext) {
7618 if (!($compileNodes instanceof jqLite)) {
7619 // jquery always rewraps, whereas we need to preserve the original selector so that we can
7621 $compileNodes = jqLite($compileNodes);
7623 // We can not compile top level text elements since text nodes can be merged and we will
7624 // not be able to attach scope data to them, so we will wrap them in <span>
7625 forEach($compileNodes, function(node, index) {
7626 if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) {
7627 $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
7630 var compositeLinkFn =
7631 compileNodes($compileNodes, transcludeFn, $compileNodes,
7632 maxPriority, ignoreDirective, previousCompileContext);
7633 compile.$$addScopeClass($compileNodes);
7634 var namespace = null;
7635 return function publicLinkFn(scope, cloneConnectFn, options) {
7636 assertArg(scope, 'scope');
7638 if (previousCompileContext && previousCompileContext.needsNewScope) {
7639 // A parent directive did a replace and a directive on this element asked
7640 // for transclusion, which caused us to lose a layer of element on which
7641 // we could hold the new transclusion scope, so we will create it manually
7643 scope = scope.$parent.$new();
7646 options = options || {};
7647 var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
7648 transcludeControllers = options.transcludeControllers,
7649 futureParentElement = options.futureParentElement;
7651 // When `parentBoundTranscludeFn` is passed, it is a
7652 // `controllersBoundTransclude` function (it was previously passed
7653 // as `transclude` to directive.link) so we must unwrap it to get
7654 // its `boundTranscludeFn`
7655 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) {
7656 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;
7660 namespace = detectNamespaceForChildElements(futureParentElement);
7663 if (namespace !== 'html') {
7664 // When using a directive with replace:true and templateUrl the $compileNodes
7665 // (or a child element inside of them)
7666 // might change, so we need to recreate the namespace adapted compileNodes
7667 // for call to the link function.
7668 // Note: This will already clone the nodes...
7670 wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html())
7672 } else if (cloneConnectFn) {
7673 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
7674 // and sometimes changes the structure of the DOM.
7675 $linkNode = JQLitePrototype.clone.call($compileNodes);
7677 $linkNode = $compileNodes;
7680 if (transcludeControllers) {
7681 for (var controllerName in transcludeControllers) {
7682 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance);
7686 compile.$$addScopeInfo($linkNode, scope);
7688 if (cloneConnectFn) cloneConnectFn($linkNode, scope);
7689 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);
7694 function detectNamespaceForChildElements(parentElement) {
7695 // TODO: Make this detect MathML as well...
7696 var node = parentElement && parentElement[0];
7700 return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html';
7705 * Compile function matches each node in nodeList against the directives. Once all directives
7706 * for a particular node are collected their compile functions are executed. The compile
7707 * functions return values - the linking functions - are combined into a composite linking
7708 * function, which is the a linking function for the node.
7710 * @param {NodeList} nodeList an array of nodes or NodeList to compile
7711 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
7712 * scope argument is auto-generated to the new child of the transcluded parent scope.
7713 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then
7714 * the rootElement must be set the jqLite collection of the compile root. This is
7715 * needed so that the jqLite collection items can be replaced with widgets.
7716 * @param {number=} maxPriority Max directive priority.
7717 * @returns {Function} A composite linking function of all of the matched directives or null.
7719 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective,
7720 previousCompileContext) {
7722 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound;
7724 for (var i = 0; i < nodeList.length; i++) {
7725 attrs = new Attributes();
7727 // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
7728 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined,
7731 nodeLinkFn = (directives.length)
7732 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement,
7733 null, [], [], previousCompileContext)
7736 if (nodeLinkFn && nodeLinkFn.scope) {
7737 compile.$$addScopeClass(attrs.$$element);
7740 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal ||
7741 !(childNodes = nodeList[i].childNodes) ||
7744 : compileNodes(childNodes,
7746 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement)
7747 && nodeLinkFn.transclude) : transcludeFn);
7749 if (nodeLinkFn || childLinkFn) {
7750 linkFns.push(i, nodeLinkFn, childLinkFn);
7752 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn;
7755 //use the previous context only for the first element in the virtual group
7756 previousCompileContext = null;
7759 // return a linking function if we have found anything, null otherwise
7760 return linkFnFound ? compositeLinkFn : null;
7762 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) {
7763 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn;
7767 if (nodeLinkFnFound) {
7768 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our
7769 // offsets don't get screwed up
7770 var nodeListLength = nodeList.length;
7771 stableNodeList = new Array(nodeListLength);
7773 // create a sparse array by only copying the elements which have a linkFn
7774 for (i = 0; i < linkFns.length; i+=3) {
7776 stableNodeList[idx] = nodeList[idx];
7779 stableNodeList = nodeList;
7782 for (i = 0, ii = linkFns.length; i < ii;) {
7783 node = stableNodeList[linkFns[i++]];
7784 nodeLinkFn = linkFns[i++];
7785 childLinkFn = linkFns[i++];
7788 if (nodeLinkFn.scope) {
7789 childScope = scope.$new();
7790 compile.$$addScopeInfo(jqLite(node), childScope);
7795 if (nodeLinkFn.transcludeOnThisElement) {
7796 childBoundTranscludeFn = createBoundTranscludeFn(
7797 scope, nodeLinkFn.transclude, parentBoundTranscludeFn);
7799 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) {
7800 childBoundTranscludeFn = parentBoundTranscludeFn;
7802 } else if (!parentBoundTranscludeFn && transcludeFn) {
7803 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn);
7806 childBoundTranscludeFn = null;
7809 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
7811 } else if (childLinkFn) {
7812 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
7818 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) {
7820 var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
7822 if (!transcludedScope) {
7823 transcludedScope = scope.$new(false, containingScope);
7824 transcludedScope.$$transcluded = true;
7827 return transcludeFn(transcludedScope, cloneFn, {
7828 parentBoundTranscludeFn: previousBoundTranscludeFn,
7829 transcludeControllers: controllers,
7830 futureParentElement: futureParentElement
7834 return boundTranscludeFn;
7838 * Looks for directives on the given node and adds them to the directive collection which is
7841 * @param node Node to search.
7842 * @param directives An array to which the directives are added to. This array is sorted before
7843 * the function returns.
7844 * @param attrs The shared attrs object which is used to populate the normalized attributes.
7845 * @param {number=} maxPriority Max directive priority.
7847 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
7848 var nodeType = node.nodeType,
7849 attrsMap = attrs.$attr,
7854 case NODE_TYPE_ELEMENT: /* Element */
7855 // use the node name: <directive>
7856 addDirective(directives,
7857 directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective);
7859 // iterate over the attributes
7860 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes,
7861 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
7862 var attrStartName = false;
7863 var attrEndName = false;
7867 value = trim(attr.value);
7869 // support ngAttr attribute binding
7870 ngAttrName = directiveNormalize(name);
7871 if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
7872 name = name.replace(PREFIX_REGEXP, '')
7873 .substr(8).replace(/_(.)/g, function(match, letter) {
7874 return letter.toUpperCase();
7878 var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE);
7879 if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) {
7880 attrStartName = name;
7881 attrEndName = name.substr(0, name.length - 5) + 'end';
7882 name = name.substr(0, name.length - 6);
7885 nName = directiveNormalize(name.toLowerCase());
7886 attrsMap[nName] = name;
7887 if (isNgAttr || !attrs.hasOwnProperty(nName)) {
7888 attrs[nName] = value;
7889 if (getBooleanAttrName(node, nName)) {
7890 attrs[nName] = true; // presence means true
7893 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
7894 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
7898 // use class as directive
7899 className = node.className;
7900 if (isObject(className)) {
7901 // Maybe SVGAnimatedString
7902 className = className.animVal;
7904 if (isString(className) && className !== '') {
7905 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
7906 nName = directiveNormalize(match[2]);
7907 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) {
7908 attrs[nName] = trim(match[3]);
7910 className = className.substr(match.index + match[0].length);
7914 case NODE_TYPE_TEXT: /* Text Node */
7916 // Workaround for #11781
7917 while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) {
7918 node.nodeValue = node.nodeValue + node.nextSibling.nodeValue;
7919 node.parentNode.removeChild(node.nextSibling);
7922 addTextInterpolateDirective(directives, node.nodeValue);
7924 case NODE_TYPE_COMMENT: /* Comment */
7926 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
7928 nName = directiveNormalize(match[1]);
7929 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) {
7930 attrs[nName] = trim(match[2]);
7934 // turns out that under some circumstances IE9 throws errors when one attempts to read
7935 // comment's node value.
7936 // Just ignore it and continue. (Can't seem to reproduce in test case.)
7941 directives.sort(byPriority);
7946 * Given a node with an directive-start it collects all of the siblings until it finds
7953 function groupScan(node, attrStart, attrEnd) {
7956 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
7959 throw $compileMinErr('uterdir',
7960 "Unterminated attribute, found '{0}' but no matching '{1}' found.",
7961 attrStart, attrEnd);
7963 if (node.nodeType == NODE_TYPE_ELEMENT) {
7964 if (node.hasAttribute(attrStart)) depth++;
7965 if (node.hasAttribute(attrEnd)) depth--;
7968 node = node.nextSibling;
7969 } while (depth > 0);
7974 return jqLite(nodes);
7978 * Wrapper for linking function which converts normal linking function into a grouped
7983 * @returns {Function}
7985 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
7986 return function(scope, element, attrs, controllers, transcludeFn) {
7987 element = groupScan(element[0], attrStart, attrEnd);
7988 return linkFn(scope, element, attrs, controllers, transcludeFn);
7993 * Once the directives have been collected, their compile functions are executed. This method
7994 * is responsible for inlining directive templates as well as terminating the application
7995 * of the directives if the terminal directive has been reached.
7997 * @param {Array} directives Array of collected directives to execute their compile function.
7998 * this needs to be pre-sorted by priority order.
7999 * @param {Node} compileNode The raw DOM node to apply the compile functions to
8000 * @param {Object} templateAttrs The shared attribute function
8001 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
8002 * scope argument is auto-generated to the new
8003 * child of the transcluded parent scope.
8004 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this
8005 * argument has the root jqLite array so that we can replace nodes
8007 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when
8008 * compiling the transclusion.
8009 * @param {Array.<Function>} preLinkFns
8010 * @param {Array.<Function>} postLinkFns
8011 * @param {Object} previousCompileContext Context used for previous compilation of the current
8013 * @returns {Function} linkFn
8015 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn,
8016 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns,
8017 previousCompileContext) {
8018 previousCompileContext = previousCompileContext || {};
8020 var terminalPriority = -Number.MAX_VALUE,
8021 newScopeDirective = previousCompileContext.newScopeDirective,
8022 controllerDirectives = previousCompileContext.controllerDirectives,
8023 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,
8024 templateDirective = previousCompileContext.templateDirective,
8025 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
8026 hasTranscludeDirective = false,
8027 hasTemplate = false,
8028 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective,
8029 $compileNode = templateAttrs.$$element = jqLite(compileNode),
8033 replaceDirective = originalReplaceDirective,
8034 childTranscludeFn = transcludeFn,
8038 // executes all directives on the current element
8039 for (var i = 0, ii = directives.length; i < ii; i++) {
8040 directive = directives[i];
8041 var attrStart = directive.$$start;
8042 var attrEnd = directive.$$end;
8044 // collect multiblock sections
8046 $compileNode = groupScan(compileNode, attrStart, attrEnd);
8048 $template = undefined;
8050 if (terminalPriority > directive.priority) {
8051 break; // prevent further processing of directives
8054 if (directiveValue = directive.scope) {
8056 // skip the check for directives with async templates, we'll check the derived sync
8057 // directive when the template arrives
8058 if (!directive.templateUrl) {
8059 if (isObject(directiveValue)) {
8060 // This directive is trying to add an isolated scope.
8061 // Check that there is no scope of any kind already
8062 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective,
8063 directive, $compileNode);
8064 newIsolateScopeDirective = directive;
8066 // This directive is trying to add a child scope.
8067 // Check that there is no isolated scope already
8068 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive,
8073 newScopeDirective = newScopeDirective || directive;
8076 directiveName = directive.name;
8078 if (!directive.templateUrl && directive.controller) {
8079 directiveValue = directive.controller;
8080 controllerDirectives = controllerDirectives || createMap();
8081 assertNoDuplicate("'" + directiveName + "' controller",
8082 controllerDirectives[directiveName], directive, $compileNode);
8083 controllerDirectives[directiveName] = directive;
8086 if (directiveValue = directive.transclude) {
8087 hasTranscludeDirective = true;
8089 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion.
8090 // This option should only be used by directives that know how to safely handle element transclusion,
8091 // where the transcluded nodes are added or replaced after linking.
8092 if (!directive.$$tlb) {
8093 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode);
8094 nonTlbTranscludeDirective = directive;
8097 if (directiveValue == 'element') {
8098 hasElementTranscludeDirective = true;
8099 terminalPriority = directive.priority;
8100 $template = $compileNode;
8101 $compileNode = templateAttrs.$$element =
8102 jqLite(document.createComment(' ' + directiveName + ': ' +
8103 templateAttrs[directiveName] + ' '));
8104 compileNode = $compileNode[0];
8105 replaceWith(jqCollection, sliceArgs($template), compileNode);
8107 childTranscludeFn = compile($template, transcludeFn, terminalPriority,
8108 replaceDirective && replaceDirective.name, {
8110 // - controllerDirectives - otherwise we'll create duplicates controllers
8111 // - newIsolateScopeDirective or templateDirective - combining templates with
8112 // element transclusion doesn't make sense.
8114 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion
8115 // on the same element more than once.
8116 nonTlbTranscludeDirective: nonTlbTranscludeDirective
8119 $template = jqLite(jqLiteClone(compileNode)).contents();
8120 $compileNode.empty(); // clear contents
8121 childTranscludeFn = compile($template, transcludeFn, undefined,
8122 undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope});
8126 if (directive.template) {
8128 assertNoDuplicate('template', templateDirective, directive, $compileNode);
8129 templateDirective = directive;
8131 directiveValue = (isFunction(directive.template))
8132 ? directive.template($compileNode, templateAttrs)
8133 : directive.template;
8135 directiveValue = denormalizeTemplate(directiveValue);
8137 if (directive.replace) {
8138 replaceDirective = directive;
8139 if (jqLiteIsTextNode(directiveValue)) {
8142 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
8144 compileNode = $template[0];
8146 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
8147 throw $compileMinErr('tplrt',
8148 "Template for directive '{0}' must have exactly one root element. {1}",
8152 replaceWith(jqCollection, $compileNode, compileNode);
8154 var newTemplateAttrs = {$attr: {}};
8156 // combine directives from the original node and from the template:
8157 // - take the array of directives for this element
8158 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed)
8159 // - collect directives from the template and sort them by priority
8160 // - combine directives as: processed + template + unprocessed
8161 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs);
8162 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1));
8164 if (newIsolateScopeDirective || newScopeDirective) {
8165 // The original directive caused the current element to be replaced but this element
8166 // also needs to have a new scope, so we need to tell the template directives
8167 // that they would need to get their scope from further up, if they require transclusion
8168 markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective);
8170 directives = directives.concat(templateDirectives).concat(unprocessedDirectives);
8171 mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
8173 ii = directives.length;
8175 $compileNode.html(directiveValue);
8179 if (directive.templateUrl) {
8181 assertNoDuplicate('template', templateDirective, directive, $compileNode);
8182 templateDirective = directive;
8184 if (directive.replace) {
8185 replaceDirective = directive;
8188 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
8189 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, {
8190 controllerDirectives: controllerDirectives,
8191 newScopeDirective: (newScopeDirective !== directive) && newScopeDirective,
8192 newIsolateScopeDirective: newIsolateScopeDirective,
8193 templateDirective: templateDirective,
8194 nonTlbTranscludeDirective: nonTlbTranscludeDirective
8196 ii = directives.length;
8197 } else if (directive.compile) {
8199 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
8200 if (isFunction(linkFn)) {
8201 addLinkFns(null, linkFn, attrStart, attrEnd);
8202 } else if (linkFn) {
8203 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd);
8206 $exceptionHandler(e, startingTag($compileNode));
8210 if (directive.terminal) {
8211 nodeLinkFn.terminal = true;
8212 terminalPriority = Math.max(terminalPriority, directive.priority);
8217 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
8218 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective;
8219 nodeLinkFn.templateOnThisElement = hasTemplate;
8220 nodeLinkFn.transclude = childTranscludeFn;
8222 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective;
8224 // might be normal or delayed nodeLinkFn depending on if templateUrl is present
8227 ////////////////////
8229 function addLinkFns(pre, post, attrStart, attrEnd) {
8231 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd);
8232 pre.require = directive.require;
8233 pre.directiveName = directiveName;
8234 if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
8235 pre = cloneAndAnnotateFn(pre, {isolateScope: true});
8237 preLinkFns.push(pre);
8240 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd);
8241 post.require = directive.require;
8242 post.directiveName = directiveName;
8243 if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
8244 post = cloneAndAnnotateFn(post, {isolateScope: true});
8246 postLinkFns.push(post);
8251 function getControllers(directiveName, require, $element, elementControllers) {
8254 if (isString(require)) {
8255 var match = require.match(REQUIRE_PREFIX_REGEXP);
8256 var name = require.substring(match[0].length);
8257 var inheritType = match[1] || match[3];
8258 var optional = match[2] === '?';
8260 //If only parents then start at the parent element
8261 if (inheritType === '^^') {
8262 $element = $element.parent();
8263 //Otherwise attempt getting the controller from elementControllers in case
8264 //the element is transcluded (and has no data) and to avoid .data if possible
8266 value = elementControllers && elementControllers[name];
8267 value = value && value.instance;
8271 var dataName = '$' + name + 'Controller';
8272 value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName);
8275 if (!value && !optional) {
8276 throw $compileMinErr('ctreq',
8277 "Controller '{0}', required by directive '{1}', can't be found!",
8278 name, directiveName);
8280 } else if (isArray(require)) {
8282 for (var i = 0, ii = require.length; i < ii; i++) {
8283 value[i] = getControllers(directiveName, require[i], $element, elementControllers);
8287 return value || null;
8290 function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope) {
8291 var elementControllers = createMap();
8292 for (var controllerKey in controllerDirectives) {
8293 var directive = controllerDirectives[controllerKey];
8295 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
8298 $transclude: transcludeFn
8301 var controller = directive.controller;
8302 if (controller == '@') {
8303 controller = attrs[directive.name];
8306 var controllerInstance = $controller(controller, locals, true, directive.controllerAs);
8308 // For directives with element transclusion the element is a comment,
8309 // but jQuery .data doesn't support attaching data to comment nodes as it's hard to
8310 // clean up (http://bugs.jquery.com/ticket/8335).
8311 // Instead, we save the controllers for the element in a local hash and attach to .data
8312 // later, once we have the actual element.
8313 elementControllers[directive.name] = controllerInstance;
8314 if (!hasElementTranscludeDirective) {
8315 $element.data('$' + directive.name + 'Controller', controllerInstance.instance);
8318 return elementControllers;
8321 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
8322 var linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element,
8323 attrs, removeScopeBindingWatches, removeControllerBindingWatches;
8325 if (compileNode === linkNode) {
8326 attrs = templateAttrs;
8327 $element = templateAttrs.$$element;
8329 $element = jqLite(linkNode);
8330 attrs = new Attributes($element, templateAttrs);
8333 controllerScope = scope;
8334 if (newIsolateScopeDirective) {
8335 isolateScope = scope.$new(true);
8336 } else if (newScopeDirective) {
8337 controllerScope = scope.$parent;
8340 if (boundTranscludeFn) {
8341 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
8342 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
8343 transcludeFn = controllersBoundTransclude;
8344 transcludeFn.$$boundTransclude = boundTranscludeFn;
8347 if (controllerDirectives) {
8348 elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope);
8351 if (newIsolateScopeDirective) {
8352 // Initialize isolate scope bindings for new isolate scope directive.
8353 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
8354 templateDirective === newIsolateScopeDirective.$$originalDirective)));
8355 compile.$$addScopeClass($element, true);
8356 isolateScope.$$isolateBindings =
8357 newIsolateScopeDirective.$$isolateBindings;
8358 removeScopeBindingWatches = initializeDirectiveBindings(scope, attrs, isolateScope,
8359 isolateScope.$$isolateBindings,
8360 newIsolateScopeDirective);
8361 if (removeScopeBindingWatches) {
8362 isolateScope.$on('$destroy', removeScopeBindingWatches);
8366 // Initialize bindToController bindings
8367 for (var name in elementControllers) {
8368 var controllerDirective = controllerDirectives[name];
8369 var controller = elementControllers[name];
8370 var bindings = controllerDirective.$$bindings.bindToController;
8372 if (controller.identifier && bindings) {
8373 removeControllerBindingWatches =
8374 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
8377 var controllerResult = controller();
8378 if (controllerResult !== controller.instance) {
8379 // If the controller constructor has a return value, overwrite the instance
8380 // from setupControllers
8381 controller.instance = controllerResult;
8382 $element.data('$' + controllerDirective.name + 'Controller', controllerResult);
8383 removeControllerBindingWatches && removeControllerBindingWatches();
8384 removeControllerBindingWatches =
8385 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
8390 for (i = 0, ii = preLinkFns.length; i < ii; i++) {
8391 linkFn = preLinkFns[i];
8392 invokeLinkFn(linkFn,
8393 linkFn.isolateScope ? isolateScope : scope,
8396 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
8402 // We only pass the isolate scope, if the isolate directive has a template,
8403 // otherwise the child elements do not belong to the isolate directive.
8404 var scopeToChild = scope;
8405 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) {
8406 scopeToChild = isolateScope;
8408 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
8411 for (i = postLinkFns.length - 1; i >= 0; i--) {
8412 linkFn = postLinkFns[i];
8413 invokeLinkFn(linkFn,
8414 linkFn.isolateScope ? isolateScope : scope,
8417 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
8422 // This is the function that is injected as `$transclude`.
8423 // Note: all arguments are optional!
8424 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) {
8425 var transcludeControllers;
8427 // No scope passed in:
8428 if (!isScope(scope)) {
8429 futureParentElement = cloneAttachFn;
8430 cloneAttachFn = scope;
8434 if (hasElementTranscludeDirective) {
8435 transcludeControllers = elementControllers;
8437 if (!futureParentElement) {
8438 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
8440 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
8445 // Depending upon the context in which a directive finds itself it might need to have a new isolated
8446 // or child scope created. For instance:
8447 // * if the directive has been pulled into a template because another directive with a higher priority
8448 // asked for element transclusion
8449 // * if the directive itself asks for transclusion but it is at the root of a template and the original
8450 // element was replaced. See https://github.com/angular/angular.js/issues/12936
8451 function markDirectiveScope(directives, isolateScope, newScope) {
8452 for (var j = 0, jj = directives.length; j < jj; j++) {
8453 directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope});
8458 * looks up the directive and decorates it with exception handling and proper parameters. We
8459 * call this the boundDirective.
8461 * @param {string} name name of the directive to look up.
8462 * @param {string} location The directive must be found in specific format.
8463 * String containing any of theses characters:
8465 * * `E`: element name
8469 * @returns {boolean} true if directive was added.
8471 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName,
8473 if (name === ignoreDirective) return null;
8475 if (hasDirectives.hasOwnProperty(name)) {
8476 for (var directive, directives = $injector.get(name + Suffix),
8477 i = 0, ii = directives.length; i < ii; i++) {
8479 directive = directives[i];
8480 if ((isUndefined(maxPriority) || maxPriority > directive.priority) &&
8481 directive.restrict.indexOf(location) != -1) {
8482 if (startAttrName) {
8483 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
8485 tDirectives.push(directive);
8488 } catch (e) { $exceptionHandler(e); }
8496 * looks up the directive and returns true if it is a multi-element directive,
8497 * and therefore requires DOM nodes between -start and -end markers to be grouped
8500 * @param {string} name name of the directive to look up.
8501 * @returns true if directive was registered as multi-element.
8503 function directiveIsMultiElement(name) {
8504 if (hasDirectives.hasOwnProperty(name)) {
8505 for (var directive, directives = $injector.get(name + Suffix),
8506 i = 0, ii = directives.length; i < ii; i++) {
8507 directive = directives[i];
8508 if (directive.multiElement) {
8517 * When the element is replaced with HTML template then the new attributes
8518 * on the template need to be merged with the existing attributes in the DOM.
8519 * The desired effect is to have both of the attributes present.
8521 * @param {object} dst destination attributes (original DOM)
8522 * @param {object} src source attributes (from the directive template)
8524 function mergeTemplateAttributes(dst, src) {
8525 var srcAttr = src.$attr,
8526 dstAttr = dst.$attr,
8527 $element = dst.$$element;
8529 // reapply the old attributes to the new element
8530 forEach(dst, function(value, key) {
8531 if (key.charAt(0) != '$') {
8532 if (src[key] && src[key] !== value) {
8533 value += (key === 'style' ? ';' : ' ') + src[key];
8535 dst.$set(key, value, true, srcAttr[key]);
8539 // copy the new attributes on the old attrs object
8540 forEach(src, function(value, key) {
8541 if (key == 'class') {
8542 safeAddClass($element, value);
8543 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value;
8544 } else if (key == 'style') {
8545 $element.attr('style', $element.attr('style') + ';' + value);
8546 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value;
8547 // `dst` will never contain hasOwnProperty as DOM parser won't let it.
8548 // You will get an "InvalidCharacterError: DOM Exception 5" error if you
8549 // have an attribute like "has-own-property" or "data-has-own-property", etc.
8550 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) {
8552 dstAttr[key] = srcAttr[key];
8558 function compileTemplateUrl(directives, $compileNode, tAttrs,
8559 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) {
8561 afterTemplateNodeLinkFn,
8562 afterTemplateChildLinkFn,
8563 beforeTemplateCompileNode = $compileNode[0],
8564 origAsyncDirective = directives.shift(),
8565 derivedSyncDirective = inherit(origAsyncDirective, {
8566 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
8568 templateUrl = (isFunction(origAsyncDirective.templateUrl))
8569 ? origAsyncDirective.templateUrl($compileNode, tAttrs)
8570 : origAsyncDirective.templateUrl,
8571 templateNamespace = origAsyncDirective.templateNamespace;
8573 $compileNode.empty();
8575 $templateRequest(templateUrl)
8576 .then(function(content) {
8577 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn;
8579 content = denormalizeTemplate(content);
8581 if (origAsyncDirective.replace) {
8582 if (jqLiteIsTextNode(content)) {
8585 $template = removeComments(wrapTemplate(templateNamespace, trim(content)));
8587 compileNode = $template[0];
8589 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
8590 throw $compileMinErr('tplrt',
8591 "Template for directive '{0}' must have exactly one root element. {1}",
8592 origAsyncDirective.name, templateUrl);
8595 tempTemplateAttrs = {$attr: {}};
8596 replaceWith($rootElement, $compileNode, compileNode);
8597 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs);
8599 if (isObject(origAsyncDirective.scope)) {
8600 // the original directive that caused the template to be loaded async required
8602 markDirectiveScope(templateDirectives, true);
8604 directives = templateDirectives.concat(directives);
8605 mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
8607 compileNode = beforeTemplateCompileNode;
8608 $compileNode.html(content);
8611 directives.unshift(derivedSyncDirective);
8613 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs,
8614 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns,
8615 previousCompileContext);
8616 forEach($rootElement, function(node, i) {
8617 if (node == compileNode) {
8618 $rootElement[i] = $compileNode[0];
8621 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
8623 while (linkQueue.length) {
8624 var scope = linkQueue.shift(),
8625 beforeTemplateLinkNode = linkQueue.shift(),
8626 linkRootElement = linkQueue.shift(),
8627 boundTranscludeFn = linkQueue.shift(),
8628 linkNode = $compileNode[0];
8630 if (scope.$$destroyed) continue;
8632 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
8633 var oldClasses = beforeTemplateLinkNode.className;
8635 if (!(previousCompileContext.hasElementTranscludeDirective &&
8636 origAsyncDirective.replace)) {
8637 // it was cloned therefore we have to clone as well.
8638 linkNode = jqLiteClone(compileNode);
8640 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
8642 // Copy in CSS classes from original node
8643 safeAddClass(jqLite(linkNode), oldClasses);
8645 if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
8646 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
8648 childBoundTranscludeFn = boundTranscludeFn;
8650 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
8651 childBoundTranscludeFn);
8656 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
8657 var childBoundTranscludeFn = boundTranscludeFn;
8658 if (scope.$$destroyed) return;
8660 linkQueue.push(scope,
8663 childBoundTranscludeFn);
8665 if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
8666 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
8668 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn);
8675 * Sorting function for bound directives.
8677 function byPriority(a, b) {
8678 var diff = b.priority - a.priority;
8679 if (diff !== 0) return diff;
8680 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1;
8681 return a.index - b.index;
8684 function assertNoDuplicate(what, previousDirective, directive, element) {
8686 function wrapModuleNameIfDefined(moduleName) {
8688 (' (module: ' + moduleName + ')') :
8692 if (previousDirective) {
8693 throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}',
8694 previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName),
8695 directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element));
8700 function addTextInterpolateDirective(directives, text) {
8701 var interpolateFn = $interpolate(text, true);
8702 if (interpolateFn) {
8705 compile: function textInterpolateCompileFn(templateNode) {
8706 var templateNodeParent = templateNode.parent(),
8707 hasCompileParent = !!templateNodeParent.length;
8709 // When transcluding a template that has bindings in the root
8710 // we don't have a parent and thus need to add the class during linking fn.
8711 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent);
8713 return function textInterpolateLinkFn(scope, node) {
8714 var parent = node.parent();
8715 if (!hasCompileParent) compile.$$addBindingClass(parent);
8716 compile.$$addBindingInfo(parent, interpolateFn.expressions);
8717 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
8718 node[0].nodeValue = value;
8727 function wrapTemplate(type, template) {
8728 type = lowercase(type || 'html');
8732 var wrapper = document.createElement('div');
8733 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>';
8734 return wrapper.childNodes[0].childNodes;
8741 function getTrustedContext(node, attrNormalizedName) {
8742 if (attrNormalizedName == "srcdoc") {
8745 var tag = nodeName_(node);
8746 // maction[xlink:href] can source SVG. It's not limited to <maction>.
8747 if (attrNormalizedName == "xlinkHref" ||
8748 (tag == "form" && attrNormalizedName == "action") ||
8749 (tag != "img" && (attrNormalizedName == "src" ||
8750 attrNormalizedName == "ngSrc"))) {
8751 return $sce.RESOURCE_URL;
8756 function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) {
8757 var trustedContext = getTrustedContext(node, name);
8758 allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing;
8760 var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing);
8762 // no interpolation found -> ignore
8763 if (!interpolateFn) return;
8766 if (name === "multiple" && nodeName_(node) === "select") {
8767 throw $compileMinErr("selmulti",
8768 "Binding to the 'multiple' attribute is not supported. Element: {0}",
8774 compile: function() {
8776 pre: function attrInterpolatePreLinkFn(scope, element, attr) {
8777 var $$observers = (attr.$$observers || (attr.$$observers = createMap()));
8779 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
8780 throw $compileMinErr('nodomevents',
8781 "Interpolations for HTML DOM event attributes are disallowed. Please use the " +
8782 "ng- versions (such as ng-click instead of onclick) instead.");
8785 // If the attribute has changed since last $interpolate()ed
8786 var newValue = attr[name];
8787 if (newValue !== value) {
8788 // we need to interpolate again since the attribute value has been updated
8789 // (e.g. by another directive's compile function)
8790 // ensure unset/empty values make interpolateFn falsy
8791 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing);
8795 // if attribute was updated so that there is no interpolation going on we don't want to
8796 // register any observers
8797 if (!interpolateFn) return;
8799 // initialize attr object so that it's ready in case we need the value for isolate
8800 // scope initialization, otherwise the value would not be available from isolate
8801 // directive's linking fn during linking phase
8802 attr[name] = interpolateFn(scope);
8804 ($$observers[name] || ($$observers[name] = [])).$$inter = true;
8805 (attr.$$observers && attr.$$observers[name].$$scope || scope).
8806 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) {
8807 //special case for class attribute addition + removal
8808 //so that class changes can tap into the animation
8809 //hooks provided by the $animate service. Be sure to
8810 //skip animations when the first digest occurs (when
8811 //both the new and the old values are the same) since
8812 //the CSS classes are the non-interpolated values
8813 if (name === 'class' && newValue != oldValue) {
8814 attr.$updateClass(newValue, oldValue);
8816 attr.$set(name, newValue);
8827 * This is a special jqLite.replaceWith, which can replace items which
8828 * have no parents, provided that the containing jqLite collection is provided.
8830 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
8831 * in the root of the tree.
8832 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep
8833 * the shell, but replace its DOM node reference.
8834 * @param {Node} newNode The new DOM node.
8836 function replaceWith($rootElement, elementsToRemove, newNode) {
8837 var firstElementToRemove = elementsToRemove[0],
8838 removeCount = elementsToRemove.length,
8839 parent = firstElementToRemove.parentNode,
8843 for (i = 0, ii = $rootElement.length; i < ii; i++) {
8844 if ($rootElement[i] == firstElementToRemove) {
8845 $rootElement[i++] = newNode;
8846 for (var j = i, j2 = j + removeCount - 1,
8847 jj = $rootElement.length;
8848 j < jj; j++, j2++) {
8850 $rootElement[j] = $rootElement[j2];
8852 delete $rootElement[j];
8855 $rootElement.length -= removeCount - 1;
8857 // If the replaced element is also the jQuery .context then replace it
8858 // .context is a deprecated jQuery api, so we should set it only when jQuery set it
8859 // http://api.jquery.com/context/
8860 if ($rootElement.context === firstElementToRemove) {
8861 $rootElement.context = newNode;
8869 parent.replaceChild(newNode, firstElementToRemove);
8872 // TODO(perf): what's this document fragment for? is it needed? can we at least reuse it?
8873 var fragment = document.createDocumentFragment();
8874 fragment.appendChild(firstElementToRemove);
8876 if (jqLite.hasData(firstElementToRemove)) {
8877 // Copy over user data (that includes Angular's $scope etc.). Don't copy private
8878 // data here because there's no public interface in jQuery to do that and copying over
8879 // event listeners (which is the main use of private data) wouldn't work anyway.
8880 jqLite.data(newNode, jqLite.data(firstElementToRemove));
8882 // Remove data of the replaced element. We cannot just call .remove()
8883 // on the element it since that would deallocate scope that is needed
8884 // for the new node. Instead, remove the data "manually".
8886 delete jqLite.cache[firstElementToRemove[jqLite.expando]];
8888 // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after
8889 // the replaced element. The cleanData version monkey-patched by Angular would cause
8890 // the scope to be trashed and we do need the very same scope to work with the new
8891 // element. However, we cannot just cache the non-patched version and use it here as
8892 // that would break if another library patches the method after Angular does (one
8893 // example is jQuery UI). Instead, set a flag indicating scope destroying should be
8894 // skipped this one time.
8895 skipDestroyOnNextJQueryCleanData = true;
8896 jQuery.cleanData([firstElementToRemove]);
8900 for (var k = 1, kk = elementsToRemove.length; k < kk; k++) {
8901 var element = elementsToRemove[k];
8902 jqLite(element).remove(); // must do this way to clean up expando
8903 fragment.appendChild(element);
8904 delete elementsToRemove[k];
8907 elementsToRemove[0] = newNode;
8908 elementsToRemove.length = 1;
8912 function cloneAndAnnotateFn(fn, annotation) {
8913 return extend(function() { return fn.apply(null, arguments); }, fn, annotation);
8917 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) {
8919 linkFn(scope, $element, attrs, controllers, transcludeFn);
8921 $exceptionHandler(e, startingTag($element));
8926 // Set up $watches for isolate scope and controller bindings. This process
8927 // only occurs for isolate scopes and new scopes with controllerAs.
8928 function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
8929 var removeWatchCollection = [];
8930 forEach(bindings, function(definition, scopeName) {
8931 var attrName = definition.attrName,
8932 optional = definition.optional,
8933 mode = definition.mode, // @, =, or &
8935 parentGet, parentSet, compare;
8940 if (!optional && !hasOwnProperty.call(attrs, attrName)) {
8941 destination[scopeName] = attrs[attrName] = void 0;
8943 attrs.$observe(attrName, function(value) {
8944 if (isString(value)) {
8945 destination[scopeName] = value;
8948 attrs.$$observers[attrName].$$scope = scope;
8949 if (isString(attrs[attrName])) {
8950 // If the attribute has been provided then we trigger an interpolation to ensure
8951 // the value is there for use in the link fn
8952 destination[scopeName] = $interpolate(attrs[attrName])(scope);
8957 if (!hasOwnProperty.call(attrs, attrName)) {
8958 if (optional) break;
8959 attrs[attrName] = void 0;
8961 if (optional && !attrs[attrName]) break;
8963 parentGet = $parse(attrs[attrName]);
8964 if (parentGet.literal) {
8967 compare = function(a, b) { return a === b || (a !== a && b !== b); };
8969 parentSet = parentGet.assign || function() {
8970 // reset the change, or we will throw this exception on every $digest
8971 lastValue = destination[scopeName] = parentGet(scope);
8972 throw $compileMinErr('nonassign',
8973 "Expression '{0}' used with directive '{1}' is non-assignable!",
8974 attrs[attrName], directive.name);
8976 lastValue = destination[scopeName] = parentGet(scope);
8977 var parentValueWatch = function parentValueWatch(parentValue) {
8978 if (!compare(parentValue, destination[scopeName])) {
8979 // we are out of sync and need to copy
8980 if (!compare(parentValue, lastValue)) {
8981 // parent changed and it has precedence
8982 destination[scopeName] = parentValue;
8984 // if the parent can be assigned then do so
8985 parentSet(scope, parentValue = destination[scopeName]);
8988 return lastValue = parentValue;
8990 parentValueWatch.$stateful = true;
8992 if (definition.collection) {
8993 removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
8995 removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
8997 removeWatchCollection.push(removeWatch);
9001 // Don't assign Object.prototype method to scope
9002 parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;
9004 // Don't assign noop to destination if expression is not valid
9005 if (parentGet === noop && optional) break;
9007 destination[scopeName] = function(locals) {
9008 return parentGet(scope, locals);
9014 return removeWatchCollection.length && function removeWatches() {
9015 for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) {
9016 removeWatchCollection[i]();
9023 var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
9025 * Converts all accepted directives format into proper directive name.
9026 * @param name Name to normalize
9028 function directiveNormalize(name) {
9029 return camelCase(name.replace(PREFIX_REGEXP, ''));
9034 * @name $compile.directive.Attributes
9037 * A shared object between directive compile / linking functions which contains normalized DOM
9038 * element attributes. The values reflect current binding state `{{ }}`. The normalization is
9039 * needed since all of these are treated as equivalent in Angular:
9042 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
9048 * @name $compile.directive.Attributes#$attr
9051 * A map of DOM element attribute names to the normalized name. This is
9052 * needed to do reverse lookup from normalized name back to actual name.
9058 * @name $compile.directive.Attributes#$set
9062 * Set DOM element attribute value.
9065 * @param {string} name Normalized element attribute name of the property to modify. The name is
9066 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
9067 * property to the original name.
9068 * @param {string} value Value to set the attribute to. The value can be an interpolated string.
9074 * Closure compiler type information
9077 function nodesetLinkingFn(
9078 /* angular.Scope */ scope,
9079 /* NodeList */ nodeList,
9080 /* Element */ rootElement,
9081 /* function(Function) */ boundTranscludeFn
9084 function directiveLinkingFn(
9085 /* nodesetLinkingFn */ nodesetLinkingFn,
9086 /* angular.Scope */ scope,
9088 /* Element */ rootElement,
9089 /* function(Function) */ boundTranscludeFn
9092 function tokenDifference(str1, str2) {
9094 tokens1 = str1.split(/\s+/),
9095 tokens2 = str2.split(/\s+/);
9098 for (var i = 0; i < tokens1.length; i++) {
9099 var token = tokens1[i];
9100 for (var j = 0; j < tokens2.length; j++) {
9101 if (token == tokens2[j]) continue outer;
9103 values += (values.length > 0 ? ' ' : '') + token;
9108 function removeComments(jqNodes) {
9109 jqNodes = jqLite(jqNodes);
9110 var i = jqNodes.length;
9117 var node = jqNodes[i];
9118 if (node.nodeType === NODE_TYPE_COMMENT) {
9119 splice.call(jqNodes, i, 1);
9125 var $controllerMinErr = minErr('$controller');
9128 var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
9129 function identifierForController(controller, ident) {
9130 if (ident && isString(ident)) return ident;
9131 if (isString(controller)) {
9132 var match = CNTRL_REG.exec(controller);
9133 if (match) return match[3];
9140 * @name $controllerProvider
9142 * The {@link ng.$controller $controller service} is used by Angular to create new
9145 * This provider allows controller registration via the
9146 * {@link ng.$controllerProvider#register register} method.
9148 function $ControllerProvider() {
9149 var controllers = {},
9154 * @name $controllerProvider#register
9155 * @param {string|Object} name Controller name, or an object map of controllers where the keys are
9156 * the names and the values are the constructors.
9157 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI
9158 * annotations in the array notation).
9160 this.register = function(name, constructor) {
9161 assertNotHasOwnProperty(name, 'controller');
9162 if (isObject(name)) {
9163 extend(controllers, name);
9165 controllers[name] = constructor;
9171 * @name $controllerProvider#allowGlobals
9172 * @description If called, allows `$controller` to find controller constructors on `window`
9174 this.allowGlobals = function() {
9179 this.$get = ['$injector', '$window', function($injector, $window) {
9184 * @requires $injector
9186 * @param {Function|string} constructor If called with a function then it's considered to be the
9187 * controller constructor function. Otherwise it's considered to be a string which is used
9188 * to retrieve the controller constructor using the following steps:
9190 * * check if a controller with given name is registered via `$controllerProvider`
9191 * * check if evaluating the string on the current scope returns a constructor
9192 * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
9193 * `window` object (not recommended)
9195 * The string can use the `controller as property` syntax, where the controller instance is published
9196 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this
9197 * to work correctly.
9199 * @param {Object} locals Injection locals for Controller.
9200 * @return {Object} Instance of given controller.
9203 * `$controller` service is responsible for instantiating controllers.
9205 * It's just a simple call to {@link auto.$injector $injector}, but extracted into
9206 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788).
9208 return function(expression, locals, later, ident) {
9210 // param `later` --- indicates that the controller's constructor is invoked at a later time.
9211 // If true, $controller will allocate the object with the correct
9212 // prototype chain, but will not invoke the controller until a returned
9213 // callback is invoked.
9214 // param `ident` --- An optional label which overrides the label parsed from the controller
9215 // expression, if any.
9216 var instance, match, constructor, identifier;
9217 later = later === true;
9218 if (ident && isString(ident)) {
9222 if (isString(expression)) {
9223 match = expression.match(CNTRL_REG);
9225 throw $controllerMinErr('ctrlfmt',
9226 "Badly formed controller string '{0}'. " +
9227 "Must match `__name__ as __id__` or `__name__`.", expression);
9229 constructor = match[1],
9230 identifier = identifier || match[3];
9231 expression = controllers.hasOwnProperty(constructor)
9232 ? controllers[constructor]
9233 : getter(locals.$scope, constructor, true) ||
9234 (globals ? getter($window, constructor, true) : undefined);
9236 assertArgFn(expression, constructor, true);
9240 // Instantiate controller later:
9241 // This machinery is used to create an instance of the object before calling the
9242 // controller's constructor itself.
9244 // This allows properties to be added to the controller before the constructor is
9245 // invoked. Primarily, this is used for isolate scope bindings in $compile.
9247 // This feature is not intended for use by applications, and is thus not documented
9249 // Object creation: http://jsperf.com/create-constructor/2
9250 var controllerPrototype = (isArray(expression) ?
9251 expression[expression.length - 1] : expression).prototype;
9252 instance = Object.create(controllerPrototype || null);
9255 addIdentifier(locals, identifier, instance, constructor || expression.name);
9259 return instantiate = extend(function() {
9260 var result = $injector.invoke(expression, instance, locals, constructor);
9261 if (result !== instance && (isObject(result) || isFunction(result))) {
9264 // If result changed, re-assign controllerAs value to scope.
9265 addIdentifier(locals, identifier, instance, constructor || expression.name);
9271 identifier: identifier
9275 instance = $injector.instantiate(expression, locals, constructor);
9278 addIdentifier(locals, identifier, instance, constructor || expression.name);
9284 function addIdentifier(locals, identifier, instance, name) {
9285 if (!(locals && isObject(locals.$scope))) {
9286 throw minErr('$controller')('noscp',
9287 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.",
9291 locals.$scope[identifier] = instance;
9302 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object.
9305 <example module="documentExample">
9306 <file name="index.html">
9307 <div ng-controller="ExampleController">
9308 <p>$document title: <b ng-bind="title"></b></p>
9309 <p>window.document title: <b ng-bind="windowTitle"></b></p>
9312 <file name="script.js">
9313 angular.module('documentExample', [])
9314 .controller('ExampleController', ['$scope', '$document', function($scope, $document) {
9315 $scope.title = $document[0].title;
9316 $scope.windowTitle = angular.element(window.document)[0].title;
9321 function $DocumentProvider() {
9322 this.$get = ['$window', function(window) {
9323 return jqLite(window.document);
9329 * @name $exceptionHandler
9333 * Any uncaught exception in angular expressions is delegated to this service.
9334 * The default implementation simply delegates to `$log.error` which logs it into
9335 * the browser console.
9337 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by
9338 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing.
9343 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() {
9344 * return function(exception, cause) {
9345 * exception.message += ' (caused by "' + cause + '")';
9351 * This example will override the normal action of `$exceptionHandler`, to make angular
9352 * exceptions fail hard when they happen, instead of just logging to the console.
9355 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind`
9356 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler}
9357 * (unless executed during a digest).
9359 * If you wish, you can manually delegate exceptions, e.g.
9360 * `try { ... } catch(e) { $exceptionHandler(e); }`
9362 * @param {Error} exception Exception associated with the error.
9363 * @param {string=} cause optional information about the context in which
9364 * the error was thrown.
9367 function $ExceptionHandlerProvider() {
9368 this.$get = ['$log', function($log) {
9369 return function(exception, cause) {
9370 $log.error.apply($log, arguments);
9375 var $$ForceReflowProvider = function() {
9376 this.$get = ['$document', function($document) {
9377 return function(domNode) {
9378 //the line below will force the browser to perform a repaint so
9379 //that all the animated elements within the animation frame will
9380 //be properly updated and drawn on screen. This is required to
9381 //ensure that the preparation animation is properly flushed so that
9382 //the active state picks up from there. DO NOT REMOVE THIS LINE.
9383 //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH
9384 //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND
9385 //WILL TAKE YEARS AWAY FROM YOUR LIFE.
9387 if (!domNode.nodeType && domNode instanceof jqLite) {
9388 domNode = domNode[0];
9391 domNode = $document[0].body;
9393 return domNode.offsetWidth + 1;
9398 var APPLICATION_JSON = 'application/json';
9399 var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
9400 var JSON_START = /^\[|^\{(?!\{)/;
9405 var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
9406 var $httpMinErr = minErr('$http');
9407 var $httpMinErrLegacyFn = function(method) {
9409 throw $httpMinErr('legacy', 'The method `{0}` on the promise returned from `$http` has been disabled.', method);
9413 function serializeValue(v) {
9415 return isDate(v) ? v.toISOString() : toJson(v);
9421 function $HttpParamSerializerProvider() {
9424 * @name $httpParamSerializer
9427 * Default {@link $http `$http`} params serializer that converts objects to strings
9428 * according to the following rules:
9430 * * `{'foo': 'bar'}` results in `foo=bar`
9431 * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object)
9432 * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element)
9433 * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object)
9435 * Note that serializer will sort the request parameters alphabetically.
9438 this.$get = function() {
9439 return function ngParamSerializer(params) {
9440 if (!params) return '';
9442 forEachSorted(params, function(value, key) {
9443 if (value === null || isUndefined(value)) return;
9444 if (isArray(value)) {
9445 forEach(value, function(v, k) {
9446 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v)));
9449 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
9453 return parts.join('&');
9458 function $HttpParamSerializerJQLikeProvider() {
9461 * @name $httpParamSerializerJQLike
9464 * Alternative {@link $http `$http`} params serializer that follows
9465 * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic.
9466 * The serializer will also sort the params alphabetically.
9468 * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property:
9475 * paramSerializer: '$httpParamSerializerJQLike'
9479 * It is also possible to set it as the default `paramSerializer` in the
9480 * {@link $httpProvider#defaults `$httpProvider`}.
9482 * Additionally, you can inject the serializer and use it explicitly, for example to serialize
9483 * form data for submission:
9486 * .controller(function($http, $httpParamSerializerJQLike) {
9492 * data: $httpParamSerializerJQLike(myData),
9494 * 'Content-Type': 'application/x-www-form-urlencoded'
9502 this.$get = function() {
9503 return function jQueryLikeParamSerializer(params) {
9504 if (!params) return '';
9506 serialize(params, '', true);
9507 return parts.join('&');
9509 function serialize(toSerialize, prefix, topLevel) {
9510 if (toSerialize === null || isUndefined(toSerialize)) return;
9511 if (isArray(toSerialize)) {
9512 forEach(toSerialize, function(value, index) {
9513 serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']');
9515 } else if (isObject(toSerialize) && !isDate(toSerialize)) {
9516 forEachSorted(toSerialize, function(value, key) {
9517 serialize(value, prefix +
9518 (topLevel ? '' : '[') +
9520 (topLevel ? '' : ']'));
9523 parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize)));
9530 function defaultHttpResponseTransform(data, headers) {
9531 if (isString(data)) {
9532 // Strip json vulnerability protection prefix and trim whitespace
9533 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim();
9536 var contentType = headers('Content-Type');
9537 if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) {
9538 data = fromJson(tempData);
9546 function isJsonLike(str) {
9547 var jsonStart = str.match(JSON_START);
9548 return jsonStart && JSON_ENDS[jsonStart[0]].test(str);
9552 * Parse headers into key value object
9554 * @param {string} headers Raw headers as a string
9555 * @returns {Object} Parsed headers as key value object
9557 function parseHeaders(headers) {
9558 var parsed = createMap(), i;
9560 function fillInParsed(key, val) {
9562 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
9566 if (isString(headers)) {
9567 forEach(headers.split('\n'), function(line) {
9568 i = line.indexOf(':');
9569 fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1)));
9571 } else if (isObject(headers)) {
9572 forEach(headers, function(headerVal, headerKey) {
9573 fillInParsed(lowercase(headerKey), trim(headerVal));
9582 * Returns a function that provides access to parsed headers.
9584 * Headers are lazy parsed when first requested.
9587 * @param {(string|Object)} headers Headers to provide access to.
9588 * @returns {function(string=)} Returns a getter function which if called with:
9590 * - if called with single an argument returns a single header value or null
9591 * - if called with no arguments returns an object containing all headers.
9593 function headersGetter(headers) {
9596 return function(name) {
9597 if (!headersObj) headersObj = parseHeaders(headers);
9600 var value = headersObj[lowercase(name)];
9601 if (value === void 0) {
9613 * Chain all given functions
9615 * This function is used for both request and response transforming
9617 * @param {*} data Data to transform.
9618 * @param {function(string=)} headers HTTP headers getter fn.
9619 * @param {number} status HTTP status code of the response.
9620 * @param {(Function|Array.<Function>)} fns Function or an array of functions.
9621 * @returns {*} Transformed data.
9623 function transformData(data, headers, status, fns) {
9624 if (isFunction(fns)) {
9625 return fns(data, headers, status);
9628 forEach(fns, function(fn) {
9629 data = fn(data, headers, status);
9636 function isSuccess(status) {
9637 return 200 <= status && status < 300;
9643 * @name $httpProvider
9645 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service.
9647 function $HttpProvider() {
9650 * @name $httpProvider#defaults
9653 * Object containing default values for all {@link ng.$http $http} requests.
9655 * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`}
9656 * that will provide the cache for all requests who set their `cache` property to `true`.
9657 * If you set the `defaults.cache = false` then only requests that specify their own custom
9658 * cache object will be cached. See {@link $http#caching $http Caching} for more information.
9660 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token.
9661 * Defaults value is `'XSRF-TOKEN'`.
9663 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the
9664 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`.
9666 * - **`defaults.headers`** - {Object} - Default headers for all $http requests.
9667 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on
9668 * setting default headers.
9669 * - **`defaults.headers.common`**
9670 * - **`defaults.headers.post`**
9671 * - **`defaults.headers.put`**
9672 * - **`defaults.headers.patch`**
9675 * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function
9676 * used to the prepare string representation of request parameters (specified as an object).
9677 * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}.
9678 * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}.
9681 var defaults = this.defaults = {
9682 // transform incoming response data
9683 transformResponse: [defaultHttpResponseTransform],
9685 // transform outgoing request data
9686 transformRequest: [function(d) {
9687 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d;
9693 'Accept': 'application/json, text/plain, */*'
9695 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
9696 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
9697 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON)
9700 xsrfCookieName: 'XSRF-TOKEN',
9701 xsrfHeaderName: 'X-XSRF-TOKEN',
9703 paramSerializer: '$httpParamSerializer'
9706 var useApplyAsync = false;
9709 * @name $httpProvider#useApplyAsync
9712 * Configure $http service to combine processing of multiple http responses received at around
9713 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in
9714 * significant performance improvement for bigger applications that make many HTTP requests
9715 * concurrently (common during application bootstrap).
9717 * Defaults to false. If no value is specified, returns the current configured value.
9719 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred
9720 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window
9721 * to load and share the same digest cycle.
9723 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
9724 * otherwise, returns the current configured value.
9726 this.useApplyAsync = function(value) {
9727 if (isDefined(value)) {
9728 useApplyAsync = !!value;
9731 return useApplyAsync;
9734 var useLegacyPromise = true;
9737 * @name $httpProvider#useLegacyPromiseExtensions
9740 * Configure `$http` service to return promises without the shorthand methods `success` and `error`.
9741 * This should be used to make sure that applications work without these methods.
9743 * Defaults to true. If no value is specified, returns the current configured value.
9745 * @param {boolean=} value If true, `$http` will return a promise with the deprecated legacy `success` and `error` methods.
9747 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
9748 * otherwise, returns the current configured value.
9750 this.useLegacyPromiseExtensions = function(value) {
9751 if (isDefined(value)) {
9752 useLegacyPromise = !!value;
9755 return useLegacyPromise;
9760 * @name $httpProvider#interceptors
9763 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http}
9764 * pre-processing of request or postprocessing of responses.
9766 * These service factories are ordered by request, i.e. they are applied in the same order as the
9767 * array, on request, but reverse order, on response.
9769 * {@link ng.$http#interceptors Interceptors detailed info}
9771 var interceptorFactories = this.interceptors = [];
9773 this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector',
9774 function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) {
9776 var defaultCache = $cacheFactory('$http');
9779 * Make sure that default param serializer is exposed as a function
9781 defaults.paramSerializer = isString(defaults.paramSerializer) ?
9782 $injector.get(defaults.paramSerializer) : defaults.paramSerializer;
9785 * Interceptors stored in reverse order. Inner interceptors before outer interceptors.
9786 * The reversal is needed so that we can build up the interception chain around the
9789 var reversedInterceptors = [];
9791 forEach(interceptorFactories, function(interceptorFactory) {
9792 reversedInterceptors.unshift(isString(interceptorFactory)
9793 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory));
9800 * @requires ng.$httpBackend
9801 * @requires $cacheFactory
9802 * @requires $rootScope
9804 * @requires $injector
9807 * The `$http` service is a core Angular service that facilitates communication with the remote
9808 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest)
9809 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP).
9811 * For unit testing applications that use `$http` service, see
9812 * {@link ngMock.$httpBackend $httpBackend mock}.
9814 * For a higher level of abstraction, please check out the {@link ngResource.$resource
9815 * $resource} service.
9817 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by
9818 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage
9819 * it is important to familiarize yourself with these APIs and the guarantees they provide.
9823 * The `$http` service is a function which takes a single argument — a {@link $http#usage configuration object} —
9824 * that is used to generate an HTTP request and returns a {@link ng.$q promise}.
9827 * // Simple GET request example:
9831 * }).then(function successCallback(response) {
9832 * // this callback will be called asynchronously
9833 * // when the response is available
9834 * }, function errorCallback(response) {
9835 * // called asynchronously if an error occurs
9836 * // or server returns response with an error status.
9840 * The response object has these properties:
9842 * - **data** – `{string|Object}` – The response body transformed with the transform
9844 * - **status** – `{number}` – HTTP status code of the response.
9845 * - **headers** – `{function([headerName])}` – Header getter function.
9846 * - **config** – `{Object}` – The configuration object that was used to generate the request.
9847 * - **statusText** – `{string}` – HTTP status text of the response.
9849 * A response status code between 200 and 299 is considered a success status and
9850 * will result in the success callback being called. Note that if the response is a redirect,
9851 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be
9852 * called for such responses.
9855 * ## Shortcut methods
9857 * Shortcut methods are also available. All shortcut methods require passing in the URL, and
9858 * request data must be passed in for POST/PUT requests. An optional config can be passed as the
9862 * $http.get('/someUrl', config).then(successCallback, errorCallback);
9863 * $http.post('/someUrl', data, config).then(successCallback, errorCallback);
9866 * Complete list of shortcut methods:
9868 * - {@link ng.$http#get $http.get}
9869 * - {@link ng.$http#head $http.head}
9870 * - {@link ng.$http#post $http.post}
9871 * - {@link ng.$http#put $http.put}
9872 * - {@link ng.$http#delete $http.delete}
9873 * - {@link ng.$http#jsonp $http.jsonp}
9874 * - {@link ng.$http#patch $http.patch}
9877 * ## Writing Unit Tests that use $http
9878 * When unit testing (using {@link ngMock ngMock}), it is necessary to call
9879 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending
9880 * request using trained responses.
9883 * $httpBackend.expectGET(...);
9885 * $httpBackend.flush();
9888 * ## Deprecation Notice
9889 * <div class="alert alert-danger">
9890 * The `$http` legacy promise methods `success` and `error` have been deprecated.
9891 * Use the standard `then` method instead.
9892 * If {@link $httpProvider#useLegacyPromiseExtensions `$httpProvider.useLegacyPromiseExtensions`} is set to
9893 * `false` then these methods will throw {@link $http:legacy `$http/legacy`} error.
9896 * ## Setting HTTP Headers
9898 * The $http service will automatically add certain HTTP headers to all requests. These defaults
9899 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration
9900 * object, which currently contains this default configuration:
9902 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
9903 * - `Accept: application/json, text/plain, * / *`
9904 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests)
9905 * - `Content-Type: application/json`
9906 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests)
9907 * - `Content-Type: application/json`
9909 * To add or overwrite these defaults, simply add or remove a property from these configuration
9910 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
9911 * with the lowercased HTTP method name as the key, e.g.
9912 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`.
9914 * The defaults can also be set at runtime via the `$http.defaults` object in the same
9915 * fashion. For example:
9918 * module.run(function($http) {
9919 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'
9923 * In addition, you can supply a `headers` property in the config object passed when
9924 * calling `$http(config)`, which overrides the defaults without changing them globally.
9926 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis,
9927 * Use the `headers` property, setting the desired header to `undefined`. For example:
9932 * url: 'http://example.com',
9934 * 'Content-Type': undefined
9936 * data: { test: 'test' }
9939 * $http(req).then(function(){...}, function(){...});
9942 * ## Transforming Requests and Responses
9944 * Both requests and responses can be transformed using transformation functions: `transformRequest`
9945 * and `transformResponse`. These properties can be a single function that returns
9946 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions,
9947 * which allows you to `push` or `unshift` a new transformation function into the transformation chain.
9949 * ### Default Transformations
9951 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and
9952 * `defaults.transformResponse` properties. If a request does not provide its own transformations
9953 * then these will be applied.
9955 * You can augment or replace the default transformations by modifying these properties by adding to or
9956 * replacing the array.
9958 * Angular provides the following default transformations:
9960 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`):
9962 * - If the `data` property of the request configuration object contains an object, serialize it
9965 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`):
9967 * - If XSRF prefix is detected, strip it (see Security Considerations section below).
9968 * - If JSON response is detected, deserialize it using a JSON parser.
9971 * ### Overriding the Default Transformations Per Request
9973 * If you wish override the request/response transformations only for a single request then provide
9974 * `transformRequest` and/or `transformResponse` properties on the configuration object passed
9977 * Note that if you provide these properties on the config object the default transformations will be
9978 * overwritten. If you wish to augment the default transformations then you must include them in your
9979 * local transformation array.
9981 * The following code demonstrates adding a new response transformation to be run after the default response
9982 * transformations have been run.
9985 * function appendTransform(defaults, transform) {
9987 * // We can't guarantee that the default transformation is an array
9988 * defaults = angular.isArray(defaults) ? defaults : [defaults];
9990 * // Append the new transformation to the defaults
9991 * return defaults.concat(transform);
9997 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) {
9998 * return doTransform(value);
10006 * To enable caching, set the request configuration `cache` property to `true` (to use default
10007 * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}).
10008 * When the cache is enabled, `$http` stores the response from the server in the specified
10009 * cache. The next time the same request is made, the response is served from the cache without
10010 * sending a request to the server.
10012 * Note that even if the response is served from cache, delivery of the data is asynchronous in
10013 * the same way that real requests are.
10015 * If there are multiple GET requests for the same URL that should be cached using the same
10016 * cache, but the cache is not populated yet, only one request to the server will be made and
10017 * the remaining requests will be fulfilled using the response from the first request.
10019 * You can change the default cache to a new object (built with
10020 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the
10021 * {@link ng.$http#defaults `$http.defaults.cache`} property. All requests who set
10022 * their `cache` property to `true` will now use this cache object.
10024 * If you set the default cache to `false` then only requests that specify their own custom
10025 * cache object will be cached.
10029 * Before you start creating interceptors, be sure to understand the
10030 * {@link ng.$q $q and deferred/promise APIs}.
10032 * For purposes of global error handling, authentication, or any kind of synchronous or
10033 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be
10034 * able to intercept requests before they are handed to the server and
10035 * responses before they are handed over to the application code that
10036 * initiated these requests. The interceptors leverage the {@link ng.$q
10037 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing.
10039 * The interceptors are service factories that are registered with the `$httpProvider` by
10040 * adding them to the `$httpProvider.interceptors` array. The factory is called and
10041 * injected with dependencies (if specified) and returns the interceptor.
10043 * There are two kinds of interceptors (and two kinds of rejection interceptors):
10045 * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to
10046 * modify the `config` object or create a new one. The function needs to return the `config`
10047 * object directly, or a promise containing the `config` or a new `config` object.
10048 * * `requestError`: interceptor gets called when a previous interceptor threw an error or
10049 * resolved with a rejection.
10050 * * `response`: interceptors get called with http `response` object. The function is free to
10051 * modify the `response` object or create a new one. The function needs to return the `response`
10052 * object directly, or as a promise containing the `response` or a new `response` object.
10053 * * `responseError`: interceptor gets called when a previous interceptor threw an error or
10054 * resolved with a rejection.
10058 * // register the interceptor as a service
10059 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
10061 * // optional method
10062 * 'request': function(config) {
10063 * // do something on success
10067 * // optional method
10068 * 'requestError': function(rejection) {
10069 * // do something on error
10070 * if (canRecover(rejection)) {
10071 * return responseOrNewPromise
10073 * return $q.reject(rejection);
10078 * // optional method
10079 * 'response': function(response) {
10080 * // do something on success
10084 * // optional method
10085 * 'responseError': function(rejection) {
10086 * // do something on error
10087 * if (canRecover(rejection)) {
10088 * return responseOrNewPromise
10090 * return $q.reject(rejection);
10095 * $httpProvider.interceptors.push('myHttpInterceptor');
10098 * // alternatively, register the interceptor via an anonymous factory
10099 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) {
10101 * 'request': function(config) {
10105 * 'response': function(response) {
10112 * ## Security Considerations
10114 * When designing web applications, consider security threats from:
10116 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
10117 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery)
10119 * Both server and the client must cooperate in order to eliminate these threats. Angular comes
10120 * pre-configured with strategies that address these issues, but for this to work backend server
10121 * cooperation is required.
10123 * ### JSON Vulnerability Protection
10125 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
10126 * allows third party website to turn your JSON resource URL into
10127 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To
10128 * counter this your server can prefix all JSON requests with following string `")]}',\n"`.
10129 * Angular will automatically strip the prefix before processing it as JSON.
10131 * For example if your server needs to return:
10136 * which is vulnerable to attack, your server can return:
10142 * Angular will strip the prefix, before processing the JSON.
10145 * ### Cross Site Request Forgery (XSRF) Protection
10147 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which
10148 * an unauthorized site can gain your user's private data. Angular provides a mechanism
10149 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie
10150 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only
10151 * JavaScript that runs on your domain could read the cookie, your server can be assured that
10152 * the XHR came from JavaScript running on your domain. The header will not be set for
10153 * cross-domain requests.
10155 * To take advantage of this, your server needs to set a token in a JavaScript readable session
10156 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the
10157 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure
10158 * that only JavaScript running on your domain could have sent the request. The token must be
10159 * unique for each user and must be verifiable by the server (to prevent the JavaScript from
10160 * making up its own tokens). We recommend that the token is a digest of your site's
10161 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography))
10162 * for added security.
10164 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName
10165 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time,
10166 * or the per-request config object.
10168 * In order to prevent collisions in environments where multiple Angular apps share the
10169 * same domain or subdomain, we recommend that each application uses unique cookie name.
10171 * @param {object} config Object describing the request to be made and how it should be
10172 * processed. The object has following properties:
10174 * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
10175 * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested.
10176 * - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be serialized
10177 * with the `paramSerializer` and appended as GET parameters.
10178 * - **data** – `{string|Object}` – Data to be sent as the request message data.
10179 * - **headers** – `{Object}` – Map of strings or functions which return strings representing
10180 * HTTP headers to send to the server. If the return value of a function is null, the
10181 * header will not be sent. Functions accept a config object as an argument.
10182 * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token.
10183 * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token.
10184 * - **transformRequest** –
10185 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
10186 * transform function or an array of such functions. The transform function takes the http
10187 * request body and headers and returns its transformed (typically serialized) version.
10188 * See {@link ng.$http#overriding-the-default-transformations-per-request
10189 * Overriding the Default Transformations}
10190 * - **transformResponse** –
10191 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` –
10192 * transform function or an array of such functions. The transform function takes the http
10193 * response body, headers and status and returns its transformed (typically deserialized) version.
10194 * See {@link ng.$http#overriding-the-default-transformations-per-request
10195 * Overriding the Default TransformationjqLiks}
10196 * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to
10197 * prepare the string representation of request parameters (specified as an object).
10198 * If specified as string, it is interpreted as function registered with the
10199 * {@link $injector $injector}, which means you can create your own serializer
10200 * by registering it as a {@link auto.$provide#service service}.
10201 * The default serializer is the {@link $httpParamSerializer $httpParamSerializer};
10202 * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike}
10203 * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
10204 * GET request, otherwise if a cache instance built with
10205 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
10207 * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise}
10208 * that should abort the request when resolved.
10209 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the
10210 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials)
10211 * for more information.
10212 * - **responseType** - `{string}` - see
10213 * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype).
10215 * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object
10216 * when the request succeeds or fails.
10219 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending
10220 * requests. This is primarily meant to be used for debugging purposes.
10224 <example module="httpExample">
10225 <file name="index.html">
10226 <div ng-controller="FetchController">
10227 <select ng-model="method" aria-label="Request method">
10228 <option>GET</option>
10229 <option>JSONP</option>
10231 <input type="text" ng-model="url" size="80" aria-label="URL" />
10232 <button id="fetchbtn" ng-click="fetch()">fetch</button><br>
10233 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
10234 <button id="samplejsonpbtn"
10235 ng-click="updateModel('JSONP',
10236 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')">
10239 <button id="invalidjsonpbtn"
10240 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')">
10243 <pre>http status code: {{status}}</pre>
10244 <pre>http response data: {{data}}</pre>
10247 <file name="script.js">
10248 angular.module('httpExample', [])
10249 .controller('FetchController', ['$scope', '$http', '$templateCache',
10250 function($scope, $http, $templateCache) {
10251 $scope.method = 'GET';
10252 $scope.url = 'http-hello.html';
10254 $scope.fetch = function() {
10255 $scope.code = null;
10256 $scope.response = null;
10258 $http({method: $scope.method, url: $scope.url, cache: $templateCache}).
10259 then(function(response) {
10260 $scope.status = response.status;
10261 $scope.data = response.data;
10262 }, function(response) {
10263 $scope.data = response.data || "Request failed";
10264 $scope.status = response.status;
10268 $scope.updateModel = function(method, url) {
10269 $scope.method = method;
10274 <file name="http-hello.html">
10277 <file name="protractor.js" type="protractor">
10278 var status = element(by.binding('status'));
10279 var data = element(by.binding('data'));
10280 var fetchBtn = element(by.id('fetchbtn'));
10281 var sampleGetBtn = element(by.id('samplegetbtn'));
10282 var sampleJsonpBtn = element(by.id('samplejsonpbtn'));
10283 var invalidJsonpBtn = element(by.id('invalidjsonpbtn'));
10285 it('should make an xhr GET request', function() {
10286 sampleGetBtn.click();
10288 expect(status.getText()).toMatch('200');
10289 expect(data.getText()).toMatch(/Hello, \$http!/);
10292 // Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185
10293 // it('should make a JSONP request to angularjs.org', function() {
10294 // sampleJsonpBtn.click();
10295 // fetchBtn.click();
10296 // expect(status.getText()).toMatch('200');
10297 // expect(data.getText()).toMatch(/Super Hero!/);
10300 it('should make JSONP request to invalid URL and invoke the error handler',
10302 invalidJsonpBtn.click();
10304 expect(status.getText()).toMatch('0');
10305 expect(data.getText()).toMatch('Request failed');
10310 function $http(requestConfig) {
10312 if (!angular.isObject(requestConfig)) {
10313 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig);
10316 var config = extend({
10318 transformRequest: defaults.transformRequest,
10319 transformResponse: defaults.transformResponse,
10320 paramSerializer: defaults.paramSerializer
10323 config.headers = mergeHeaders(requestConfig);
10324 config.method = uppercase(config.method);
10325 config.paramSerializer = isString(config.paramSerializer) ?
10326 $injector.get(config.paramSerializer) : config.paramSerializer;
10328 var serverRequest = function(config) {
10329 var headers = config.headers;
10330 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest);
10332 // strip content-type if data is undefined
10333 if (isUndefined(reqData)) {
10334 forEach(headers, function(value, header) {
10335 if (lowercase(header) === 'content-type') {
10336 delete headers[header];
10341 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
10342 config.withCredentials = defaults.withCredentials;
10346 return sendReq(config, reqData).then(transformResponse, transformResponse);
10349 var chain = [serverRequest, undefined];
10350 var promise = $q.when(config);
10352 // apply interceptors
10353 forEach(reversedInterceptors, function(interceptor) {
10354 if (interceptor.request || interceptor.requestError) {
10355 chain.unshift(interceptor.request, interceptor.requestError);
10357 if (interceptor.response || interceptor.responseError) {
10358 chain.push(interceptor.response, interceptor.responseError);
10362 while (chain.length) {
10363 var thenFn = chain.shift();
10364 var rejectFn = chain.shift();
10366 promise = promise.then(thenFn, rejectFn);
10369 if (useLegacyPromise) {
10370 promise.success = function(fn) {
10371 assertArgFn(fn, 'fn');
10373 promise.then(function(response) {
10374 fn(response.data, response.status, response.headers, config);
10379 promise.error = function(fn) {
10380 assertArgFn(fn, 'fn');
10382 promise.then(null, function(response) {
10383 fn(response.data, response.status, response.headers, config);
10388 promise.success = $httpMinErrLegacyFn('success');
10389 promise.error = $httpMinErrLegacyFn('error');
10394 function transformResponse(response) {
10395 // make a copy since the response must be cacheable
10396 var resp = extend({}, response);
10397 resp.data = transformData(response.data, response.headers, response.status,
10398 config.transformResponse);
10399 return (isSuccess(response.status))
10404 function executeHeaderFns(headers, config) {
10405 var headerContent, processedHeaders = {};
10407 forEach(headers, function(headerFn, header) {
10408 if (isFunction(headerFn)) {
10409 headerContent = headerFn(config);
10410 if (headerContent != null) {
10411 processedHeaders[header] = headerContent;
10414 processedHeaders[header] = headerFn;
10418 return processedHeaders;
10421 function mergeHeaders(config) {
10422 var defHeaders = defaults.headers,
10423 reqHeaders = extend({}, config.headers),
10424 defHeaderName, lowercaseDefHeaderName, reqHeaderName;
10426 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]);
10428 // using for-in instead of forEach to avoid unecessary iteration after header has been found
10429 defaultHeadersIteration:
10430 for (defHeaderName in defHeaders) {
10431 lowercaseDefHeaderName = lowercase(defHeaderName);
10433 for (reqHeaderName in reqHeaders) {
10434 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) {
10435 continue defaultHeadersIteration;
10439 reqHeaders[defHeaderName] = defHeaders[defHeaderName];
10442 // execute if header value is a function for merged headers
10443 return executeHeaderFns(reqHeaders, shallowCopy(config));
10447 $http.pendingRequests = [];
10454 * Shortcut method to perform `GET` request.
10456 * @param {string} url Relative or absolute URL specifying the destination of the request
10457 * @param {Object=} config Optional configuration object
10458 * @returns {HttpPromise} Future object
10463 * @name $http#delete
10466 * Shortcut method to perform `DELETE` request.
10468 * @param {string} url Relative or absolute URL specifying the destination of the request
10469 * @param {Object=} config Optional configuration object
10470 * @returns {HttpPromise} Future object
10478 * Shortcut method to perform `HEAD` request.
10480 * @param {string} url Relative or absolute URL specifying the destination of the request
10481 * @param {Object=} config Optional configuration object
10482 * @returns {HttpPromise} Future object
10487 * @name $http#jsonp
10490 * Shortcut method to perform `JSONP` request.
10492 * @param {string} url Relative or absolute URL specifying the destination of the request.
10493 * The name of the callback should be the string `JSON_CALLBACK`.
10494 * @param {Object=} config Optional configuration object
10495 * @returns {HttpPromise} Future object
10497 createShortMethods('get', 'delete', 'head', 'jsonp');
10504 * Shortcut method to perform `POST` request.
10506 * @param {string} url Relative or absolute URL specifying the destination of the request
10507 * @param {*} data Request content
10508 * @param {Object=} config Optional configuration object
10509 * @returns {HttpPromise} Future object
10517 * Shortcut method to perform `PUT` request.
10519 * @param {string} url Relative or absolute URL specifying the destination of the request
10520 * @param {*} data Request content
10521 * @param {Object=} config Optional configuration object
10522 * @returns {HttpPromise} Future object
10527 * @name $http#patch
10530 * Shortcut method to perform `PATCH` request.
10532 * @param {string} url Relative or absolute URL specifying the destination of the request
10533 * @param {*} data Request content
10534 * @param {Object=} config Optional configuration object
10535 * @returns {HttpPromise} Future object
10537 createShortMethodsWithData('post', 'put', 'patch');
10541 * @name $http#defaults
10544 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
10545 * default headers, withCredentials as well as request and response transformations.
10547 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
10549 $http.defaults = defaults;
10555 function createShortMethods(names) {
10556 forEach(arguments, function(name) {
10557 $http[name] = function(url, config) {
10558 return $http(extend({}, config || {}, {
10567 function createShortMethodsWithData(name) {
10568 forEach(arguments, function(name) {
10569 $http[name] = function(url, data, config) {
10570 return $http(extend({}, config || {}, {
10581 * Makes the request.
10583 * !!! ACCESSES CLOSURE VARS:
10584 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests
10586 function sendReq(config, reqData) {
10587 var deferred = $q.defer(),
10588 promise = deferred.promise,
10591 reqHeaders = config.headers,
10592 url = buildUrl(config.url, config.paramSerializer(config.params));
10594 $http.pendingRequests.push(config);
10595 promise.then(removePendingReq, removePendingReq);
10598 if ((config.cache || defaults.cache) && config.cache !== false &&
10599 (config.method === 'GET' || config.method === 'JSONP')) {
10600 cache = isObject(config.cache) ? config.cache
10601 : isObject(defaults.cache) ? defaults.cache
10606 cachedResp = cache.get(url);
10607 if (isDefined(cachedResp)) {
10608 if (isPromiseLike(cachedResp)) {
10609 // cached request has already been sent, but there is no response yet
10610 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult);
10612 // serving from cache
10613 if (isArray(cachedResp)) {
10614 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]);
10616 resolvePromise(cachedResp, 200, {}, 'OK');
10620 // put the promise for the non-transformed response into cache as a placeholder
10621 cache.put(url, promise);
10626 // if we won't have the response in cache, set the xsrf headers and
10627 // send the request to the backend
10628 if (isUndefined(cachedResp)) {
10629 var xsrfValue = urlIsSameOrigin(config.url)
10630 ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName]
10633 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
10636 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
10637 config.withCredentials, config.responseType);
10644 * Callback registered to $httpBackend():
10645 * - caches the response if desired
10646 * - resolves the raw $http promise
10649 function done(status, response, headersString, statusText) {
10651 if (isSuccess(status)) {
10652 cache.put(url, [status, response, parseHeaders(headersString), statusText]);
10654 // remove promise from the cache
10659 function resolveHttpPromise() {
10660 resolvePromise(response, status, headersString, statusText);
10663 if (useApplyAsync) {
10664 $rootScope.$applyAsync(resolveHttpPromise);
10666 resolveHttpPromise();
10667 if (!$rootScope.$$phase) $rootScope.$apply();
10673 * Resolves the raw $http promise.
10675 function resolvePromise(response, status, headers, statusText) {
10676 //status: HTTP response status code, 0, -1 (aborted by timeout / promise)
10677 status = status >= -1 ? status : 0;
10679 (isSuccess(status) ? deferred.resolve : deferred.reject)({
10682 headers: headersGetter(headers),
10684 statusText: statusText
10688 function resolvePromiseWithResult(result) {
10689 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText);
10692 function removePendingReq() {
10693 var idx = $http.pendingRequests.indexOf(config);
10694 if (idx !== -1) $http.pendingRequests.splice(idx, 1);
10699 function buildUrl(url, serializedParams) {
10700 if (serializedParams.length > 0) {
10701 url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams;
10710 * @name $xhrFactory
10713 * Factory function used to create XMLHttpRequest objects.
10715 * Replace or decorate this service to create your own custom XMLHttpRequest objects.
10718 * angular.module('myApp', [])
10719 * .factory('$xhrFactory', function() {
10720 * return function createXhr(method, url) {
10721 * return new window.XMLHttpRequest({mozSystem: true});
10726 * @param {string} method HTTP method of the request (GET, POST, PUT, ..)
10727 * @param {string} url URL of the request.
10729 function $xhrFactoryProvider() {
10730 this.$get = function() {
10731 return function createXhr() {
10732 return new window.XMLHttpRequest();
10739 * @name $httpBackend
10740 * @requires $window
10741 * @requires $document
10742 * @requires $xhrFactory
10745 * HTTP backend used by the {@link ng.$http service} that delegates to
10746 * XMLHttpRequest object or JSONP and deals with browser incompatibilities.
10748 * You should never need to use this service directly, instead use the higher-level abstractions:
10749 * {@link ng.$http $http} or {@link ngResource.$resource $resource}.
10751 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock
10752 * $httpBackend} which can be trained with responses.
10754 function $HttpBackendProvider() {
10755 this.$get = ['$browser', '$window', '$document', '$xhrFactory', function($browser, $window, $document, $xhrFactory) {
10756 return createHttpBackend($browser, $xhrFactory, $browser.defer, $window.angular.callbacks, $document[0]);
10760 function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
10761 // TODO(vojta): fix the signature
10762 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
10763 $browser.$$incOutstandingRequestCount();
10764 url = url || $browser.url();
10766 if (lowercase(method) == 'jsonp') {
10767 var callbackId = '_' + (callbacks.counter++).toString(36);
10768 callbacks[callbackId] = function(data) {
10769 callbacks[callbackId].data = data;
10770 callbacks[callbackId].called = true;
10773 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId),
10774 callbackId, function(status, text) {
10775 completeRequest(callback, status, callbacks[callbackId].data, "", text);
10776 callbacks[callbackId] = noop;
10780 var xhr = createXhr(method, url);
10782 xhr.open(method, url, true);
10783 forEach(headers, function(value, key) {
10784 if (isDefined(value)) {
10785 xhr.setRequestHeader(key, value);
10789 xhr.onload = function requestLoaded() {
10790 var statusText = xhr.statusText || '';
10792 // responseText is the old-school way of retrieving response (supported by IE9)
10793 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
10794 var response = ('response' in xhr) ? xhr.response : xhr.responseText;
10796 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
10797 var status = xhr.status === 1223 ? 204 : xhr.status;
10799 // fix status code when it is 0 (0 status is undocumented).
10800 // Occurs when accessing file resources or on Android 4.1 stock browser
10801 // while retrieving files from application cache.
10802 if (status === 0) {
10803 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0;
10806 completeRequest(callback,
10809 xhr.getAllResponseHeaders(),
10813 var requestError = function() {
10814 // The response is always empty
10815 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
10816 completeRequest(callback, -1, null, null, '');
10819 xhr.onerror = requestError;
10820 xhr.onabort = requestError;
10822 if (withCredentials) {
10823 xhr.withCredentials = true;
10826 if (responseType) {
10828 xhr.responseType = responseType;
10830 // WebKit added support for the json responseType value on 09/03/2013
10831 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are
10832 // known to throw when setting the value "json" as the response type. Other older
10833 // browsers implementing the responseType
10835 // The json response type can be ignored if not supported, because JSON payloads are
10836 // parsed on the client-side regardless.
10837 if (responseType !== 'json') {
10843 xhr.send(isUndefined(post) ? null : post);
10847 var timeoutId = $browserDefer(timeoutRequest, timeout);
10848 } else if (isPromiseLike(timeout)) {
10849 timeout.then(timeoutRequest);
10853 function timeoutRequest() {
10854 jsonpDone && jsonpDone();
10855 xhr && xhr.abort();
10858 function completeRequest(callback, status, response, headersString, statusText) {
10859 // cancel timeout and subsequent timeout promise resolution
10860 if (isDefined(timeoutId)) {
10861 $browserDefer.cancel(timeoutId);
10863 jsonpDone = xhr = null;
10865 callback(status, response, headersString, statusText);
10866 $browser.$$completeOutstandingRequest(noop);
10870 function jsonpReq(url, callbackId, done) {
10871 // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.:
10872 // - fetches local scripts via XHR and evals them
10873 // - adds and immediately removes script elements from the document
10874 var script = rawDocument.createElement('script'), callback = null;
10875 script.type = "text/javascript";
10877 script.async = true;
10879 callback = function(event) {
10880 removeEventListenerFn(script, "load", callback);
10881 removeEventListenerFn(script, "error", callback);
10882 rawDocument.body.removeChild(script);
10885 var text = "unknown";
10888 if (event.type === "load" && !callbacks[callbackId].called) {
10889 event = { type: "error" };
10892 status = event.type === "error" ? 404 : 200;
10896 done(status, text);
10900 addEventListenerFn(script, "load", callback);
10901 addEventListenerFn(script, "error", callback);
10902 rawDocument.body.appendChild(script);
10907 var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate');
10908 $interpolateMinErr.throwNoconcat = function(text) {
10909 throw $interpolateMinErr('noconcat',
10910 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
10911 "interpolations that concatenate multiple expressions when a trusted value is " +
10912 "required. See http://docs.angularjs.org/api/ng.$sce", text);
10915 $interpolateMinErr.interr = function(text, err) {
10916 return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString());
10921 * @name $interpolateProvider
10925 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
10928 <example module="customInterpolationApp">
10929 <file name="index.html">
10931 var customInterpolationApp = angular.module('customInterpolationApp', []);
10933 customInterpolationApp.config(function($interpolateProvider) {
10934 $interpolateProvider.startSymbol('//');
10935 $interpolateProvider.endSymbol('//');
10939 customInterpolationApp.controller('DemoController', function() {
10940 this.label = "This binding is brought you by // interpolation symbols.";
10943 <div ng-app="App" ng-controller="DemoController as demo">
10947 <file name="protractor.js" type="protractor">
10948 it('should interpolate binding with custom symbols', function() {
10949 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
10954 function $InterpolateProvider() {
10955 var startSymbol = '{{';
10956 var endSymbol = '}}';
10960 * @name $interpolateProvider#startSymbol
10962 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
10964 * @param {string=} value new value to set the starting symbol to.
10965 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
10967 this.startSymbol = function(value) {
10969 startSymbol = value;
10972 return startSymbol;
10978 * @name $interpolateProvider#endSymbol
10980 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
10982 * @param {string=} value new value to set the ending symbol to.
10983 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
10985 this.endSymbol = function(value) {
10995 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
10996 var startSymbolLength = startSymbol.length,
10997 endSymbolLength = endSymbol.length,
10998 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'),
10999 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g');
11001 function escape(ch) {
11002 return '\\\\\\' + ch;
11005 function unescapeText(text) {
11006 return text.replace(escapedStartRegexp, startSymbol).
11007 replace(escapedEndRegexp, endSymbol);
11010 function stringify(value) {
11011 if (value == null) { // null || undefined
11014 switch (typeof value) {
11018 value = '' + value;
11021 value = toJson(value);
11029 * @name $interpolate
11037 * Compiles a string with markup into an interpolation function. This service is used by the
11038 * HTML {@link ng.$compile $compile} service for data binding. See
11039 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
11040 * interpolation markup.
11044 * var $interpolate = ...; // injected
11045 * var exp = $interpolate('Hello {{name | uppercase}}!');
11046 * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!');
11049 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is
11050 * `true`, the interpolation function will return `undefined` unless all embedded expressions
11051 * evaluate to a value other than `undefined`.
11054 * var $interpolate = ...; // injected
11055 * var context = {greeting: 'Hello', name: undefined };
11057 * // default "forgiving" mode
11058 * var exp = $interpolate('{{greeting}} {{name}}!');
11059 * expect(exp(context)).toEqual('Hello !');
11061 * // "allOrNothing" mode
11062 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true);
11063 * expect(exp(context)).toBeUndefined();
11064 * context.name = 'Angular';
11065 * expect(exp(context)).toEqual('Hello Angular!');
11068 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior.
11070 * ####Escaped Interpolation
11071 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers
11072 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash).
11073 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression
11076 * This enables web-servers to prevent script injection attacks and defacing attacks, to some
11077 * degree, while also enabling code examples to work without relying on the
11078 * {@link ng.directive:ngNonBindable ngNonBindable} directive.
11080 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data,
11081 * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all
11082 * interpolation start/end markers with their escaped counterparts.**
11084 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered
11085 * output when the $interpolate service processes the text. So, for HTML elements interpolated
11086 * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter
11087 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such,
11088 * this is typically useful only when user-data is used in rendering a template from the server, or
11089 * when otherwise untrusted data is used by a directive.
11092 * <file name="index.html">
11093 * <div ng-init="username='A user'">
11094 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\}
11096 * <p><strong>{{username}}</strong> attempts to inject code which will deface the
11097 * application, but fails to accomplish their task, because the server has correctly
11098 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash)
11100 * <p>Instead, the result of the attempted script injection is visible, and can be removed
11101 * from the database by an administrator.</p>
11106 * @param {string} text The text with markup to interpolate.
11107 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
11108 * embedded expression in order to return an interpolation function. Strings with no
11109 * embedded expression will return null for the interpolation function.
11110 * @param {string=} trustedContext when provided, the returned function passes the interpolated
11111 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult,
11112 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that
11113 * provides Strict Contextual Escaping for details.
11114 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined
11115 * unless all embedded expressions evaluate to a value other than `undefined`.
11116 * @returns {function(context)} an interpolation function which is used to compute the
11117 * interpolated string. The function has these parameters:
11119 * - `context`: evaluation context for all expressions embedded in the interpolated text
11121 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
11122 allOrNothing = !!allOrNothing;
11128 textLength = text.length,
11131 expressionPositions = [];
11133 while (index < textLength) {
11134 if (((startIndex = text.indexOf(startSymbol, index)) != -1) &&
11135 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) {
11136 if (index !== startIndex) {
11137 concat.push(unescapeText(text.substring(index, startIndex)));
11139 exp = text.substring(startIndex + startSymbolLength, endIndex);
11140 expressions.push(exp);
11141 parseFns.push($parse(exp, parseStringifyInterceptor));
11142 index = endIndex + endSymbolLength;
11143 expressionPositions.push(concat.length);
11146 // we did not find an interpolation, so we have to add the remainder to the separators array
11147 if (index !== textLength) {
11148 concat.push(unescapeText(text.substring(index)));
11154 // Concatenating expressions makes it hard to reason about whether some combination of
11155 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a
11156 // single expression be used for iframe[src], object[src], etc., we ensure that the value
11157 // that's used is assigned or constructed by some JS code somewhere that is more testable or
11158 // make it obvious that you bound the value to some user controlled value. This helps reduce
11159 // the load when auditing for XSS issues.
11160 if (trustedContext && concat.length > 1) {
11161 $interpolateMinErr.throwNoconcat(text);
11164 if (!mustHaveExpression || expressions.length) {
11165 var compute = function(values) {
11166 for (var i = 0, ii = expressions.length; i < ii; i++) {
11167 if (allOrNothing && isUndefined(values[i])) return;
11168 concat[expressionPositions[i]] = values[i];
11170 return concat.join('');
11173 var getValue = function(value) {
11174 return trustedContext ?
11175 $sce.getTrusted(trustedContext, value) :
11176 $sce.valueOf(value);
11179 return extend(function interpolationFn(context) {
11181 var ii = expressions.length;
11182 var values = new Array(ii);
11185 for (; i < ii; i++) {
11186 values[i] = parseFns[i](context);
11189 return compute(values);
11191 $exceptionHandler($interpolateMinErr.interr(text, err));
11195 // all of these properties are undocumented for now
11196 exp: text, //just for compatibility with regular watchers created via $watch
11197 expressions: expressions,
11198 $$watchDelegate: function(scope, listener) {
11200 return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) {
11201 var currValue = compute(values);
11202 if (isFunction(listener)) {
11203 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
11205 lastValue = currValue;
11211 function parseStringifyInterceptor(value) {
11213 value = getValue(value);
11214 return allOrNothing && !isDefined(value) ? value : stringify(value);
11216 $exceptionHandler($interpolateMinErr.interr(text, err));
11224 * @name $interpolate#startSymbol
11226 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
11228 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change
11231 * @returns {string} start symbol.
11233 $interpolate.startSymbol = function() {
11234 return startSymbol;
11240 * @name $interpolate#endSymbol
11242 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
11244 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change
11247 * @returns {string} end symbol.
11249 $interpolate.endSymbol = function() {
11253 return $interpolate;
11257 function $IntervalProvider() {
11258 this.$get = ['$rootScope', '$window', '$q', '$$q',
11259 function($rootScope, $window, $q, $$q) {
11260 var intervals = {};
11268 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay`
11271 * The return value of registering an interval function is a promise. This promise will be
11272 * notified upon each tick of the interval, and will be resolved after `count` iterations, or
11273 * run indefinitely if `count` is not defined. The value of the notification will be the
11274 * number of iterations that have run.
11275 * To cancel an interval, call `$interval.cancel(promise)`.
11277 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to
11278 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that
11281 * <div class="alert alert-warning">
11282 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished
11283 * with them. In particular they are not automatically destroyed when a controller's scope or a
11284 * directive's element are destroyed.
11285 * You should take this into consideration and make sure to always cancel the interval at the
11286 * appropriate moment. See the example below for more details on how and when to do this.
11289 * @param {function()} fn A function that should be called repeatedly.
11290 * @param {number} delay Number of milliseconds between each function call.
11291 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
11293 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
11294 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
11295 * @param {...*=} Pass additional parameters to the executed function.
11296 * @returns {promise} A promise which will be notified on each iteration.
11299 * <example module="intervalExample">
11300 * <file name="index.html">
11302 * angular.module('intervalExample', [])
11303 * .controller('ExampleController', ['$scope', '$interval',
11304 * function($scope, $interval) {
11305 * $scope.format = 'M/d/yy h:mm:ss a';
11306 * $scope.blood_1 = 100;
11307 * $scope.blood_2 = 120;
11310 * $scope.fight = function() {
11311 * // Don't start a new fight if we are already fighting
11312 * if ( angular.isDefined(stop) ) return;
11314 * stop = $interval(function() {
11315 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
11316 * $scope.blood_1 = $scope.blood_1 - 3;
11317 * $scope.blood_2 = $scope.blood_2 - 4;
11319 * $scope.stopFight();
11324 * $scope.stopFight = function() {
11325 * if (angular.isDefined(stop)) {
11326 * $interval.cancel(stop);
11327 * stop = undefined;
11331 * $scope.resetFight = function() {
11332 * $scope.blood_1 = 100;
11333 * $scope.blood_2 = 120;
11336 * $scope.$on('$destroy', function() {
11337 * // Make sure that the interval is destroyed too
11338 * $scope.stopFight();
11341 * // Register the 'myCurrentTime' directive factory method.
11342 * // We inject $interval and dateFilter service since the factory method is DI.
11343 * .directive('myCurrentTime', ['$interval', 'dateFilter',
11344 * function($interval, dateFilter) {
11345 * // return the directive link function. (compile function not needed)
11346 * return function(scope, element, attrs) {
11347 * var format, // date format
11348 * stopTime; // so that we can cancel the time updates
11350 * // used to update the UI
11351 * function updateTime() {
11352 * element.text(dateFilter(new Date(), format));
11355 * // watch the expression, and update the UI on change.
11356 * scope.$watch(attrs.myCurrentTime, function(value) {
11361 * stopTime = $interval(updateTime, 1000);
11363 * // listen on DOM destroy (removal) event, and cancel the next UI update
11364 * // to prevent updating time after the DOM element was removed.
11365 * element.on('$destroy', function() {
11366 * $interval.cancel(stopTime);
11373 * <div ng-controller="ExampleController">
11374 * <label>Date format: <input ng-model="format"></label> <hr/>
11375 * Current time is: <span my-current-time="format"></span>
11377 * Blood 1 : <font color='red'>{{blood_1}}</font>
11378 * Blood 2 : <font color='red'>{{blood_2}}</font>
11379 * <button type="button" data-ng-click="fight()">Fight</button>
11380 * <button type="button" data-ng-click="stopFight()">StopFight</button>
11381 * <button type="button" data-ng-click="resetFight()">resetFight</button>
11388 function interval(fn, delay, count, invokeApply) {
11389 var hasParams = arguments.length > 4,
11390 args = hasParams ? sliceArgs(arguments, 4) : [],
11391 setInterval = $window.setInterval,
11392 clearInterval = $window.clearInterval,
11394 skipApply = (isDefined(invokeApply) && !invokeApply),
11395 deferred = (skipApply ? $$q : $q).defer(),
11396 promise = deferred.promise;
11398 count = isDefined(count) ? count : 0;
11400 promise.then(null, null, (!hasParams) ? fn : function() {
11401 fn.apply(null, args);
11404 promise.$$intervalId = setInterval(function tick() {
11405 deferred.notify(iteration++);
11407 if (count > 0 && iteration >= count) {
11408 deferred.resolve(iteration);
11409 clearInterval(promise.$$intervalId);
11410 delete intervals[promise.$$intervalId];
11413 if (!skipApply) $rootScope.$apply();
11417 intervals[promise.$$intervalId] = deferred;
11425 * @name $interval#cancel
11428 * Cancels a task associated with the `promise`.
11430 * @param {Promise=} promise returned by the `$interval` function.
11431 * @returns {boolean} Returns `true` if the task was successfully canceled.
11433 interval.cancel = function(promise) {
11434 if (promise && promise.$$intervalId in intervals) {
11435 intervals[promise.$$intervalId].reject('canceled');
11436 $window.clearInterval(promise.$$intervalId);
11437 delete intervals[promise.$$intervalId];
11452 * $locale service provides localization rules for various Angular components. As of right now the
11453 * only public api is:
11455 * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
11458 var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/,
11459 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
11460 var $locationMinErr = minErr('$location');
11464 * Encode path using encodeUriSegment, ignoring forward slashes
11466 * @param {string} path Path to encode
11467 * @returns {string}
11469 function encodePath(path) {
11470 var segments = path.split('/'),
11471 i = segments.length;
11474 segments[i] = encodeUriSegment(segments[i]);
11477 return segments.join('/');
11480 function parseAbsoluteUrl(absoluteUrl, locationObj) {
11481 var parsedUrl = urlResolve(absoluteUrl);
11483 locationObj.$$protocol = parsedUrl.protocol;
11484 locationObj.$$host = parsedUrl.hostname;
11485 locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
11489 function parseAppUrl(relativeUrl, locationObj) {
11490 var prefixed = (relativeUrl.charAt(0) !== '/');
11492 relativeUrl = '/' + relativeUrl;
11494 var match = urlResolve(relativeUrl);
11495 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ?
11496 match.pathname.substring(1) : match.pathname);
11497 locationObj.$$search = parseKeyValue(match.search);
11498 locationObj.$$hash = decodeURIComponent(match.hash);
11500 // make sure path starts with '/';
11501 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') {
11502 locationObj.$$path = '/' + locationObj.$$path;
11509 * @param {string} begin
11510 * @param {string} whole
11511 * @returns {string} returns text from whole after begin or undefined if it does not begin with
11514 function beginsWith(begin, whole) {
11515 if (whole.indexOf(begin) === 0) {
11516 return whole.substr(begin.length);
11521 function stripHash(url) {
11522 var index = url.indexOf('#');
11523 return index == -1 ? url : url.substr(0, index);
11526 function trimEmptyHash(url) {
11527 return url.replace(/(#.+)|#$/, '$1');
11531 function stripFile(url) {
11532 return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
11535 /* return the server only (scheme://host:port) */
11536 function serverBase(url) {
11537 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2));
11542 * LocationHtml5Url represents an url
11543 * This object is exposed as $location service when HTML5 mode is enabled and supported
11546 * @param {string} appBase application base URL
11547 * @param {string} appBaseNoFile application base URL stripped of any filename
11548 * @param {string} basePrefix url path prefix
11550 function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
11551 this.$$html5 = true;
11552 basePrefix = basePrefix || '';
11553 parseAbsoluteUrl(appBase, this);
11557 * Parse given html5 (regular) url string into properties
11558 * @param {string} url HTML5 url
11561 this.$$parse = function(url) {
11562 var pathUrl = beginsWith(appBaseNoFile, url);
11563 if (!isString(pathUrl)) {
11564 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url,
11568 parseAppUrl(pathUrl, this);
11570 if (!this.$$path) {
11578 * Compose url and update `absUrl` property
11581 this.$$compose = function() {
11582 var search = toKeyValue(this.$$search),
11583 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11585 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11586 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/'
11589 this.$$parseLinkUrl = function(url, relHref) {
11590 if (relHref && relHref[0] === '#') {
11591 // special case for links to hash fragments:
11592 // keep the old url and only replace the hash fragment
11593 this.hash(relHref.slice(1));
11596 var appUrl, prevAppUrl;
11599 if (isDefined(appUrl = beginsWith(appBase, url))) {
11600 prevAppUrl = appUrl;
11601 if (isDefined(appUrl = beginsWith(basePrefix, appUrl))) {
11602 rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
11604 rewrittenUrl = appBase + prevAppUrl;
11606 } else if (isDefined(appUrl = beginsWith(appBaseNoFile, url))) {
11607 rewrittenUrl = appBaseNoFile + appUrl;
11608 } else if (appBaseNoFile == url + '/') {
11609 rewrittenUrl = appBaseNoFile;
11611 if (rewrittenUrl) {
11612 this.$$parse(rewrittenUrl);
11614 return !!rewrittenUrl;
11620 * LocationHashbangUrl represents url
11621 * This object is exposed as $location service when developer doesn't opt into html5 mode.
11622 * It also serves as the base class for html5 mode fallback on legacy browsers.
11625 * @param {string} appBase application base URL
11626 * @param {string} appBaseNoFile application base URL stripped of any filename
11627 * @param {string} hashPrefix hashbang prefix
11629 function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
11631 parseAbsoluteUrl(appBase, this);
11635 * Parse given hashbang url into properties
11636 * @param {string} url Hashbang url
11639 this.$$parse = function(url) {
11640 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
11641 var withoutHashUrl;
11643 if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') {
11645 // The rest of the url starts with a hash so we have
11646 // got either a hashbang path or a plain hash fragment
11647 withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl);
11648 if (isUndefined(withoutHashUrl)) {
11649 // There was no hashbang prefix so we just have a hash fragment
11650 withoutHashUrl = withoutBaseUrl;
11654 // There was no hashbang path nor hash fragment:
11655 // If we are in HTML5 mode we use what is left as the path;
11656 // Otherwise we ignore what is left
11657 if (this.$$html5) {
11658 withoutHashUrl = withoutBaseUrl;
11660 withoutHashUrl = '';
11661 if (isUndefined(withoutBaseUrl)) {
11668 parseAppUrl(withoutHashUrl, this);
11670 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
11675 * In Windows, on an anchor node on documents loaded from
11676 * the filesystem, the browser will return a pathname
11677 * prefixed with the drive name ('/C:/path') when a
11678 * pathname without a drive is set:
11679 * * a.setAttribute('href', '/foo')
11680 * * a.pathname === '/C:/foo' //true
11682 * Inside of Angular, we're always using pathnames that
11683 * do not include drive names for routing.
11685 function removeWindowsDriveName(path, url, base) {
11687 Matches paths for file protocol on windows,
11688 such as /C:/foo/bar, and captures only /foo/bar.
11690 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/;
11692 var firstPathSegmentMatch;
11694 //Get the relative path from the input URL.
11695 if (url.indexOf(base) === 0) {
11696 url = url.replace(base, '');
11699 // The input URL intentionally contains a first path segment that ends with a colon.
11700 if (windowsFilePathExp.exec(url)) {
11704 firstPathSegmentMatch = windowsFilePathExp.exec(path);
11705 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
11710 * Compose hashbang url and update `absUrl` property
11713 this.$$compose = function() {
11714 var search = toKeyValue(this.$$search),
11715 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11717 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11718 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : '');
11721 this.$$parseLinkUrl = function(url, relHref) {
11722 if (stripHash(appBase) == stripHash(url)) {
11732 * LocationHashbangUrl represents url
11733 * This object is exposed as $location service when html5 history api is enabled but the browser
11734 * does not support it.
11737 * @param {string} appBase application base URL
11738 * @param {string} appBaseNoFile application base URL stripped of any filename
11739 * @param {string} hashPrefix hashbang prefix
11741 function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
11742 this.$$html5 = true;
11743 LocationHashbangUrl.apply(this, arguments);
11745 this.$$parseLinkUrl = function(url, relHref) {
11746 if (relHref && relHref[0] === '#') {
11747 // special case for links to hash fragments:
11748 // keep the old url and only replace the hash fragment
11749 this.hash(relHref.slice(1));
11756 if (appBase == stripHash(url)) {
11757 rewrittenUrl = url;
11758 } else if ((appUrl = beginsWith(appBaseNoFile, url))) {
11759 rewrittenUrl = appBase + hashPrefix + appUrl;
11760 } else if (appBaseNoFile === url + '/') {
11761 rewrittenUrl = appBaseNoFile;
11763 if (rewrittenUrl) {
11764 this.$$parse(rewrittenUrl);
11766 return !!rewrittenUrl;
11769 this.$$compose = function() {
11770 var search = toKeyValue(this.$$search),
11771 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11773 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11774 // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#'
11775 this.$$absUrl = appBase + hashPrefix + this.$$url;
11781 var locationPrototype = {
11784 * Are we in html5 mode?
11790 * Has any change been replacing?
11797 * @name $location#absUrl
11800 * This method is getter only.
11802 * Return full url representation with all segments encoded according to rules specified in
11803 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
11807 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11808 * var absUrl = $location.absUrl();
11809 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
11812 * @return {string} full url
11814 absUrl: locationGetter('$$absUrl'),
11818 * @name $location#url
11821 * This method is getter / setter.
11823 * Return url (e.g. `/path?a=b#hash`) when called without any parameter.
11825 * Change path, search and hash, when called with parameter and return `$location`.
11829 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11830 * var url = $location.url();
11831 * // => "/some/path?foo=bar&baz=xoxo"
11834 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
11835 * @return {string} url
11837 url: function(url) {
11838 if (isUndefined(url)) {
11842 var match = PATH_MATCH.exec(url);
11843 if (match[1] || url === '') this.path(decodeURIComponent(match[1]));
11844 if (match[2] || match[1] || url === '') this.search(match[3] || '');
11845 this.hash(match[5] || '');
11852 * @name $location#protocol
11855 * This method is getter only.
11857 * Return protocol of current url.
11861 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11862 * var protocol = $location.protocol();
11866 * @return {string} protocol of current url
11868 protocol: locationGetter('$$protocol'),
11872 * @name $location#host
11875 * This method is getter only.
11877 * Return host of current url.
11879 * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.
11883 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11884 * var host = $location.host();
11885 * // => "example.com"
11887 * // given url http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
11888 * host = $location.host();
11889 * // => "example.com"
11890 * host = location.host;
11891 * // => "example.com:8080"
11894 * @return {string} host of current url.
11896 host: locationGetter('$$host'),
11900 * @name $location#port
11903 * This method is getter only.
11905 * Return port of current url.
11909 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11910 * var port = $location.port();
11914 * @return {Number} port
11916 port: locationGetter('$$port'),
11920 * @name $location#path
11923 * This method is getter / setter.
11925 * Return path of current url when called without any parameter.
11927 * Change path when called with parameter and return `$location`.
11929 * Note: Path should always begin with forward slash (/), this method will add the forward slash
11930 * if it is missing.
11934 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11935 * var path = $location.path();
11936 * // => "/some/path"
11939 * @param {(string|number)=} path New path
11940 * @return {string} path
11942 path: locationGetterSetter('$$path', function(path) {
11943 path = path !== null ? path.toString() : '';
11944 return path.charAt(0) == '/' ? path : '/' + path;
11949 * @name $location#search
11952 * This method is getter / setter.
11954 * Return search part (as object) of current url when called without any parameter.
11956 * Change search part when called with parameter and return `$location`.
11960 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11961 * var searchObject = $location.search();
11962 * // => {foo: 'bar', baz: 'xoxo'}
11964 * // set foo to 'yipee'
11965 * $location.search('foo', 'yipee');
11966 * // $location.search() => {foo: 'yipee', baz: 'xoxo'}
11969 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or
11972 * When called with a single argument the method acts as a setter, setting the `search` component
11973 * of `$location` to the specified value.
11975 * If the argument is a hash object containing an array of values, these values will be encoded
11976 * as duplicate search parameters in the url.
11978 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue`
11979 * will override only a single search property.
11981 * If `paramValue` is an array, it will override the property of the `search` component of
11982 * `$location` specified via the first argument.
11984 * If `paramValue` is `null`, the property specified via the first argument will be deleted.
11986 * If `paramValue` is `true`, the property specified via the first argument will be added with no
11987 * value nor trailing equal sign.
11989 * @return {Object} If called with no arguments returns the parsed `search` object. If called with
11990 * one or more arguments returns `$location` object itself.
11992 search: function(search, paramValue) {
11993 switch (arguments.length) {
11995 return this.$$search;
11997 if (isString(search) || isNumber(search)) {
11998 search = search.toString();
11999 this.$$search = parseKeyValue(search);
12000 } else if (isObject(search)) {
12001 search = copy(search, {});
12002 // remove object undefined or null properties
12003 forEach(search, function(value, key) {
12004 if (value == null) delete search[key];
12007 this.$$search = search;
12009 throw $locationMinErr('isrcharg',
12010 'The first argument of the `$location#search()` call must be a string or an object.');
12014 if (isUndefined(paramValue) || paramValue === null) {
12015 delete this.$$search[search];
12017 this.$$search[search] = paramValue;
12027 * @name $location#hash
12030 * This method is getter / setter.
12032 * Returns the hash fragment when called without any parameters.
12034 * Changes the hash fragment when called with a parameter and returns `$location`.
12038 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
12039 * var hash = $location.hash();
12040 * // => "hashValue"
12043 * @param {(string|number)=} hash New hash fragment
12044 * @return {string} hash
12046 hash: locationGetterSetter('$$hash', function(hash) {
12047 return hash !== null ? hash.toString() : '';
12052 * @name $location#replace
12055 * If called, all changes to $location during the current `$digest` will replace the current history
12056 * record, instead of adding a new one.
12058 replace: function() {
12059 this.$$replace = true;
12064 forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) {
12065 Location.prototype = Object.create(locationPrototype);
12069 * @name $location#state
12072 * This method is getter / setter.
12074 * Return the history state object when called without any parameter.
12076 * Change the history state object when called with one parameter and return `$location`.
12077 * The state object is later passed to `pushState` or `replaceState`.
12079 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting
12080 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
12081 * older browsers (like IE9 or Android < 4.0), don't use this method.
12083 * @param {object=} state State object for pushState or replaceState
12084 * @return {object} state
12086 Location.prototype.state = function(state) {
12087 if (!arguments.length) {
12088 return this.$$state;
12091 if (Location !== LocationHtml5Url || !this.$$html5) {
12092 throw $locationMinErr('nostate', 'History API state support is available only ' +
12093 'in HTML5 mode and only in browsers supporting HTML5 History API');
12095 // The user might modify `stateObject` after invoking `$location.state(stateObject)`
12096 // but we're changing the $$state reference to $browser.state() during the $digest
12097 // so the modification window is narrow.
12098 this.$$state = isUndefined(state) ? null : state;
12105 function locationGetter(property) {
12106 return function() {
12107 return this[property];
12112 function locationGetterSetter(property, preprocess) {
12113 return function(value) {
12114 if (isUndefined(value)) {
12115 return this[property];
12118 this[property] = preprocess(value);
12130 * @requires $rootElement
12133 * The $location service parses the URL in the browser address bar (based on the
12134 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL
12135 * available to your application. Changes to the URL in the address bar are reflected into
12136 * $location service and changes to $location are reflected into the browser address bar.
12138 * **The $location service:**
12140 * - Exposes the current URL in the browser address bar, so you can
12141 * - Watch and observe the URL.
12142 * - Change the URL.
12143 * - Synchronizes the URL with the browser when the user
12144 * - Changes the address bar.
12145 * - Clicks the back or forward button (or clicks a History link).
12146 * - Clicks on a link.
12147 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
12149 * For more information see {@link guide/$location Developer Guide: Using $location}
12154 * @name $locationProvider
12156 * Use the `$locationProvider` to configure how the application deep linking paths are stored.
12158 function $LocationProvider() {
12159 var hashPrefix = '',
12168 * @name $locationProvider#hashPrefix
12170 * @param {string=} prefix Prefix for hash part (containing path and search)
12171 * @returns {*} current value if used as getter or itself (chaining) if used as setter
12173 this.hashPrefix = function(prefix) {
12174 if (isDefined(prefix)) {
12175 hashPrefix = prefix;
12184 * @name $locationProvider#html5Mode
12186 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value.
12187 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported
12189 * - **enabled** – `{boolean}` – (default: false) If true, will rely on `history.pushState` to
12190 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not
12191 * support `pushState`.
12192 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies
12193 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are
12194 * true, and a base tag is not present, an error will be thrown when `$location` is injected.
12195 * See the {@link guide/$location $location guide for more information}
12196 * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled,
12197 * enables/disables url rewriting for relative links.
12199 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
12201 this.html5Mode = function(mode) {
12202 if (isBoolean(mode)) {
12203 html5Mode.enabled = mode;
12205 } else if (isObject(mode)) {
12207 if (isBoolean(mode.enabled)) {
12208 html5Mode.enabled = mode.enabled;
12211 if (isBoolean(mode.requireBase)) {
12212 html5Mode.requireBase = mode.requireBase;
12215 if (isBoolean(mode.rewriteLinks)) {
12216 html5Mode.rewriteLinks = mode.rewriteLinks;
12227 * @name $location#$locationChangeStart
12228 * @eventType broadcast on root scope
12230 * Broadcasted before a URL will change.
12232 * This change can be prevented by calling
12233 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
12234 * details about event object. Upon successful change
12235 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired.
12237 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
12238 * the browser supports the HTML5 History API.
12240 * @param {Object} angularEvent Synthetic event object.
12241 * @param {string} newUrl New URL
12242 * @param {string=} oldUrl URL that was before it was changed.
12243 * @param {string=} newState New history state object
12244 * @param {string=} oldState History state object that was before it was changed.
12249 * @name $location#$locationChangeSuccess
12250 * @eventType broadcast on root scope
12252 * Broadcasted after a URL was changed.
12254 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
12255 * the browser supports the HTML5 History API.
12257 * @param {Object} angularEvent Synthetic event object.
12258 * @param {string} newUrl New URL
12259 * @param {string=} oldUrl URL that was before it was changed.
12260 * @param {string=} newState New history state object
12261 * @param {string=} oldState History state object that was before it was changed.
12264 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window',
12265 function($rootScope, $browser, $sniffer, $rootElement, $window) {
12268 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to ''
12269 initialUrl = $browser.url(),
12272 if (html5Mode.enabled) {
12273 if (!baseHref && html5Mode.requireBase) {
12274 throw $locationMinErr('nobase',
12275 "$location in HTML5 mode requires a <base> tag to be present!");
12277 appBase = serverBase(initialUrl) + (baseHref || '/');
12278 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url;
12280 appBase = stripHash(initialUrl);
12281 LocationMode = LocationHashbangUrl;
12283 var appBaseNoFile = stripFile(appBase);
12285 $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix);
12286 $location.$$parseLinkUrl(initialUrl, initialUrl);
12288 $location.$$state = $browser.state();
12290 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
12292 function setBrowserUrlWithFallback(url, replace, state) {
12293 var oldUrl = $location.url();
12294 var oldState = $location.$$state;
12296 $browser.url(url, replace, state);
12298 // Make sure $location.state() returns referentially identical (not just deeply equal)
12299 // state object; this makes possible quick checking if the state changed in the digest
12300 // loop. Checking deep equality would be too expensive.
12301 $location.$$state = $browser.state();
12303 // Restore old values if pushState fails
12304 $location.url(oldUrl);
12305 $location.$$state = oldState;
12311 $rootElement.on('click', function(event) {
12312 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
12313 // currently we open nice url link and redirect then
12315 if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return;
12317 var elm = jqLite(event.target);
12319 // traverse the DOM up to find first A tag
12320 while (nodeName_(elm[0]) !== 'a') {
12321 // ignore rewriting if no A tag (reached root element, or no parent - removed from document)
12322 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return;
12325 var absHref = elm.prop('href');
12326 // get the actual href attribute - see
12327 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
12328 var relHref = elm.attr('href') || elm.attr('xlink:href');
12330 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') {
12331 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during
12333 absHref = urlResolve(absHref.animVal).href;
12336 // Ignore when url is started with javascript: or mailto:
12337 if (IGNORE_URI_REGEXP.test(absHref)) return;
12339 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) {
12340 if ($location.$$parseLinkUrl(absHref, relHref)) {
12341 // We do a preventDefault for all urls that are part of the angular application,
12342 // in html5mode and also without, so that we are able to abort navigation without
12343 // getting double entries in the location history.
12344 event.preventDefault();
12345 // update location manually
12346 if ($location.absUrl() != $browser.url()) {
12347 $rootScope.$apply();
12348 // hack to work around FF6 bug 684208 when scenario runner clicks on links
12349 $window.angular['ff-684208-preventDefault'] = true;
12356 // rewrite hashbang url <> html5 url
12357 if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) {
12358 $browser.url($location.absUrl(), true);
12361 var initializing = true;
12363 // update $location when $browser url changes
12364 $browser.onUrlChange(function(newUrl, newState) {
12366 if (isUndefined(beginsWith(appBaseNoFile, newUrl))) {
12367 // If we are navigating outside of the app then force a reload
12368 $window.location.href = newUrl;
12372 $rootScope.$evalAsync(function() {
12373 var oldUrl = $location.absUrl();
12374 var oldState = $location.$$state;
12375 var defaultPrevented;
12376 newUrl = trimEmptyHash(newUrl);
12377 $location.$$parse(newUrl);
12378 $location.$$state = newState;
12380 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
12381 newState, oldState).defaultPrevented;
12383 // if the location was changed by a `$locationChangeStart` handler then stop
12384 // processing this location change
12385 if ($location.absUrl() !== newUrl) return;
12387 if (defaultPrevented) {
12388 $location.$$parse(oldUrl);
12389 $location.$$state = oldState;
12390 setBrowserUrlWithFallback(oldUrl, false, oldState);
12392 initializing = false;
12393 afterLocationChange(oldUrl, oldState);
12396 if (!$rootScope.$$phase) $rootScope.$digest();
12400 $rootScope.$watch(function $locationWatch() {
12401 var oldUrl = trimEmptyHash($browser.url());
12402 var newUrl = trimEmptyHash($location.absUrl());
12403 var oldState = $browser.state();
12404 var currentReplace = $location.$$replace;
12405 var urlOrStateChanged = oldUrl !== newUrl ||
12406 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
12408 if (initializing || urlOrStateChanged) {
12409 initializing = false;
12411 $rootScope.$evalAsync(function() {
12412 var newUrl = $location.absUrl();
12413 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
12414 $location.$$state, oldState).defaultPrevented;
12416 // if the location was changed by a `$locationChangeStart` handler then stop
12417 // processing this location change
12418 if ($location.absUrl() !== newUrl) return;
12420 if (defaultPrevented) {
12421 $location.$$parse(oldUrl);
12422 $location.$$state = oldState;
12424 if (urlOrStateChanged) {
12425 setBrowserUrlWithFallback(newUrl, currentReplace,
12426 oldState === $location.$$state ? null : $location.$$state);
12428 afterLocationChange(oldUrl, oldState);
12433 $location.$$replace = false;
12435 // we don't need to return anything because $evalAsync will make the digest loop dirty when
12436 // there is a change
12441 function afterLocationChange(oldUrl, oldState) {
12442 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl,
12443 $location.$$state, oldState);
12451 * @requires $window
12454 * Simple service for logging. Default implementation safely writes the message
12455 * into the browser's console (if present).
12457 * The main purpose of this service is to simplify debugging and troubleshooting.
12459 * The default is to log `debug` messages. You can use
12460 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this.
12463 <example module="logExample">
12464 <file name="script.js">
12465 angular.module('logExample', [])
12466 .controller('LogController', ['$scope', '$log', function($scope, $log) {
12467 $scope.$log = $log;
12468 $scope.message = 'Hello World!';
12471 <file name="index.html">
12472 <div ng-controller="LogController">
12473 <p>Reload this page with open console, enter text and hit the log button...</p>
12475 <input type="text" ng-model="message" /></label>
12476 <button ng-click="$log.log(message)">log</button>
12477 <button ng-click="$log.warn(message)">warn</button>
12478 <button ng-click="$log.info(message)">info</button>
12479 <button ng-click="$log.error(message)">error</button>
12480 <button ng-click="$log.debug(message)">debug</button>
12488 * @name $logProvider
12490 * Use the `$logProvider` to configure how the application logs messages
12492 function $LogProvider() {
12498 * @name $logProvider#debugEnabled
12500 * @param {boolean=} flag enable or disable debug level messages
12501 * @returns {*} current value if used as getter or itself (chaining) if used as setter
12503 this.debugEnabled = function(flag) {
12504 if (isDefined(flag)) {
12512 this.$get = ['$window', function($window) {
12519 * Write a log message
12521 log: consoleLog('log'),
12528 * Write an information message
12530 info: consoleLog('info'),
12537 * Write a warning message
12539 warn: consoleLog('warn'),
12546 * Write an error message
12548 error: consoleLog('error'),
12555 * Write a debug message
12557 debug: (function() {
12558 var fn = consoleLog('debug');
12560 return function() {
12562 fn.apply(self, arguments);
12568 function formatError(arg) {
12569 if (arg instanceof Error) {
12571 arg = (arg.message && arg.stack.indexOf(arg.message) === -1)
12572 ? 'Error: ' + arg.message + '\n' + arg.stack
12574 } else if (arg.sourceURL) {
12575 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
12581 function consoleLog(type) {
12582 var console = $window.console || {},
12583 logFn = console[type] || console.log || noop,
12586 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode.
12587 // The reason behind this is that console.log has type "object" in IE8...
12589 hasApply = !!logFn.apply;
12593 return function() {
12595 forEach(arguments, function(arg) {
12596 args.push(formatError(arg));
12598 return logFn.apply(console, args);
12602 // we are IE which either doesn't have window.console => this is noop and we do nothing,
12603 // or we are IE where console.log doesn't have apply so we log at least first 2 args
12604 return function(arg1, arg2) {
12605 logFn(arg1, arg2 == null ? '' : arg2);
12611 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
12612 * Any commits to this file should be reviewed with security in mind. *
12613 * Changes to this file can potentially create security vulnerabilities. *
12614 * An approval from 2 Core members with history of modifying *
12615 * this file is required. *
12617 * Does the change somehow allow for arbitrary javascript to be executed? *
12618 * Or allows for someone to change the prototype of built-in objects? *
12619 * Or gives undesired access to variables likes document or window? *
12620 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
12622 var $parseMinErr = minErr('$parse');
12624 // Sandboxing Angular Expressions
12625 // ------------------------------
12626 // Angular expressions are generally considered safe because these expressions only have direct
12627 // access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by
12628 // obtaining a reference to native JS functions such as the Function constructor.
12630 // As an example, consider the following Angular expression:
12632 // {}.toString.constructor('alert("evil JS code")')
12634 // This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits
12635 // against the expression language, but not to prevent exploits that were enabled by exposing
12636 // sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good
12637 // practice and therefore we are not even trying to protect against interaction with an object
12638 // explicitly exposed in this way.
12640 // In general, it is not possible to access a Window object from an angular expression unless a
12641 // window or some DOM object that has a reference to window is published onto a Scope.
12642 // Similarly we prevent invocations of function known to be dangerous, as well as assignments to
12645 // See https://docs.angularjs.org/guide/security
12648 function ensureSafeMemberName(name, fullExpression) {
12649 if (name === "__defineGetter__" || name === "__defineSetter__"
12650 || name === "__lookupGetter__" || name === "__lookupSetter__"
12651 || name === "__proto__") {
12652 throw $parseMinErr('isecfld',
12653 'Attempting to access a disallowed field in Angular expressions! '
12654 + 'Expression: {0}', fullExpression);
12659 function getStringValue(name, fullExpression) {
12660 // From the JavaScript docs:
12661 // Property names must be strings. This means that non-string objects cannot be used
12662 // as keys in an object. Any non-string object, including a number, is typecasted
12663 // into a string via the toString method.
12665 // So, to ensure that we are checking the same `name` that JavaScript would use,
12666 // we cast it to a string, if possible.
12667 // Doing `name + ''` can cause a repl error if the result to `toString` is not a string,
12668 // this is, this will handle objects that misbehave.
12670 if (!isString(name)) {
12671 throw $parseMinErr('iseccst',
12672 'Cannot convert object to primitive value! '
12673 + 'Expression: {0}', fullExpression);
12678 function ensureSafeObject(obj, fullExpression) {
12679 // nifty check if obj is Function that is fast and works across iframes and other contexts
12681 if (obj.constructor === obj) {
12682 throw $parseMinErr('isecfn',
12683 'Referencing Function in Angular expressions is disallowed! Expression: {0}',
12685 } else if (// isWindow(obj)
12686 obj.window === obj) {
12687 throw $parseMinErr('isecwindow',
12688 'Referencing the Window in Angular expressions is disallowed! Expression: {0}',
12690 } else if (// isElement(obj)
12691 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) {
12692 throw $parseMinErr('isecdom',
12693 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}',
12695 } else if (// block Object so that we can't get hold of dangerous Object.* methods
12697 throw $parseMinErr('isecobj',
12698 'Referencing Object in Angular expressions is disallowed! Expression: {0}',
12705 var CALL = Function.prototype.call;
12706 var APPLY = Function.prototype.apply;
12707 var BIND = Function.prototype.bind;
12709 function ensureSafeFunction(obj, fullExpression) {
12711 if (obj.constructor === obj) {
12712 throw $parseMinErr('isecfn',
12713 'Referencing Function in Angular expressions is disallowed! Expression: {0}',
12715 } else if (obj === CALL || obj === APPLY || obj === BIND) {
12716 throw $parseMinErr('isecff',
12717 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}',
12723 function ensureSafeAssignContext(obj, fullExpression) {
12725 if (obj === (0).constructor || obj === (false).constructor || obj === ''.constructor ||
12726 obj === {}.constructor || obj === [].constructor || obj === Function.constructor) {
12727 throw $parseMinErr('isecaf',
12728 'Assigning to a constructor is disallowed! Expression: {0}', fullExpression);
12733 var OPERATORS = createMap();
12734 forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; });
12735 var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
12738 /////////////////////////////////////////
12744 var Lexer = function(options) {
12745 this.options = options;
12748 Lexer.prototype = {
12749 constructor: Lexer,
12751 lex: function(text) {
12756 while (this.index < this.text.length) {
12757 var ch = this.text.charAt(this.index);
12758 if (ch === '"' || ch === "'") {
12759 this.readString(ch);
12760 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {
12762 } else if (this.isIdent(ch)) {
12764 } else if (this.is(ch, '(){}[].,;:?')) {
12765 this.tokens.push({index: this.index, text: ch});
12767 } else if (this.isWhitespace(ch)) {
12770 var ch2 = ch + this.peek();
12771 var ch3 = ch2 + this.peek(2);
12772 var op1 = OPERATORS[ch];
12773 var op2 = OPERATORS[ch2];
12774 var op3 = OPERATORS[ch3];
12775 if (op1 || op2 || op3) {
12776 var token = op3 ? ch3 : (op2 ? ch2 : ch);
12777 this.tokens.push({index: this.index, text: token, operator: true});
12778 this.index += token.length;
12780 this.throwError('Unexpected next character ', this.index, this.index + 1);
12784 return this.tokens;
12787 is: function(ch, chars) {
12788 return chars.indexOf(ch) !== -1;
12791 peek: function(i) {
12793 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false;
12796 isNumber: function(ch) {
12797 return ('0' <= ch && ch <= '9') && typeof ch === "string";
12800 isWhitespace: function(ch) {
12801 // IE treats non-breaking space as \u00A0
12802 return (ch === ' ' || ch === '\r' || ch === '\t' ||
12803 ch === '\n' || ch === '\v' || ch === '\u00A0');
12806 isIdent: function(ch) {
12807 return ('a' <= ch && ch <= 'z' ||
12808 'A' <= ch && ch <= 'Z' ||
12809 '_' === ch || ch === '$');
12812 isExpOperator: function(ch) {
12813 return (ch === '-' || ch === '+' || this.isNumber(ch));
12816 throwError: function(error, start, end) {
12817 end = end || this.index;
12818 var colStr = (isDefined(start)
12819 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']'
12821 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].',
12822 error, colStr, this.text);
12825 readNumber: function() {
12827 var start = this.index;
12828 while (this.index < this.text.length) {
12829 var ch = lowercase(this.text.charAt(this.index));
12830 if (ch == '.' || this.isNumber(ch)) {
12833 var peekCh = this.peek();
12834 if (ch == 'e' && this.isExpOperator(peekCh)) {
12836 } else if (this.isExpOperator(ch) &&
12837 peekCh && this.isNumber(peekCh) &&
12838 number.charAt(number.length - 1) == 'e') {
12840 } else if (this.isExpOperator(ch) &&
12841 (!peekCh || !this.isNumber(peekCh)) &&
12842 number.charAt(number.length - 1) == 'e') {
12843 this.throwError('Invalid exponent');
12854 value: Number(number)
12858 readIdent: function() {
12859 var start = this.index;
12860 while (this.index < this.text.length) {
12861 var ch = this.text.charAt(this.index);
12862 if (!(this.isIdent(ch) || this.isNumber(ch))) {
12869 text: this.text.slice(start, this.index),
12874 readString: function(quote) {
12875 var start = this.index;
12878 var rawString = quote;
12879 var escape = false;
12880 while (this.index < this.text.length) {
12881 var ch = this.text.charAt(this.index);
12885 var hex = this.text.substring(this.index + 1, this.index + 5);
12886 if (!hex.match(/[\da-f]{4}/i)) {
12887 this.throwError('Invalid unicode escape [\\u' + hex + ']');
12890 string += String.fromCharCode(parseInt(hex, 16));
12892 var rep = ESCAPE[ch];
12893 string = string + (rep || ch);
12896 } else if (ch === '\\') {
12898 } else if (ch === quote) {
12912 this.throwError('Unterminated quote', start);
12916 var AST = function(lexer, options) {
12917 this.lexer = lexer;
12918 this.options = options;
12921 AST.Program = 'Program';
12922 AST.ExpressionStatement = 'ExpressionStatement';
12923 AST.AssignmentExpression = 'AssignmentExpression';
12924 AST.ConditionalExpression = 'ConditionalExpression';
12925 AST.LogicalExpression = 'LogicalExpression';
12926 AST.BinaryExpression = 'BinaryExpression';
12927 AST.UnaryExpression = 'UnaryExpression';
12928 AST.CallExpression = 'CallExpression';
12929 AST.MemberExpression = 'MemberExpression';
12930 AST.Identifier = 'Identifier';
12931 AST.Literal = 'Literal';
12932 AST.ArrayExpression = 'ArrayExpression';
12933 AST.Property = 'Property';
12934 AST.ObjectExpression = 'ObjectExpression';
12935 AST.ThisExpression = 'ThisExpression';
12937 // Internal use only
12938 AST.NGValueParameter = 'NGValueParameter';
12941 ast: function(text) {
12943 this.tokens = this.lexer.lex(text);
12945 var value = this.program();
12947 if (this.tokens.length !== 0) {
12948 this.throwError('is an unexpected token', this.tokens[0]);
12954 program: function() {
12957 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']'))
12958 body.push(this.expressionStatement());
12959 if (!this.expect(';')) {
12960 return { type: AST.Program, body: body};
12965 expressionStatement: function() {
12966 return { type: AST.ExpressionStatement, expression: this.filterChain() };
12969 filterChain: function() {
12970 var left = this.expression();
12972 while ((token = this.expect('|'))) {
12973 left = this.filter(left);
12978 expression: function() {
12979 return this.assignment();
12982 assignment: function() {
12983 var result = this.ternary();
12984 if (this.expect('=')) {
12985 result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='};
12990 ternary: function() {
12991 var test = this.logicalOR();
12994 if (this.expect('?')) {
12995 alternate = this.expression();
12996 if (this.consume(':')) {
12997 consequent = this.expression();
12998 return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent};
13004 logicalOR: function() {
13005 var left = this.logicalAND();
13006 while (this.expect('||')) {
13007 left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() };
13012 logicalAND: function() {
13013 var left = this.equality();
13014 while (this.expect('&&')) {
13015 left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()};
13020 equality: function() {
13021 var left = this.relational();
13023 while ((token = this.expect('==','!=','===','!=='))) {
13024 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() };
13029 relational: function() {
13030 var left = this.additive();
13032 while ((token = this.expect('<', '>', '<=', '>='))) {
13033 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() };
13038 additive: function() {
13039 var left = this.multiplicative();
13041 while ((token = this.expect('+','-'))) {
13042 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() };
13047 multiplicative: function() {
13048 var left = this.unary();
13050 while ((token = this.expect('*','/','%'))) {
13051 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() };
13056 unary: function() {
13058 if ((token = this.expect('+', '-', '!'))) {
13059 return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() };
13061 return this.primary();
13065 primary: function() {
13067 if (this.expect('(')) {
13068 primary = this.filterChain();
13070 } else if (this.expect('[')) {
13071 primary = this.arrayDeclaration();
13072 } else if (this.expect('{')) {
13073 primary = this.object();
13074 } else if (this.constants.hasOwnProperty(this.peek().text)) {
13075 primary = copy(this.constants[this.consume().text]);
13076 } else if (this.peek().identifier) {
13077 primary = this.identifier();
13078 } else if (this.peek().constant) {
13079 primary = this.constant();
13081 this.throwError('not a primary expression', this.peek());
13085 while ((next = this.expect('(', '[', '.'))) {
13086 if (next.text === '(') {
13087 primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() };
13089 } else if (next.text === '[') {
13090 primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true };
13092 } else if (next.text === '.') {
13093 primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false };
13095 this.throwError('IMPOSSIBLE');
13101 filter: function(baseExpression) {
13102 var args = [baseExpression];
13103 var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true};
13105 while (this.expect(':')) {
13106 args.push(this.expression());
13112 parseArguments: function() {
13114 if (this.peekToken().text !== ')') {
13116 args.push(this.expression());
13117 } while (this.expect(','));
13122 identifier: function() {
13123 var token = this.consume();
13124 if (!token.identifier) {
13125 this.throwError('is not a valid identifier', token);
13127 return { type: AST.Identifier, name: token.text };
13130 constant: function() {
13131 // TODO check that it is a constant
13132 return { type: AST.Literal, value: this.consume().value };
13135 arrayDeclaration: function() {
13137 if (this.peekToken().text !== ']') {
13139 if (this.peek(']')) {
13140 // Support trailing commas per ES5.1.
13143 elements.push(this.expression());
13144 } while (this.expect(','));
13148 return { type: AST.ArrayExpression, elements: elements };
13151 object: function() {
13152 var properties = [], property;
13153 if (this.peekToken().text !== '}') {
13155 if (this.peek('}')) {
13156 // Support trailing commas per ES5.1.
13159 property = {type: AST.Property, kind: 'init'};
13160 if (this.peek().constant) {
13161 property.key = this.constant();
13162 } else if (this.peek().identifier) {
13163 property.key = this.identifier();
13165 this.throwError("invalid key", this.peek());
13168 property.value = this.expression();
13169 properties.push(property);
13170 } while (this.expect(','));
13174 return {type: AST.ObjectExpression, properties: properties };
13177 throwError: function(msg, token) {
13178 throw $parseMinErr('syntax',
13179 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].',
13180 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index));
13183 consume: function(e1) {
13184 if (this.tokens.length === 0) {
13185 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
13188 var token = this.expect(e1);
13190 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek());
13195 peekToken: function() {
13196 if (this.tokens.length === 0) {
13197 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
13199 return this.tokens[0];
13202 peek: function(e1, e2, e3, e4) {
13203 return this.peekAhead(0, e1, e2, e3, e4);
13206 peekAhead: function(i, e1, e2, e3, e4) {
13207 if (this.tokens.length > i) {
13208 var token = this.tokens[i];
13209 var t = token.text;
13210 if (t === e1 || t === e2 || t === e3 || t === e4 ||
13211 (!e1 && !e2 && !e3 && !e4)) {
13218 expect: function(e1, e2, e3, e4) {
13219 var token = this.peek(e1, e2, e3, e4);
13221 this.tokens.shift();
13228 /* `undefined` is not a constant, it is an identifier,
13229 * but using it as an identifier is not supported
13232 'true': { type: AST.Literal, value: true },
13233 'false': { type: AST.Literal, value: false },
13234 'null': { type: AST.Literal, value: null },
13235 'undefined': {type: AST.Literal, value: undefined },
13236 'this': {type: AST.ThisExpression }
13240 function ifDefined(v, d) {
13241 return typeof v !== 'undefined' ? v : d;
13244 function plusFn(l, r) {
13245 if (typeof l === 'undefined') return r;
13246 if (typeof r === 'undefined') return l;
13250 function isStateless($filter, filterName) {
13251 var fn = $filter(filterName);
13252 return !fn.$stateful;
13255 function findConstantAndWatchExpressions(ast, $filter) {
13258 switch (ast.type) {
13260 allConstants = true;
13261 forEach(ast.body, function(expr) {
13262 findConstantAndWatchExpressions(expr.expression, $filter);
13263 allConstants = allConstants && expr.expression.constant;
13265 ast.constant = allConstants;
13268 ast.constant = true;
13271 case AST.UnaryExpression:
13272 findConstantAndWatchExpressions(ast.argument, $filter);
13273 ast.constant = ast.argument.constant;
13274 ast.toWatch = ast.argument.toWatch;
13276 case AST.BinaryExpression:
13277 findConstantAndWatchExpressions(ast.left, $filter);
13278 findConstantAndWatchExpressions(ast.right, $filter);
13279 ast.constant = ast.left.constant && ast.right.constant;
13280 ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch);
13282 case AST.LogicalExpression:
13283 findConstantAndWatchExpressions(ast.left, $filter);
13284 findConstantAndWatchExpressions(ast.right, $filter);
13285 ast.constant = ast.left.constant && ast.right.constant;
13286 ast.toWatch = ast.constant ? [] : [ast];
13288 case AST.ConditionalExpression:
13289 findConstantAndWatchExpressions(ast.test, $filter);
13290 findConstantAndWatchExpressions(ast.alternate, $filter);
13291 findConstantAndWatchExpressions(ast.consequent, $filter);
13292 ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant;
13293 ast.toWatch = ast.constant ? [] : [ast];
13295 case AST.Identifier:
13296 ast.constant = false;
13297 ast.toWatch = [ast];
13299 case AST.MemberExpression:
13300 findConstantAndWatchExpressions(ast.object, $filter);
13301 if (ast.computed) {
13302 findConstantAndWatchExpressions(ast.property, $filter);
13304 ast.constant = ast.object.constant && (!ast.computed || ast.property.constant);
13305 ast.toWatch = [ast];
13307 case AST.CallExpression:
13308 allConstants = ast.filter ? isStateless($filter, ast.callee.name) : false;
13310 forEach(ast.arguments, function(expr) {
13311 findConstantAndWatchExpressions(expr, $filter);
13312 allConstants = allConstants && expr.constant;
13313 if (!expr.constant) {
13314 argsToWatch.push.apply(argsToWatch, expr.toWatch);
13317 ast.constant = allConstants;
13318 ast.toWatch = ast.filter && isStateless($filter, ast.callee.name) ? argsToWatch : [ast];
13320 case AST.AssignmentExpression:
13321 findConstantAndWatchExpressions(ast.left, $filter);
13322 findConstantAndWatchExpressions(ast.right, $filter);
13323 ast.constant = ast.left.constant && ast.right.constant;
13324 ast.toWatch = [ast];
13326 case AST.ArrayExpression:
13327 allConstants = true;
13329 forEach(ast.elements, function(expr) {
13330 findConstantAndWatchExpressions(expr, $filter);
13331 allConstants = allConstants && expr.constant;
13332 if (!expr.constant) {
13333 argsToWatch.push.apply(argsToWatch, expr.toWatch);
13336 ast.constant = allConstants;
13337 ast.toWatch = argsToWatch;
13339 case AST.ObjectExpression:
13340 allConstants = true;
13342 forEach(ast.properties, function(property) {
13343 findConstantAndWatchExpressions(property.value, $filter);
13344 allConstants = allConstants && property.value.constant;
13345 if (!property.value.constant) {
13346 argsToWatch.push.apply(argsToWatch, property.value.toWatch);
13349 ast.constant = allConstants;
13350 ast.toWatch = argsToWatch;
13352 case AST.ThisExpression:
13353 ast.constant = false;
13359 function getInputs(body) {
13360 if (body.length != 1) return;
13361 var lastExpression = body[0].expression;
13362 var candidate = lastExpression.toWatch;
13363 if (candidate.length !== 1) return candidate;
13364 return candidate[0] !== lastExpression ? candidate : undefined;
13367 function isAssignable(ast) {
13368 return ast.type === AST.Identifier || ast.type === AST.MemberExpression;
13371 function assignableAST(ast) {
13372 if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) {
13373 return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='};
13377 function isLiteral(ast) {
13378 return ast.body.length === 0 ||
13379 ast.body.length === 1 && (
13380 ast.body[0].expression.type === AST.Literal ||
13381 ast.body[0].expression.type === AST.ArrayExpression ||
13382 ast.body[0].expression.type === AST.ObjectExpression);
13385 function isConstant(ast) {
13386 return ast.constant;
13389 function ASTCompiler(astBuilder, $filter) {
13390 this.astBuilder = astBuilder;
13391 this.$filter = $filter;
13394 ASTCompiler.prototype = {
13395 compile: function(expression, expensiveChecks) {
13397 var ast = this.astBuilder.ast(expression);
13401 expensiveChecks: expensiveChecks,
13402 fn: {vars: [], body: [], own: {}},
13403 assign: {vars: [], body: [], own: {}},
13406 findConstantAndWatchExpressions(ast, self.$filter);
13409 this.stage = 'assign';
13410 if ((assignable = assignableAST(ast))) {
13411 this.state.computing = 'assign';
13412 var result = this.nextId();
13413 this.recurse(assignable, result);
13414 this.return_(result);
13415 extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l');
13417 var toWatch = getInputs(ast.body);
13418 self.stage = 'inputs';
13419 forEach(toWatch, function(watch, key) {
13420 var fnKey = 'fn' + key;
13421 self.state[fnKey] = {vars: [], body: [], own: {}};
13422 self.state.computing = fnKey;
13423 var intoId = self.nextId();
13424 self.recurse(watch, intoId);
13425 self.return_(intoId);
13426 self.state.inputs.push(fnKey);
13427 watch.watchId = key;
13429 this.state.computing = 'fn';
13430 this.stage = 'main';
13433 // The build and minification steps remove the string "use strict" from the code, but this is done using a regex.
13434 // This is a workaround for this until we do a better job at only removing the prefix only when we should.
13435 '"' + this.USE + ' ' + this.STRICT + '";\n' +
13436 this.filterPrefix() +
13437 'var fn=' + this.generateFunction('fn', 's,l,a,i') +
13443 var fn = (new Function('$filter',
13444 'ensureSafeMemberName',
13445 'ensureSafeObject',
13446 'ensureSafeFunction',
13448 'ensureSafeAssignContext',
13454 ensureSafeMemberName,
13456 ensureSafeFunction,
13458 ensureSafeAssignContext,
13463 this.state = this.stage = undefined;
13464 fn.literal = isLiteral(ast);
13465 fn.constant = isConstant(ast);
13473 watchFns: function() {
13475 var fns = this.state.inputs;
13477 forEach(fns, function(name) {
13478 result.push('var ' + name + '=' + self.generateFunction(name, 's'));
13481 result.push('fn.inputs=[' + fns.join(',') + '];');
13483 return result.join('');
13486 generateFunction: function(name, params) {
13487 return 'function(' + params + '){' +
13488 this.varsPrefix(name) +
13493 filterPrefix: function() {
13496 forEach(this.state.filters, function(id, filter) {
13497 parts.push(id + '=$filter(' + self.escape(filter) + ')');
13499 if (parts.length) return 'var ' + parts.join(',') + ';';
13503 varsPrefix: function(section) {
13504 return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : '';
13507 body: function(section) {
13508 return this.state[section].body.join('');
13511 recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
13512 var left, right, self = this, args, expression;
13513 recursionFn = recursionFn || noop;
13514 if (!skipWatchIdCheck && isDefined(ast.watchId)) {
13515 intoId = intoId || this.nextId();
13517 this.lazyAssign(intoId, this.computedMember('i', ast.watchId)),
13518 this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true)
13522 switch (ast.type) {
13524 forEach(ast.body, function(expression, pos) {
13525 self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; });
13526 if (pos !== ast.body.length - 1) {
13527 self.current().body.push(right, ';');
13529 self.return_(right);
13534 expression = this.escape(ast.value);
13535 this.assign(intoId, expression);
13536 recursionFn(expression);
13538 case AST.UnaryExpression:
13539 this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; });
13540 expression = ast.operator + '(' + this.ifDefined(right, 0) + ')';
13541 this.assign(intoId, expression);
13542 recursionFn(expression);
13544 case AST.BinaryExpression:
13545 this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; });
13546 this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; });
13547 if (ast.operator === '+') {
13548 expression = this.plus(left, right);
13549 } else if (ast.operator === '-') {
13550 expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0);
13552 expression = '(' + left + ')' + ast.operator + '(' + right + ')';
13554 this.assign(intoId, expression);
13555 recursionFn(expression);
13557 case AST.LogicalExpression:
13558 intoId = intoId || this.nextId();
13559 self.recurse(ast.left, intoId);
13560 self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId));
13561 recursionFn(intoId);
13563 case AST.ConditionalExpression:
13564 intoId = intoId || this.nextId();
13565 self.recurse(ast.test, intoId);
13566 self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId));
13567 recursionFn(intoId);
13569 case AST.Identifier:
13570 intoId = intoId || this.nextId();
13572 nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s');
13573 nameId.computed = false;
13574 nameId.name = ast.name;
13576 ensureSafeMemberName(ast.name);
13577 self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)),
13579 self.if_(self.stage === 'inputs' || 's', function() {
13580 if (create && create !== 1) {
13582 self.not(self.nonComputedMember('s', ast.name)),
13583 self.lazyAssign(self.nonComputedMember('s', ast.name), '{}'));
13585 self.assign(intoId, self.nonComputedMember('s', ast.name));
13587 }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name))
13589 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) {
13590 self.addEnsureSafeObject(intoId);
13592 recursionFn(intoId);
13594 case AST.MemberExpression:
13595 left = nameId && (nameId.context = this.nextId()) || this.nextId();
13596 intoId = intoId || this.nextId();
13597 self.recurse(ast.object, left, undefined, function() {
13598 self.if_(self.notNull(left), function() {
13599 if (ast.computed) {
13600 right = self.nextId();
13601 self.recurse(ast.property, right);
13602 self.getStringValue(right);
13603 self.addEnsureSafeMemberName(right);
13604 if (create && create !== 1) {
13605 self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}'));
13607 expression = self.ensureSafeObject(self.computedMember(left, right));
13608 self.assign(intoId, expression);
13610 nameId.computed = true;
13611 nameId.name = right;
13614 ensureSafeMemberName(ast.property.name);
13615 if (create && create !== 1) {
13616 self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}'));
13618 expression = self.nonComputedMember(left, ast.property.name);
13619 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) {
13620 expression = self.ensureSafeObject(expression);
13622 self.assign(intoId, expression);
13624 nameId.computed = false;
13625 nameId.name = ast.property.name;
13629 self.assign(intoId, 'undefined');
13631 recursionFn(intoId);
13634 case AST.CallExpression:
13635 intoId = intoId || this.nextId();
13637 right = self.filter(ast.callee.name);
13639 forEach(ast.arguments, function(expr) {
13640 var argument = self.nextId();
13641 self.recurse(expr, argument);
13642 args.push(argument);
13644 expression = right + '(' + args.join(',') + ')';
13645 self.assign(intoId, expression);
13646 recursionFn(intoId);
13648 right = self.nextId();
13651 self.recurse(ast.callee, right, left, function() {
13652 self.if_(self.notNull(right), function() {
13653 self.addEnsureSafeFunction(right);
13654 forEach(ast.arguments, function(expr) {
13655 self.recurse(expr, self.nextId(), undefined, function(argument) {
13656 args.push(self.ensureSafeObject(argument));
13660 if (!self.state.expensiveChecks) {
13661 self.addEnsureSafeObject(left.context);
13663 expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')';
13665 expression = right + '(' + args.join(',') + ')';
13667 expression = self.ensureSafeObject(expression);
13668 self.assign(intoId, expression);
13670 self.assign(intoId, 'undefined');
13672 recursionFn(intoId);
13676 case AST.AssignmentExpression:
13677 right = this.nextId();
13679 if (!isAssignable(ast.left)) {
13680 throw $parseMinErr('lval', 'Trying to assing a value to a non l-value');
13682 this.recurse(ast.left, undefined, left, function() {
13683 self.if_(self.notNull(left.context), function() {
13684 self.recurse(ast.right, right);
13685 self.addEnsureSafeObject(self.member(left.context, left.name, left.computed));
13686 self.addEnsureSafeAssignContext(left.context);
13687 expression = self.member(left.context, left.name, left.computed) + ast.operator + right;
13688 self.assign(intoId, expression);
13689 recursionFn(intoId || expression);
13693 case AST.ArrayExpression:
13695 forEach(ast.elements, function(expr) {
13696 self.recurse(expr, self.nextId(), undefined, function(argument) {
13697 args.push(argument);
13700 expression = '[' + args.join(',') + ']';
13701 this.assign(intoId, expression);
13702 recursionFn(expression);
13704 case AST.ObjectExpression:
13706 forEach(ast.properties, function(property) {
13707 self.recurse(property.value, self.nextId(), undefined, function(expr) {
13708 args.push(self.escape(
13709 property.key.type === AST.Identifier ? property.key.name :
13710 ('' + property.key.value)) +
13714 expression = '{' + args.join(',') + '}';
13715 this.assign(intoId, expression);
13716 recursionFn(expression);
13718 case AST.ThisExpression:
13719 this.assign(intoId, 's');
13722 case AST.NGValueParameter:
13723 this.assign(intoId, 'v');
13729 getHasOwnProperty: function(element, property) {
13730 var key = element + '.' + property;
13731 var own = this.current().own;
13732 if (!own.hasOwnProperty(key)) {
13733 own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')');
13738 assign: function(id, value) {
13740 this.current().body.push(id, '=', value, ';');
13744 filter: function(filterName) {
13745 if (!this.state.filters.hasOwnProperty(filterName)) {
13746 this.state.filters[filterName] = this.nextId(true);
13748 return this.state.filters[filterName];
13751 ifDefined: function(id, defaultValue) {
13752 return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')';
13755 plus: function(left, right) {
13756 return 'plus(' + left + ',' + right + ')';
13759 return_: function(id) {
13760 this.current().body.push('return ', id, ';');
13763 if_: function(test, alternate, consequent) {
13764 if (test === true) {
13767 var body = this.current().body;
13768 body.push('if(', test, '){');
13772 body.push('else{');
13779 not: function(expression) {
13780 return '!(' + expression + ')';
13783 notNull: function(expression) {
13784 return expression + '!=null';
13787 nonComputedMember: function(left, right) {
13788 return left + '.' + right;
13791 computedMember: function(left, right) {
13792 return left + '[' + right + ']';
13795 member: function(left, right, computed) {
13796 if (computed) return this.computedMember(left, right);
13797 return this.nonComputedMember(left, right);
13800 addEnsureSafeObject: function(item) {
13801 this.current().body.push(this.ensureSafeObject(item), ';');
13804 addEnsureSafeMemberName: function(item) {
13805 this.current().body.push(this.ensureSafeMemberName(item), ';');
13808 addEnsureSafeFunction: function(item) {
13809 this.current().body.push(this.ensureSafeFunction(item), ';');
13812 addEnsureSafeAssignContext: function(item) {
13813 this.current().body.push(this.ensureSafeAssignContext(item), ';');
13816 ensureSafeObject: function(item) {
13817 return 'ensureSafeObject(' + item + ',text)';
13820 ensureSafeMemberName: function(item) {
13821 return 'ensureSafeMemberName(' + item + ',text)';
13824 ensureSafeFunction: function(item) {
13825 return 'ensureSafeFunction(' + item + ',text)';
13828 getStringValue: function(item) {
13829 this.assign(item, 'getStringValue(' + item + ',text)');
13832 ensureSafeAssignContext: function(item) {
13833 return 'ensureSafeAssignContext(' + item + ',text)';
13836 lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
13838 return function() {
13839 self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck);
13843 lazyAssign: function(id, value) {
13845 return function() {
13846 self.assign(id, value);
13850 stringEscapeRegex: /[^ a-zA-Z0-9]/g,
13852 stringEscapeFn: function(c) {
13853 return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4);
13856 escape: function(value) {
13857 if (isString(value)) return "'" + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + "'";
13858 if (isNumber(value)) return value.toString();
13859 if (value === true) return 'true';
13860 if (value === false) return 'false';
13861 if (value === null) return 'null';
13862 if (typeof value === 'undefined') return 'undefined';
13864 throw $parseMinErr('esc', 'IMPOSSIBLE');
13867 nextId: function(skip, init) {
13868 var id = 'v' + (this.state.nextId++);
13870 this.current().vars.push(id + (init ? '=' + init : ''));
13875 current: function() {
13876 return this.state[this.state.computing];
13881 function ASTInterpreter(astBuilder, $filter) {
13882 this.astBuilder = astBuilder;
13883 this.$filter = $filter;
13886 ASTInterpreter.prototype = {
13887 compile: function(expression, expensiveChecks) {
13889 var ast = this.astBuilder.ast(expression);
13890 this.expression = expression;
13891 this.expensiveChecks = expensiveChecks;
13892 findConstantAndWatchExpressions(ast, self.$filter);
13895 if ((assignable = assignableAST(ast))) {
13896 assign = this.recurse(assignable);
13898 var toWatch = getInputs(ast.body);
13902 forEach(toWatch, function(watch, key) {
13903 var input = self.recurse(watch);
13904 watch.input = input;
13905 inputs.push(input);
13906 watch.watchId = key;
13909 var expressions = [];
13910 forEach(ast.body, function(expression) {
13911 expressions.push(self.recurse(expression.expression));
13913 var fn = ast.body.length === 0 ? function() {} :
13914 ast.body.length === 1 ? expressions[0] :
13915 function(scope, locals) {
13917 forEach(expressions, function(exp) {
13918 lastValue = exp(scope, locals);
13923 fn.assign = function(scope, value, locals) {
13924 return assign(scope, locals, value);
13928 fn.inputs = inputs;
13930 fn.literal = isLiteral(ast);
13931 fn.constant = isConstant(ast);
13935 recurse: function(ast, context, create) {
13936 var left, right, self = this, args, expression;
13938 return this.inputs(ast.input, ast.watchId);
13940 switch (ast.type) {
13942 return this.value(ast.value, context);
13943 case AST.UnaryExpression:
13944 right = this.recurse(ast.argument);
13945 return this['unary' + ast.operator](right, context);
13946 case AST.BinaryExpression:
13947 left = this.recurse(ast.left);
13948 right = this.recurse(ast.right);
13949 return this['binary' + ast.operator](left, right, context);
13950 case AST.LogicalExpression:
13951 left = this.recurse(ast.left);
13952 right = this.recurse(ast.right);
13953 return this['binary' + ast.operator](left, right, context);
13954 case AST.ConditionalExpression:
13955 return this['ternary?:'](
13956 this.recurse(ast.test),
13957 this.recurse(ast.alternate),
13958 this.recurse(ast.consequent),
13961 case AST.Identifier:
13962 ensureSafeMemberName(ast.name, self.expression);
13963 return self.identifier(ast.name,
13964 self.expensiveChecks || isPossiblyDangerousMemberName(ast.name),
13965 context, create, self.expression);
13966 case AST.MemberExpression:
13967 left = this.recurse(ast.object, false, !!create);
13968 if (!ast.computed) {
13969 ensureSafeMemberName(ast.property.name, self.expression);
13970 right = ast.property.name;
13972 if (ast.computed) right = this.recurse(ast.property);
13973 return ast.computed ?
13974 this.computedMember(left, right, context, create, self.expression) :
13975 this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression);
13976 case AST.CallExpression:
13978 forEach(ast.arguments, function(expr) {
13979 args.push(self.recurse(expr));
13981 if (ast.filter) right = this.$filter(ast.callee.name);
13982 if (!ast.filter) right = this.recurse(ast.callee, true);
13983 return ast.filter ?
13984 function(scope, locals, assign, inputs) {
13986 for (var i = 0; i < args.length; ++i) {
13987 values.push(args[i](scope, locals, assign, inputs));
13989 var value = right.apply(undefined, values, inputs);
13990 return context ? {context: undefined, name: undefined, value: value} : value;
13992 function(scope, locals, assign, inputs) {
13993 var rhs = right(scope, locals, assign, inputs);
13995 if (rhs.value != null) {
13996 ensureSafeObject(rhs.context, self.expression);
13997 ensureSafeFunction(rhs.value, self.expression);
13999 for (var i = 0; i < args.length; ++i) {
14000 values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression));
14002 value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression);
14004 return context ? {value: value} : value;
14006 case AST.AssignmentExpression:
14007 left = this.recurse(ast.left, true, 1);
14008 right = this.recurse(ast.right);
14009 return function(scope, locals, assign, inputs) {
14010 var lhs = left(scope, locals, assign, inputs);
14011 var rhs = right(scope, locals, assign, inputs);
14012 ensureSafeObject(lhs.value, self.expression);
14013 ensureSafeAssignContext(lhs.context);
14014 lhs.context[lhs.name] = rhs;
14015 return context ? {value: rhs} : rhs;
14017 case AST.ArrayExpression:
14019 forEach(ast.elements, function(expr) {
14020 args.push(self.recurse(expr));
14022 return function(scope, locals, assign, inputs) {
14024 for (var i = 0; i < args.length; ++i) {
14025 value.push(args[i](scope, locals, assign, inputs));
14027 return context ? {value: value} : value;
14029 case AST.ObjectExpression:
14031 forEach(ast.properties, function(property) {
14032 args.push({key: property.key.type === AST.Identifier ?
14033 property.key.name :
14034 ('' + property.key.value),
14035 value: self.recurse(property.value)
14038 return function(scope, locals, assign, inputs) {
14040 for (var i = 0; i < args.length; ++i) {
14041 value[args[i].key] = args[i].value(scope, locals, assign, inputs);
14043 return context ? {value: value} : value;
14045 case AST.ThisExpression:
14046 return function(scope) {
14047 return context ? {value: scope} : scope;
14049 case AST.NGValueParameter:
14050 return function(scope, locals, assign, inputs) {
14051 return context ? {value: assign} : assign;
14056 'unary+': function(argument, context) {
14057 return function(scope, locals, assign, inputs) {
14058 var arg = argument(scope, locals, assign, inputs);
14059 if (isDefined(arg)) {
14064 return context ? {value: arg} : arg;
14067 'unary-': function(argument, context) {
14068 return function(scope, locals, assign, inputs) {
14069 var arg = argument(scope, locals, assign, inputs);
14070 if (isDefined(arg)) {
14075 return context ? {value: arg} : arg;
14078 'unary!': function(argument, context) {
14079 return function(scope, locals, assign, inputs) {
14080 var arg = !argument(scope, locals, assign, inputs);
14081 return context ? {value: arg} : arg;
14084 'binary+': function(left, right, context) {
14085 return function(scope, locals, assign, inputs) {
14086 var lhs = left(scope, locals, assign, inputs);
14087 var rhs = right(scope, locals, assign, inputs);
14088 var arg = plusFn(lhs, rhs);
14089 return context ? {value: arg} : arg;
14092 'binary-': function(left, right, context) {
14093 return function(scope, locals, assign, inputs) {
14094 var lhs = left(scope, locals, assign, inputs);
14095 var rhs = right(scope, locals, assign, inputs);
14096 var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0);
14097 return context ? {value: arg} : arg;
14100 'binary*': function(left, right, context) {
14101 return function(scope, locals, assign, inputs) {
14102 var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs);
14103 return context ? {value: arg} : arg;
14106 'binary/': function(left, right, context) {
14107 return function(scope, locals, assign, inputs) {
14108 var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs);
14109 return context ? {value: arg} : arg;
14112 'binary%': function(left, right, context) {
14113 return function(scope, locals, assign, inputs) {
14114 var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs);
14115 return context ? {value: arg} : arg;
14118 'binary===': function(left, right, context) {
14119 return function(scope, locals, assign, inputs) {
14120 var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs);
14121 return context ? {value: arg} : arg;
14124 'binary!==': function(left, right, context) {
14125 return function(scope, locals, assign, inputs) {
14126 var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs);
14127 return context ? {value: arg} : arg;
14130 'binary==': function(left, right, context) {
14131 return function(scope, locals, assign, inputs) {
14132 var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs);
14133 return context ? {value: arg} : arg;
14136 'binary!=': function(left, right, context) {
14137 return function(scope, locals, assign, inputs) {
14138 var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs);
14139 return context ? {value: arg} : arg;
14142 'binary<': function(left, right, context) {
14143 return function(scope, locals, assign, inputs) {
14144 var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs);
14145 return context ? {value: arg} : arg;
14148 'binary>': function(left, right, context) {
14149 return function(scope, locals, assign, inputs) {
14150 var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs);
14151 return context ? {value: arg} : arg;
14154 'binary<=': function(left, right, context) {
14155 return function(scope, locals, assign, inputs) {
14156 var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs);
14157 return context ? {value: arg} : arg;
14160 'binary>=': function(left, right, context) {
14161 return function(scope, locals, assign, inputs) {
14162 var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs);
14163 return context ? {value: arg} : arg;
14166 'binary&&': function(left, right, context) {
14167 return function(scope, locals, assign, inputs) {
14168 var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs);
14169 return context ? {value: arg} : arg;
14172 'binary||': function(left, right, context) {
14173 return function(scope, locals, assign, inputs) {
14174 var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs);
14175 return context ? {value: arg} : arg;
14178 'ternary?:': function(test, alternate, consequent, context) {
14179 return function(scope, locals, assign, inputs) {
14180 var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs);
14181 return context ? {value: arg} : arg;
14184 value: function(value, context) {
14185 return function() { return context ? {context: undefined, name: undefined, value: value} : value; };
14187 identifier: function(name, expensiveChecks, context, create, expression) {
14188 return function(scope, locals, assign, inputs) {
14189 var base = locals && (name in locals) ? locals : scope;
14190 if (create && create !== 1 && base && !(base[name])) {
14193 var value = base ? base[name] : undefined;
14194 if (expensiveChecks) {
14195 ensureSafeObject(value, expression);
14198 return {context: base, name: name, value: value};
14204 computedMember: function(left, right, context, create, expression) {
14205 return function(scope, locals, assign, inputs) {
14206 var lhs = left(scope, locals, assign, inputs);
14210 rhs = right(scope, locals, assign, inputs);
14211 rhs = getStringValue(rhs);
14212 ensureSafeMemberName(rhs, expression);
14213 if (create && create !== 1 && lhs && !(lhs[rhs])) {
14217 ensureSafeObject(value, expression);
14220 return {context: lhs, name: rhs, value: value};
14226 nonComputedMember: function(left, right, expensiveChecks, context, create, expression) {
14227 return function(scope, locals, assign, inputs) {
14228 var lhs = left(scope, locals, assign, inputs);
14229 if (create && create !== 1 && lhs && !(lhs[right])) {
14232 var value = lhs != null ? lhs[right] : undefined;
14233 if (expensiveChecks || isPossiblyDangerousMemberName(right)) {
14234 ensureSafeObject(value, expression);
14237 return {context: lhs, name: right, value: value};
14243 inputs: function(input, watchId) {
14244 return function(scope, value, locals, inputs) {
14245 if (inputs) return inputs[watchId];
14246 return input(scope, value, locals);
14254 var Parser = function(lexer, $filter, options) {
14255 this.lexer = lexer;
14256 this.$filter = $filter;
14257 this.options = options;
14258 this.ast = new AST(this.lexer);
14259 this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) :
14260 new ASTCompiler(this.ast, $filter);
14263 Parser.prototype = {
14264 constructor: Parser,
14266 parse: function(text) {
14267 return this.astCompiler.compile(text, this.options.expensiveChecks);
14271 var getterFnCacheDefault = createMap();
14272 var getterFnCacheExpensive = createMap();
14274 function isPossiblyDangerousMemberName(name) {
14275 return name == 'constructor';
14278 var objectValueOf = Object.prototype.valueOf;
14280 function getValueOf(value) {
14281 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value);
14284 ///////////////////////////////////
14293 * Converts Angular {@link guide/expression expression} into a function.
14296 * var getter = $parse('user.name');
14297 * var setter = getter.assign;
14298 * var context = {user:{name:'angular'}};
14299 * var locals = {user:{name:'local'}};
14301 * expect(getter(context)).toEqual('angular');
14302 * setter(context, 'newValue');
14303 * expect(context.user.name).toEqual('newValue');
14304 * expect(getter(context, locals)).toEqual('local');
14308 * @param {string} expression String expression to compile.
14309 * @returns {function(context, locals)} a function which represents the compiled expression:
14311 * * `context` – `{object}` – an object against which any expressions embedded in the strings
14312 * are evaluated against (typically a scope object).
14313 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
14316 * The returned function also has the following properties:
14317 * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript
14319 * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript
14320 * constant literals.
14321 * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be
14322 * set to a function to change its value on the given context.
14329 * @name $parseProvider
14332 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse}
14335 function $ParseProvider() {
14336 var cacheDefault = createMap();
14337 var cacheExpensive = createMap();
14339 this.$get = ['$filter', function($filter) {
14340 var noUnsafeEval = csp().noUnsafeEval;
14341 var $parseOptions = {
14343 expensiveChecks: false
14345 $parseOptionsExpensive = {
14347 expensiveChecks: true
14350 return function $parse(exp, interceptorFn, expensiveChecks) {
14351 var parsedExpression, oneTime, cacheKey;
14353 switch (typeof exp) {
14358 var cache = (expensiveChecks ? cacheExpensive : cacheDefault);
14359 parsedExpression = cache[cacheKey];
14361 if (!parsedExpression) {
14362 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {
14364 exp = exp.substring(2);
14366 var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions;
14367 var lexer = new Lexer(parseOptions);
14368 var parser = new Parser(lexer, $filter, parseOptions);
14369 parsedExpression = parser.parse(exp);
14370 if (parsedExpression.constant) {
14371 parsedExpression.$$watchDelegate = constantWatchDelegate;
14372 } else if (oneTime) {
14373 parsedExpression.$$watchDelegate = parsedExpression.literal ?
14374 oneTimeLiteralWatchDelegate : oneTimeWatchDelegate;
14375 } else if (parsedExpression.inputs) {
14376 parsedExpression.$$watchDelegate = inputsWatchDelegate;
14378 cache[cacheKey] = parsedExpression;
14380 return addInterceptor(parsedExpression, interceptorFn);
14383 return addInterceptor(exp, interceptorFn);
14390 function expressionInputDirtyCheck(newValue, oldValueOfValue) {
14392 if (newValue == null || oldValueOfValue == null) { // null/undefined
14393 return newValue === oldValueOfValue;
14396 if (typeof newValue === 'object') {
14398 // attempt to convert the value to a primitive type
14399 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can
14400 // be cheaply dirty-checked
14401 newValue = getValueOf(newValue);
14403 if (typeof newValue === 'object') {
14404 // objects/arrays are not supported - deep-watching them would be too expensive
14408 // fall-through to the primitive equality check
14412 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue);
14415 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) {
14416 var inputExpressions = parsedExpression.inputs;
14419 if (inputExpressions.length === 1) {
14420 var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails
14421 inputExpressions = inputExpressions[0];
14422 return scope.$watch(function expressionInputWatch(scope) {
14423 var newInputValue = inputExpressions(scope);
14424 if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) {
14425 lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]);
14426 oldInputValueOf = newInputValue && getValueOf(newInputValue);
14429 }, listener, objectEquality, prettyPrintExpression);
14432 var oldInputValueOfValues = [];
14433 var oldInputValues = [];
14434 for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
14435 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails
14436 oldInputValues[i] = null;
14439 return scope.$watch(function expressionInputsWatch(scope) {
14440 var changed = false;
14442 for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
14443 var newInputValue = inputExpressions[i](scope);
14444 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) {
14445 oldInputValues[i] = newInputValue;
14446 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue);
14451 lastResult = parsedExpression(scope, undefined, undefined, oldInputValues);
14455 }, listener, objectEquality, prettyPrintExpression);
14458 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14459 var unwatch, lastValue;
14460 return unwatch = scope.$watch(function oneTimeWatch(scope) {
14461 return parsedExpression(scope);
14462 }, function oneTimeListener(value, old, scope) {
14464 if (isFunction(listener)) {
14465 listener.apply(this, arguments);
14467 if (isDefined(value)) {
14468 scope.$$postDigest(function() {
14469 if (isDefined(lastValue)) {
14474 }, objectEquality);
14477 function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14478 var unwatch, lastValue;
14479 return unwatch = scope.$watch(function oneTimeWatch(scope) {
14480 return parsedExpression(scope);
14481 }, function oneTimeListener(value, old, scope) {
14483 if (isFunction(listener)) {
14484 listener.call(this, value, old, scope);
14486 if (isAllDefined(value)) {
14487 scope.$$postDigest(function() {
14488 if (isAllDefined(lastValue)) unwatch();
14491 }, objectEquality);
14493 function isAllDefined(value) {
14494 var allDefined = true;
14495 forEach(value, function(val) {
14496 if (!isDefined(val)) allDefined = false;
14502 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14504 return unwatch = scope.$watch(function constantWatch(scope) {
14505 return parsedExpression(scope);
14506 }, function constantListener(value, old, scope) {
14507 if (isFunction(listener)) {
14508 listener.apply(this, arguments);
14511 }, objectEquality);
14514 function addInterceptor(parsedExpression, interceptorFn) {
14515 if (!interceptorFn) return parsedExpression;
14516 var watchDelegate = parsedExpression.$$watchDelegate;
14517 var useInputs = false;
14520 watchDelegate !== oneTimeLiteralWatchDelegate &&
14521 watchDelegate !== oneTimeWatchDelegate;
14523 var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) {
14524 var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs);
14525 return interceptorFn(value, scope, locals);
14526 } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) {
14527 var value = parsedExpression(scope, locals, assign, inputs);
14528 var result = interceptorFn(value, scope, locals);
14529 // we only return the interceptor's result if the
14530 // initial value is defined (for bind-once)
14531 return isDefined(value) ? result : value;
14534 // Propagate $$watchDelegates other then inputsWatchDelegate
14535 if (parsedExpression.$$watchDelegate &&
14536 parsedExpression.$$watchDelegate !== inputsWatchDelegate) {
14537 fn.$$watchDelegate = parsedExpression.$$watchDelegate;
14538 } else if (!interceptorFn.$stateful) {
14539 // If there is an interceptor, but no watchDelegate then treat the interceptor like
14540 // we treat filters - it is assumed to be a pure function unless flagged with $stateful
14541 fn.$$watchDelegate = inputsWatchDelegate;
14542 useInputs = !parsedExpression.inputs;
14543 fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression];
14554 * @requires $rootScope
14557 * A service that helps you run functions asynchronously, and use their return values (or exceptions)
14558 * when they are done processing.
14560 * This is an implementation of promises/deferred objects inspired by
14561 * [Kris Kowal's Q](https://github.com/kriskowal/q).
14563 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred
14564 * implementations, and the other which resembles ES6 promises to some degree.
14568 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver`
14569 * function as the first argument. This is similar to the native Promise implementation from ES6 Harmony,
14570 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
14572 * While the constructor-style use is supported, not all of the supporting methods from ES6 Harmony promises are
14575 * It can be used like so:
14578 * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
14579 * // are available in the current lexical scope (they could have been injected or passed in).
14581 * function asyncGreet(name) {
14582 * // perform some asynchronous operation, resolve or reject the promise when appropriate.
14583 * return $q(function(resolve, reject) {
14584 * setTimeout(function() {
14585 * if (okToGreet(name)) {
14586 * resolve('Hello, ' + name + '!');
14588 * reject('Greeting ' + name + ' is not allowed.');
14594 * var promise = asyncGreet('Robin Hood');
14595 * promise.then(function(greeting) {
14596 * alert('Success: ' + greeting);
14597 * }, function(reason) {
14598 * alert('Failed: ' + reason);
14602 * Note: progress/notify callbacks are not currently supported via the ES6-style interface.
14604 * Note: unlike ES6 behaviour, an exception thrown in the constructor function will NOT implicitly reject the promise.
14606 * However, the more traditional CommonJS-style usage is still available, and documented below.
14608 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
14609 * interface for interacting with an object that represents the result of an action that is
14610 * performed asynchronously, and may or may not be finished at any given point in time.
14612 * From the perspective of dealing with error handling, deferred and promise APIs are to
14613 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
14616 * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
14617 * // are available in the current lexical scope (they could have been injected or passed in).
14619 * function asyncGreet(name) {
14620 * var deferred = $q.defer();
14622 * setTimeout(function() {
14623 * deferred.notify('About to greet ' + name + '.');
14625 * if (okToGreet(name)) {
14626 * deferred.resolve('Hello, ' + name + '!');
14628 * deferred.reject('Greeting ' + name + ' is not allowed.');
14632 * return deferred.promise;
14635 * var promise = asyncGreet('Robin Hood');
14636 * promise.then(function(greeting) {
14637 * alert('Success: ' + greeting);
14638 * }, function(reason) {
14639 * alert('Failed: ' + reason);
14640 * }, function(update) {
14641 * alert('Got notification: ' + update);
14645 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff
14646 * comes in the way of guarantees that promise and deferred APIs make, see
14647 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md.
14649 * Additionally the promise api allows for composition that is very hard to do with the
14650 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
14651 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the
14652 * section on serial or parallel joining of promises.
14654 * # The Deferred API
14656 * A new instance of deferred is constructed by calling `$q.defer()`.
14658 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs
14659 * that can be used for signaling the successful or unsuccessful completion, as well as the status
14664 * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection
14665 * constructed via `$q.reject`, the promise will be rejected instead.
14666 * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
14667 * resolving it with a rejection constructed via `$q.reject`.
14668 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called
14669 * multiple times before the promise is either resolved or rejected.
14673 * - promise – `{Promise}` – promise object associated with this deferred.
14676 * # The Promise API
14678 * A new promise instance is created when a deferred instance is created and can be retrieved by
14679 * calling `deferred.promise`.
14681 * The purpose of the promise object is to allow for interested parties to get access to the result
14682 * of the deferred task when it completes.
14686 * - `then(successCallback, errorCallback, notifyCallback)` – regardless of when the promise was or
14687 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously
14688 * as soon as the result is available. The callbacks are called with a single argument: the result
14689 * or rejection reason. Additionally, the notify callback may be called zero or more times to
14690 * provide a progress indication, before the promise is resolved or rejected.
14692 * This method *returns a new promise* which is resolved or rejected via the return value of the
14693 * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
14694 * with the value which is resolved in that promise using
14695 * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)).
14696 * It also notifies via the return value of the `notifyCallback` method. The promise cannot be
14697 * resolved or rejected from the notifyCallback method.
14699 * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)`
14701 * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise,
14702 * but to do so without modifying the final value. This is useful to release resources or do some
14703 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full
14704 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for
14705 * more information.
14707 * # Chaining promises
14709 * Because calling the `then` method of a promise returns a new derived promise, it is easily
14710 * possible to create a chain of promises:
14713 * promiseB = promiseA.then(function(result) {
14714 * return result + 1;
14717 * // promiseB will be resolved immediately after promiseA is resolved and its value
14718 * // will be the result of promiseA incremented by 1
14721 * It is possible to create chains of any length and since a promise can be resolved with another
14722 * promise (which will defer its resolution further), it is possible to pause/defer resolution of
14723 * the promises at any point in the chain. This makes it possible to implement powerful APIs like
14724 * $http's response interceptors.
14727 * # Differences between Kris Kowal's Q and $q
14729 * There are two main differences:
14731 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
14732 * mechanism in angular, which means faster propagation of resolution or rejection into your
14733 * models and avoiding unnecessary browser repaints, which would result in flickering UI.
14734 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
14735 * all the important functionality needed for common async tasks.
14740 * it('should simulate promise', inject(function($q, $rootScope) {
14741 * var deferred = $q.defer();
14742 * var promise = deferred.promise;
14743 * var resolvedValue;
14745 * promise.then(function(value) { resolvedValue = value; });
14746 * expect(resolvedValue).toBeUndefined();
14748 * // Simulate resolving of promise
14749 * deferred.resolve(123);
14750 * // Note that the 'then' function does not get called synchronously.
14751 * // This is because we want the promise API to always be async, whether or not
14752 * // it got called synchronously or asynchronously.
14753 * expect(resolvedValue).toBeUndefined();
14755 * // Propagate promise resolution to 'then' functions using $apply().
14756 * $rootScope.$apply();
14757 * expect(resolvedValue).toEqual(123);
14761 * @param {function(function, function)} resolver Function which is responsible for resolving or
14762 * rejecting the newly created promise. The first parameter is a function which resolves the
14763 * promise, the second parameter is a function which rejects the promise.
14765 * @returns {Promise} The newly created promise.
14767 function $QProvider() {
14769 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
14770 return qFactory(function(callback) {
14771 $rootScope.$evalAsync(callback);
14772 }, $exceptionHandler);
14776 function $$QProvider() {
14777 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) {
14778 return qFactory(function(callback) {
14779 $browser.defer(callback);
14780 }, $exceptionHandler);
14785 * Constructs a promise manager.
14787 * @param {function(function)} nextTick Function for executing functions in the next turn.
14788 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for
14789 * debugging purposes.
14790 * @returns {object} Promise manager.
14792 function qFactory(nextTick, exceptionHandler) {
14793 var $qMinErr = minErr('$q', TypeError);
14794 function callOnce(self, resolveFn, rejectFn) {
14795 var called = false;
14796 function wrap(fn) {
14797 return function(value) {
14798 if (called) return;
14800 fn.call(self, value);
14804 return [wrap(resolveFn), wrap(rejectFn)];
14809 * @name ng.$q#defer
14813 * Creates a `Deferred` object which represents a task which will finish in the future.
14815 * @returns {Deferred} Returns a new instance of deferred.
14817 var defer = function() {
14818 return new Deferred();
14821 function Promise() {
14822 this.$$state = { status: 0 };
14825 extend(Promise.prototype, {
14826 then: function(onFulfilled, onRejected, progressBack) {
14827 if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) {
14830 var result = new Deferred();
14832 this.$$state.pending = this.$$state.pending || [];
14833 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);
14834 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state);
14836 return result.promise;
14839 "catch": function(callback) {
14840 return this.then(null, callback);
14843 "finally": function(callback, progressBack) {
14844 return this.then(function(value) {
14845 return handleCallback(value, true, callback);
14846 }, function(error) {
14847 return handleCallback(error, false, callback);
14852 //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native
14853 function simpleBind(context, fn) {
14854 return function(value) {
14855 fn.call(context, value);
14859 function processQueue(state) {
14860 var fn, deferred, pending;
14862 pending = state.pending;
14863 state.processScheduled = false;
14864 state.pending = undefined;
14865 for (var i = 0, ii = pending.length; i < ii; ++i) {
14866 deferred = pending[i][0];
14867 fn = pending[i][state.status];
14869 if (isFunction(fn)) {
14870 deferred.resolve(fn(state.value));
14871 } else if (state.status === 1) {
14872 deferred.resolve(state.value);
14874 deferred.reject(state.value);
14877 deferred.reject(e);
14878 exceptionHandler(e);
14883 function scheduleProcessQueue(state) {
14884 if (state.processScheduled || !state.pending) return;
14885 state.processScheduled = true;
14886 nextTick(function() { processQueue(state); });
14889 function Deferred() {
14890 this.promise = new Promise();
14891 //Necessary to support unbound execution :/
14892 this.resolve = simpleBind(this, this.resolve);
14893 this.reject = simpleBind(this, this.reject);
14894 this.notify = simpleBind(this, this.notify);
14897 extend(Deferred.prototype, {
14898 resolve: function(val) {
14899 if (this.promise.$$state.status) return;
14900 if (val === this.promise) {
14901 this.$$reject($qMinErr(
14903 "Expected promise to be resolved with value other than itself '{0}'",
14906 this.$$resolve(val);
14911 $$resolve: function(val) {
14914 fns = callOnce(this, this.$$resolve, this.$$reject);
14916 if ((isObject(val) || isFunction(val))) then = val && val.then;
14917 if (isFunction(then)) {
14918 this.promise.$$state.status = -1;
14919 then.call(val, fns[0], fns[1], this.notify);
14921 this.promise.$$state.value = val;
14922 this.promise.$$state.status = 1;
14923 scheduleProcessQueue(this.promise.$$state);
14927 exceptionHandler(e);
14931 reject: function(reason) {
14932 if (this.promise.$$state.status) return;
14933 this.$$reject(reason);
14936 $$reject: function(reason) {
14937 this.promise.$$state.value = reason;
14938 this.promise.$$state.status = 2;
14939 scheduleProcessQueue(this.promise.$$state);
14942 notify: function(progress) {
14943 var callbacks = this.promise.$$state.pending;
14945 if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) {
14946 nextTick(function() {
14947 var callback, result;
14948 for (var i = 0, ii = callbacks.length; i < ii; i++) {
14949 result = callbacks[i][0];
14950 callback = callbacks[i][3];
14952 result.notify(isFunction(callback) ? callback(progress) : progress);
14954 exceptionHandler(e);
14968 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be
14969 * used to forward rejection in a chain of promises. If you are dealing with the last promise in
14970 * a promise chain, you don't need to worry about it.
14972 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of
14973 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via
14974 * a promise error callback and you want to forward the error to the promise derived from the
14975 * current promise, you have to "rethrow" the error by returning a rejection constructed via
14979 * promiseB = promiseA.then(function(result) {
14980 * // success: do something and resolve promiseB
14981 * // with the old or a new result
14983 * }, function(reason) {
14984 * // error: handle the error if possible and
14985 * // resolve promiseB with newPromiseOrValue,
14986 * // otherwise forward the rejection to promiseB
14987 * if (canHandle(reason)) {
14988 * // handle the error and recover
14989 * return newPromiseOrValue;
14991 * return $q.reject(reason);
14995 * @param {*} reason Constant, message, exception or an object representing the rejection reason.
14996 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
14998 var reject = function(reason) {
14999 var result = new Deferred();
15000 result.reject(reason);
15001 return result.promise;
15004 var makePromise = function makePromise(value, resolved) {
15005 var result = new Deferred();
15007 result.resolve(value);
15009 result.reject(value);
15011 return result.promise;
15014 var handleCallback = function handleCallback(value, isResolved, callback) {
15015 var callbackOutput = null;
15017 if (isFunction(callback)) callbackOutput = callback();
15019 return makePromise(e, false);
15021 if (isPromiseLike(callbackOutput)) {
15022 return callbackOutput.then(function() {
15023 return makePromise(value, isResolved);
15024 }, function(error) {
15025 return makePromise(error, false);
15028 return makePromise(value, isResolved);
15038 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
15039 * This is useful when you are dealing with an object that might or might not be a promise, or if
15040 * the promise comes from a source that can't be trusted.
15042 * @param {*} value Value or a promise
15043 * @param {Function=} successCallback
15044 * @param {Function=} errorCallback
15045 * @param {Function=} progressCallback
15046 * @returns {Promise} Returns a promise of the passed value or promise
15050 var when = function(value, callback, errback, progressBack) {
15051 var result = new Deferred();
15052 result.resolve(value);
15053 return result.promise.then(callback, errback, progressBack);
15062 * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6.
15064 * @param {*} value Value or a promise
15065 * @param {Function=} successCallback
15066 * @param {Function=} errorCallback
15067 * @param {Function=} progressCallback
15068 * @returns {Promise} Returns a promise of the passed value or promise
15070 var resolve = when;
15078 * Combines multiple promises into a single promise that is resolved when all of the input
15079 * promises are resolved.
15081 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
15082 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values,
15083 * each value corresponding to the promise at the same index/key in the `promises` array/hash.
15084 * If any of the promises is resolved with a rejection, this resulting promise will be rejected
15085 * with the same rejection value.
15088 function all(promises) {
15089 var deferred = new Deferred(),
15091 results = isArray(promises) ? [] : {};
15093 forEach(promises, function(promise, key) {
15095 when(promise).then(function(value) {
15096 if (results.hasOwnProperty(key)) return;
15097 results[key] = value;
15098 if (!(--counter)) deferred.resolve(results);
15099 }, function(reason) {
15100 if (results.hasOwnProperty(key)) return;
15101 deferred.reject(reason);
15105 if (counter === 0) {
15106 deferred.resolve(results);
15109 return deferred.promise;
15112 var $Q = function Q(resolver) {
15113 if (!isFunction(resolver)) {
15114 throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver);
15117 if (!(this instanceof Q)) {
15118 // More useful when $Q is the Promise itself.
15119 return new Q(resolver);
15122 var deferred = new Deferred();
15124 function resolveFn(value) {
15125 deferred.resolve(value);
15128 function rejectFn(reason) {
15129 deferred.reject(reason);
15132 resolver(resolveFn, rejectFn);
15134 return deferred.promise;
15138 $Q.reject = reject;
15140 $Q.resolve = resolve;
15146 function $$RAFProvider() { //rAF
15147 this.$get = ['$window', '$timeout', function($window, $timeout) {
15148 var requestAnimationFrame = $window.requestAnimationFrame ||
15149 $window.webkitRequestAnimationFrame;
15151 var cancelAnimationFrame = $window.cancelAnimationFrame ||
15152 $window.webkitCancelAnimationFrame ||
15153 $window.webkitCancelRequestAnimationFrame;
15155 var rafSupported = !!requestAnimationFrame;
15156 var raf = rafSupported
15158 var id = requestAnimationFrame(fn);
15159 return function() {
15160 cancelAnimationFrame(id);
15164 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666
15165 return function() {
15166 $timeout.cancel(timer);
15170 raf.supported = rafSupported;
15179 * The design decisions behind the scope are heavily favored for speed and memory consumption.
15181 * The typical use of scope is to watch the expressions, which most of the time return the same
15182 * value as last time so we optimize the operation.
15184 * Closures construction is expensive in terms of speed as well as memory:
15185 * - No closures, instead use prototypical inheritance for API
15186 * - Internal state needs to be stored on scope directly, which means that private state is
15187 * exposed as $$____ properties
15189 * Loop operations are optimized by using while(count--) { ... }
15190 * - This means that in order to keep the same order of execution as addition we have to add
15191 * items to the array at the beginning (unshift) instead of at the end (push)
15193 * Child scopes are created and removed often
15194 * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists
15196 * There are fewer watches than observers. This is why you don't want the observer to be implemented
15197 * in the same way as watch. Watch requires return of the initialization function which is expensive
15204 * @name $rootScopeProvider
15207 * Provider for the $rootScope service.
15212 * @name $rootScopeProvider#digestTtl
15215 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and
15216 * assuming that the model is unstable.
15218 * The current default is 10 iterations.
15220 * In complex applications it's possible that the dependencies between `$watch`s will result in
15221 * several digest iterations. However if an application needs more than the default 10 digest
15222 * iterations for its model to stabilize then you should investigate what is causing the model to
15223 * continuously change during the digest.
15225 * Increasing the TTL could have performance implications, so you should not change it without
15226 * proper justification.
15228 * @param {number} limit The number of digest iterations.
15237 * Every application has a single root {@link ng.$rootScope.Scope scope}.
15238 * All other scopes are descendant scopes of the root scope. Scopes provide separation
15239 * between the model and the view, via a mechanism for watching the model for changes.
15240 * They also provide event emission/broadcast and subscription facility. See the
15241 * {@link guide/scope developer guide on scopes}.
15243 function $RootScopeProvider() {
15245 var $rootScopeMinErr = minErr('$rootScope');
15246 var lastDirtyWatch = null;
15247 var applyAsyncId = null;
15249 this.digestTtl = function(value) {
15250 if (arguments.length) {
15256 function createChildScopeClass(parent) {
15257 function ChildScope() {
15258 this.$$watchers = this.$$nextSibling =
15259 this.$$childHead = this.$$childTail = null;
15260 this.$$listeners = {};
15261 this.$$listenerCount = {};
15262 this.$$watchersCount = 0;
15263 this.$id = nextUid();
15264 this.$$ChildScope = null;
15266 ChildScope.prototype = parent;
15270 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser',
15271 function($injector, $exceptionHandler, $parse, $browser) {
15273 function destroyChildScope($event) {
15274 $event.currentScope.$$destroyed = true;
15277 function cleanUpScope($scope) {
15280 // There is a memory leak in IE9 if all child scopes are not disconnected
15281 // completely when a scope is destroyed. So this code will recurse up through
15282 // all this scopes children
15284 // See issue https://github.com/angular/angular.js/issues/10706
15285 $scope.$$childHead && cleanUpScope($scope.$$childHead);
15286 $scope.$$nextSibling && cleanUpScope($scope.$$nextSibling);
15289 // The code below works around IE9 and V8's memory leaks
15292 // - https://code.google.com/p/v8/issues/detail?id=2073#c26
15293 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909
15294 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
15296 $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead =
15297 $scope.$$childTail = $scope.$root = $scope.$$watchers = null;
15302 * @name $rootScope.Scope
15305 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
15306 * {@link auto.$injector $injector}. Child scopes are created using the
15307 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
15308 * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for
15309 * an in-depth introduction and usage examples.
15313 * A scope can inherit from a parent scope, as in this example:
15315 var parent = $rootScope;
15316 var child = parent.$new();
15318 parent.salutation = "Hello";
15319 expect(child.salutation).toEqual('Hello');
15321 child.salutation = "Welcome";
15322 expect(child.salutation).toEqual('Welcome');
15323 expect(parent.salutation).toEqual('Hello');
15326 * When interacting with `Scope` in tests, additional helper methods are available on the
15327 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional
15331 * @param {Object.<string, function()>=} providers Map of service factory which need to be
15332 * provided for the current scope. Defaults to {@link ng}.
15333 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should
15334 * append/override services provided by `providers`. This is handy
15335 * when unit-testing and having the need to override a default
15337 * @returns {Object} Newly created scope.
15341 this.$id = nextUid();
15342 this.$$phase = this.$parent = this.$$watchers =
15343 this.$$nextSibling = this.$$prevSibling =
15344 this.$$childHead = this.$$childTail = null;
15346 this.$$destroyed = false;
15347 this.$$listeners = {};
15348 this.$$listenerCount = {};
15349 this.$$watchersCount = 0;
15350 this.$$isolateBindings = null;
15355 * @name $rootScope.Scope#$id
15358 * Unique scope ID (monotonically increasing) useful for debugging.
15363 * @name $rootScope.Scope#$parent
15366 * Reference to the parent scope.
15371 * @name $rootScope.Scope#$root
15374 * Reference to the root scope.
15377 Scope.prototype = {
15378 constructor: Scope,
15381 * @name $rootScope.Scope#$new
15385 * Creates a new child {@link ng.$rootScope.Scope scope}.
15387 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event.
15388 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
15390 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is
15391 * desired for the scope and its child scopes to be permanently detached from the parent and
15392 * thus stop participating in model change detection and listener notification by invoking.
15394 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the
15395 * parent scope. The scope is isolated, as it can not see parent scope properties.
15396 * When creating widgets, it is useful for the widget to not accidentally read parent
15399 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent`
15400 * of the newly created scope. Defaults to `this` scope if not provided.
15401 * This is used when creating a transclude scope to correctly place it
15402 * in the scope hierarchy while maintaining the correct prototypical
15405 * @returns {Object} The newly created child scope.
15408 $new: function(isolate, parent) {
15411 parent = parent || this;
15414 child = new Scope();
15415 child.$root = this.$root;
15417 // Only create a child scope class if somebody asks for one,
15418 // but cache it to allow the VM to optimize lookups.
15419 if (!this.$$ChildScope) {
15420 this.$$ChildScope = createChildScopeClass(this);
15422 child = new this.$$ChildScope();
15424 child.$parent = parent;
15425 child.$$prevSibling = parent.$$childTail;
15426 if (parent.$$childHead) {
15427 parent.$$childTail.$$nextSibling = child;
15428 parent.$$childTail = child;
15430 parent.$$childHead = parent.$$childTail = child;
15433 // When the new scope is not isolated or we inherit from `this`, and
15434 // the parent scope is destroyed, the property `$$destroyed` is inherited
15435 // prototypically. In all other cases, this property needs to be set
15436 // when the parent scope is destroyed.
15437 // The listener needs to be added after the parent is set
15438 if (isolate || parent != this) child.$on('$destroy', destroyChildScope);
15445 * @name $rootScope.Scope#$watch
15449 * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
15451 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest
15452 * $digest()} and should return the value that will be watched. (`watchExpression` should not change
15453 * its value when executed multiple times with the same input because it may be executed multiple
15454 * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be
15455 * [idempotent](http://en.wikipedia.org/wiki/Idempotence).
15456 * - The `listener` is called only when the value from the current `watchExpression` and the
15457 * previous call to `watchExpression` are not equal (with the exception of the initial run,
15458 * see below). Inequality is determined according to reference inequality,
15459 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators)
15460 * via the `!==` Javascript operator, unless `objectEquality == true`
15462 * - When `objectEquality == true`, inequality of the `watchExpression` is determined
15463 * according to the {@link angular.equals} function. To save the value of the object for
15464 * later comparison, the {@link angular.copy} function is used. This therefore means that
15465 * watching complex objects will have adverse memory and performance implications.
15466 * - The watch `listener` may change the model, which may trigger other `listener`s to fire.
15467 * This is achieved by rerunning the watchers until no changes are detected. The rerun
15468 * iteration limit is 10 to prevent an infinite loop deadlock.
15471 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
15472 * you can register a `watchExpression` function with no `listener`. (Be prepared for
15473 * multiple calls to your `watchExpression` because it will execute multiple times in a
15474 * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.)
15476 * After a watcher is registered with the scope, the `listener` fn is called asynchronously
15477 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
15478 * watcher. In rare cases, this is undesirable because the listener is called when the result
15479 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
15480 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the
15481 * listener was called due to initialization.
15487 // let's assume that scope was dependency injected as the $rootScope
15488 var scope = $rootScope;
15489 scope.name = 'misko';
15492 expect(scope.counter).toEqual(0);
15493 scope.$watch('name', function(newValue, oldValue) {
15494 scope.counter = scope.counter + 1;
15496 expect(scope.counter).toEqual(0);
15499 // the listener is always called during the first $digest loop after it was registered
15500 expect(scope.counter).toEqual(1);
15503 // but now it will not be called unless the value changes
15504 expect(scope.counter).toEqual(1);
15506 scope.name = 'adam';
15508 expect(scope.counter).toEqual(2);
15512 // Using a function as a watchExpression
15514 scope.foodCounter = 0;
15515 expect(scope.foodCounter).toEqual(0);
15517 // This function returns the value being watched. It is called for each turn of the $digest loop
15518 function() { return food; },
15519 // This is the change listener, called when the value returned from the above function changes
15520 function(newValue, oldValue) {
15521 if ( newValue !== oldValue ) {
15522 // Only increment the counter if the value changed
15523 scope.foodCounter = scope.foodCounter + 1;
15527 // No digest has been run so the counter will be zero
15528 expect(scope.foodCounter).toEqual(0);
15530 // Run the digest but since food has not changed count will still be zero
15532 expect(scope.foodCounter).toEqual(0);
15534 // Update food and run digest. Now the counter will increment
15535 food = 'cheeseburger';
15537 expect(scope.foodCounter).toEqual(1);
15543 * @param {(function()|string)} watchExpression Expression that is evaluated on each
15544 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers
15545 * a call to the `listener`.
15547 * - `string`: Evaluated as {@link guide/expression expression}
15548 * - `function(scope)`: called with current `scope` as a parameter.
15549 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value
15550 * of `watchExpression` changes.
15552 * - `newVal` contains the current value of the `watchExpression`
15553 * - `oldVal` contains the previous value of the `watchExpression`
15554 * - `scope` refers to the current scope
15555 * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of
15556 * comparing for reference equality.
15557 * @returns {function()} Returns a deregistration function for this listener.
15559 $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) {
15560 var get = $parse(watchExp);
15562 if (get.$$watchDelegate) {
15563 return get.$$watchDelegate(this, listener, objectEquality, get, watchExp);
15566 array = scope.$$watchers,
15569 last: initWatchVal,
15571 exp: prettyPrintExpression || watchExp,
15572 eq: !!objectEquality
15575 lastDirtyWatch = null;
15577 if (!isFunction(listener)) {
15582 array = scope.$$watchers = [];
15584 // we use unshift since we use a while loop in $digest for speed.
15585 // the while loop reads in reverse order.
15586 array.unshift(watcher);
15587 incrementWatchersCount(this, 1);
15589 return function deregisterWatch() {
15590 if (arrayRemove(array, watcher) >= 0) {
15591 incrementWatchersCount(scope, -1);
15593 lastDirtyWatch = null;
15599 * @name $rootScope.Scope#$watchGroup
15603 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`.
15604 * If any one expression in the collection changes the `listener` is executed.
15606 * - The items in the `watchExpressions` array are observed via standard $watch operation and are examined on every
15607 * call to $digest() to see if any items changes.
15608 * - The `listener` is called whenever any expression in the `watchExpressions` array changes.
15610 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually
15611 * watched using {@link ng.$rootScope.Scope#$watch $watch()}
15613 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any
15614 * expression in `watchExpressions` changes
15615 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching
15616 * those of `watchExpression`
15617 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching
15618 * those of `watchExpression`
15619 * The `scope` refers to the current scope.
15620 * @returns {function()} Returns a de-registration function for all listeners.
15622 $watchGroup: function(watchExpressions, listener) {
15623 var oldValues = new Array(watchExpressions.length);
15624 var newValues = new Array(watchExpressions.length);
15625 var deregisterFns = [];
15627 var changeReactionScheduled = false;
15628 var firstRun = true;
15630 if (!watchExpressions.length) {
15631 // No expressions means we call the listener ASAP
15632 var shouldCall = true;
15633 self.$evalAsync(function() {
15634 if (shouldCall) listener(newValues, newValues, self);
15636 return function deregisterWatchGroup() {
15637 shouldCall = false;
15641 if (watchExpressions.length === 1) {
15642 // Special case size of one
15643 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) {
15644 newValues[0] = value;
15645 oldValues[0] = oldValue;
15646 listener(newValues, (value === oldValue) ? newValues : oldValues, scope);
15650 forEach(watchExpressions, function(expr, i) {
15651 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) {
15652 newValues[i] = value;
15653 oldValues[i] = oldValue;
15654 if (!changeReactionScheduled) {
15655 changeReactionScheduled = true;
15656 self.$evalAsync(watchGroupAction);
15659 deregisterFns.push(unwatchFn);
15662 function watchGroupAction() {
15663 changeReactionScheduled = false;
15667 listener(newValues, newValues, self);
15669 listener(newValues, oldValues, self);
15673 return function deregisterWatchGroup() {
15674 while (deregisterFns.length) {
15675 deregisterFns.shift()();
15683 * @name $rootScope.Scope#$watchCollection
15687 * Shallow watches the properties of an object and fires whenever any of the properties change
15688 * (for arrays, this implies watching the array items; for object maps, this implies watching
15689 * the properties). If a change is detected, the `listener` callback is fired.
15691 * - The `obj` collection is observed via standard $watch operation and is examined on every
15692 * call to $digest() to see if any items have been added, removed, or moved.
15693 * - The `listener` is called whenever anything within the `obj` has changed. Examples include
15694 * adding, removing, and moving items belonging to an object or array.
15699 $scope.names = ['igor', 'matias', 'misko', 'james'];
15700 $scope.dataCount = 4;
15702 $scope.$watchCollection('names', function(newNames, oldNames) {
15703 $scope.dataCount = newNames.length;
15706 expect($scope.dataCount).toEqual(4);
15709 //still at 4 ... no changes
15710 expect($scope.dataCount).toEqual(4);
15712 $scope.names.pop();
15715 //now there's been a change
15716 expect($scope.dataCount).toEqual(3);
15720 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The
15721 * expression value should evaluate to an object or an array which is observed on each
15722 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the
15723 * collection will trigger a call to the `listener`.
15725 * @param {function(newCollection, oldCollection, scope)} listener a callback function called
15726 * when a change is detected.
15727 * - The `newCollection` object is the newly modified data obtained from the `obj` expression
15728 * - The `oldCollection` object is a copy of the former collection data.
15729 * Due to performance considerations, the`oldCollection` value is computed only if the
15730 * `listener` function declares two or more arguments.
15731 * - The `scope` argument refers to the current scope.
15733 * @returns {function()} Returns a de-registration function for this listener. When the
15734 * de-registration function is executed, the internal watch operation is terminated.
15736 $watchCollection: function(obj, listener) {
15737 $watchCollectionInterceptor.$stateful = true;
15740 // the current value, updated on each dirty-check run
15742 // a shallow copy of the newValue from the last dirty-check run,
15743 // updated to match newValue during dirty-check run
15745 // a shallow copy of the newValue from when the last change happened
15747 // only track veryOldValue if the listener is asking for it
15748 var trackVeryOldValue = (listener.length > 1);
15749 var changeDetected = 0;
15750 var changeDetector = $parse(obj, $watchCollectionInterceptor);
15751 var internalArray = [];
15752 var internalObject = {};
15753 var initRun = true;
15756 function $watchCollectionInterceptor(_value) {
15758 var newLength, key, bothNaN, newItem, oldItem;
15760 // If the new value is undefined, then return undefined as the watch may be a one-time watch
15761 if (isUndefined(newValue)) return;
15763 if (!isObject(newValue)) { // if primitive
15764 if (oldValue !== newValue) {
15765 oldValue = newValue;
15768 } else if (isArrayLike(newValue)) {
15769 if (oldValue !== internalArray) {
15770 // we are transitioning from something which was not an array into array.
15771 oldValue = internalArray;
15772 oldLength = oldValue.length = 0;
15776 newLength = newValue.length;
15778 if (oldLength !== newLength) {
15779 // if lengths do not match we need to trigger change notification
15781 oldValue.length = oldLength = newLength;
15783 // copy the items to oldValue and look for changes.
15784 for (var i = 0; i < newLength; i++) {
15785 oldItem = oldValue[i];
15786 newItem = newValue[i];
15788 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
15789 if (!bothNaN && (oldItem !== newItem)) {
15791 oldValue[i] = newItem;
15795 if (oldValue !== internalObject) {
15796 // we are transitioning from something which was not an object into object.
15797 oldValue = internalObject = {};
15801 // copy the items to oldValue and look for changes.
15803 for (key in newValue) {
15804 if (hasOwnProperty.call(newValue, key)) {
15806 newItem = newValue[key];
15807 oldItem = oldValue[key];
15809 if (key in oldValue) {
15810 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
15811 if (!bothNaN && (oldItem !== newItem)) {
15813 oldValue[key] = newItem;
15817 oldValue[key] = newItem;
15822 if (oldLength > newLength) {
15823 // we used to have more keys, need to find them and destroy them.
15825 for (key in oldValue) {
15826 if (!hasOwnProperty.call(newValue, key)) {
15828 delete oldValue[key];
15833 return changeDetected;
15836 function $watchCollectionAction() {
15839 listener(newValue, newValue, self);
15841 listener(newValue, veryOldValue, self);
15844 // make a copy for the next time a collection is changed
15845 if (trackVeryOldValue) {
15846 if (!isObject(newValue)) {
15848 veryOldValue = newValue;
15849 } else if (isArrayLike(newValue)) {
15850 veryOldValue = new Array(newValue.length);
15851 for (var i = 0; i < newValue.length; i++) {
15852 veryOldValue[i] = newValue[i];
15854 } else { // if object
15856 for (var key in newValue) {
15857 if (hasOwnProperty.call(newValue, key)) {
15858 veryOldValue[key] = newValue[key];
15865 return this.$watch(changeDetector, $watchCollectionAction);
15870 * @name $rootScope.Scope#$digest
15874 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and
15875 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change
15876 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers}
15877 * until no more listeners are firing. This means that it is possible to get into an infinite
15878 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of
15879 * iterations exceeds 10.
15881 * Usually, you don't call `$digest()` directly in
15882 * {@link ng.directive:ngController controllers} or in
15883 * {@link ng.$compileProvider#directive directives}.
15884 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within
15885 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`.
15887 * If you want to be notified whenever `$digest()` is called,
15888 * you can register a `watchExpression` function with
15889 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`.
15891 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle.
15896 scope.name = 'misko';
15899 expect(scope.counter).toEqual(0);
15900 scope.$watch('name', function(newValue, oldValue) {
15901 scope.counter = scope.counter + 1;
15903 expect(scope.counter).toEqual(0);
15906 // the listener is always called during the first $digest loop after it was registered
15907 expect(scope.counter).toEqual(1);
15910 // but now it will not be called unless the value changes
15911 expect(scope.counter).toEqual(1);
15913 scope.name = 'adam';
15915 expect(scope.counter).toEqual(2);
15919 $digest: function() {
15920 var watch, value, last,
15924 next, current, target = this,
15926 logIdx, logMsg, asyncTask;
15928 beginPhase('$digest');
15929 // Check for changes to browser url that happened in sync before the call to $digest
15930 $browser.$$checkUrlChange();
15932 if (this === $rootScope && applyAsyncId !== null) {
15933 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then
15934 // cancel the scheduled $apply and flush the queue of expressions to be evaluated.
15935 $browser.defer.cancel(applyAsyncId);
15939 lastDirtyWatch = null;
15941 do { // "while dirty" loop
15945 while (asyncQueue.length) {
15947 asyncTask = asyncQueue.shift();
15948 asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals);
15950 $exceptionHandler(e);
15952 lastDirtyWatch = null;
15955 traverseScopesLoop:
15956 do { // "traverse the scopes" loop
15957 if ((watchers = current.$$watchers)) {
15958 // process our watches
15959 length = watchers.length;
15962 watch = watchers[length];
15963 // Most common watches are on primitives, in which case we can short
15964 // circuit it with === operator, only when === fails do we use .equals
15966 if ((value = watch.get(current)) !== (last = watch.last) &&
15968 ? equals(value, last)
15969 : (typeof value === 'number' && typeof last === 'number'
15970 && isNaN(value) && isNaN(last)))) {
15972 lastDirtyWatch = watch;
15973 watch.last = watch.eq ? copy(value, null) : value;
15974 watch.fn(value, ((last === initWatchVal) ? value : last), current);
15977 if (!watchLog[logIdx]) watchLog[logIdx] = [];
15978 watchLog[logIdx].push({
15979 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
15984 } else if (watch === lastDirtyWatch) {
15985 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers
15986 // have already been tested.
15988 break traverseScopesLoop;
15992 $exceptionHandler(e);
15997 // Insanity Warning: scope depth-first traversal
15998 // yes, this code is a bit crazy, but it works and we have tests to prove it!
15999 // this piece should be kept in sync with the traversal in $broadcast
16000 if (!(next = ((current.$$watchersCount && current.$$childHead) ||
16001 (current !== target && current.$$nextSibling)))) {
16002 while (current !== target && !(next = current.$$nextSibling)) {
16003 current = current.$parent;
16006 } while ((current = next));
16008 // `break traverseScopesLoop;` takes us to here
16010 if ((dirty || asyncQueue.length) && !(ttl--)) {
16012 throw $rootScopeMinErr('infdig',
16013 '{0} $digest() iterations reached. Aborting!\n' +
16014 'Watchers fired in the last 5 iterations: {1}',
16018 } while (dirty || asyncQueue.length);
16022 while (postDigestQueue.length) {
16024 postDigestQueue.shift()();
16026 $exceptionHandler(e);
16034 * @name $rootScope.Scope#$destroy
16035 * @eventType broadcast on scope being destroyed
16038 * Broadcasted when a scope and its children are being destroyed.
16040 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
16041 * clean up DOM bindings before an element is removed from the DOM.
16046 * @name $rootScope.Scope#$destroy
16050 * Removes the current scope (and all of its children) from the parent scope. Removal implies
16051 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
16052 * propagate to the current scope and its children. Removal also implies that the current
16053 * scope is eligible for garbage collection.
16055 * The `$destroy()` is usually used by directives such as
16056 * {@link ng.directive:ngRepeat ngRepeat} for managing the
16057 * unrolling of the loop.
16059 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope.
16060 * Application code can register a `$destroy` event handler that will give it a chance to
16061 * perform any necessary cleanup.
16063 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
16064 * clean up DOM bindings before an element is removed from the DOM.
16066 $destroy: function() {
16067 // We can't destroy a scope that has been already destroyed.
16068 if (this.$$destroyed) return;
16069 var parent = this.$parent;
16071 this.$broadcast('$destroy');
16072 this.$$destroyed = true;
16074 if (this === $rootScope) {
16075 //Remove handlers attached to window when $rootScope is removed
16076 $browser.$$applicationDestroyed();
16079 incrementWatchersCount(this, -this.$$watchersCount);
16080 for (var eventName in this.$$listenerCount) {
16081 decrementListenerCount(this, this.$$listenerCount[eventName], eventName);
16084 // sever all the references to parent scopes (after this cleanup, the current scope should
16085 // not be retained by any of our references and should be eligible for garbage collection)
16086 if (parent && parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
16087 if (parent && parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
16088 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
16089 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
16091 // Disable listeners, watchers and apply/digest methods
16092 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop;
16093 this.$on = this.$watch = this.$watchGroup = function() { return noop; };
16094 this.$$listeners = {};
16096 // Disconnect the next sibling to prevent `cleanUpScope` destroying those too
16097 this.$$nextSibling = null;
16098 cleanUpScope(this);
16103 * @name $rootScope.Scope#$eval
16107 * Executes the `expression` on the current scope and returns the result. Any exceptions in
16108 * the expression are propagated (uncaught). This is useful when evaluating Angular
16113 var scope = ng.$rootScope.Scope();
16117 expect(scope.$eval('a+b')).toEqual(3);
16118 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
16121 * @param {(string|function())=} expression An angular expression to be executed.
16123 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16124 * - `function(scope)`: execute the function with the current `scope` parameter.
16126 * @param {(object)=} locals Local variables object, useful for overriding values in scope.
16127 * @returns {*} The result of evaluating the expression.
16129 $eval: function(expr, locals) {
16130 return $parse(expr)(this, locals);
16135 * @name $rootScope.Scope#$evalAsync
16139 * Executes the expression on the current scope at a later point in time.
16141 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only
16144 * - it will execute after the function that scheduled the evaluation (preferably before DOM
16146 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after
16147 * `expression` execution.
16149 * Any exceptions from the execution of the expression are forwarded to the
16150 * {@link ng.$exceptionHandler $exceptionHandler} service.
16152 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle
16153 * will be scheduled. However, it is encouraged to always call code that changes the model
16154 * from within an `$apply` call. That includes code evaluated via `$evalAsync`.
16156 * @param {(string|function())=} expression An angular expression to be executed.
16158 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16159 * - `function(scope)`: execute the function with the current `scope` parameter.
16161 * @param {(object)=} locals Local variables object, useful for overriding values in scope.
16163 $evalAsync: function(expr, locals) {
16164 // if we are outside of an $digest loop and this is the first time we are scheduling async
16165 // task also schedule async auto-flush
16166 if (!$rootScope.$$phase && !asyncQueue.length) {
16167 $browser.defer(function() {
16168 if (asyncQueue.length) {
16169 $rootScope.$digest();
16174 asyncQueue.push({scope: this, expression: expr, locals: locals});
16177 $$postDigest: function(fn) {
16178 postDigestQueue.push(fn);
16183 * @name $rootScope.Scope#$apply
16187 * `$apply()` is used to execute an expression in angular from outside of the angular
16188 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries).
16189 * Because we are calling into the angular framework we need to perform proper scope life
16190 * cycle of {@link ng.$exceptionHandler exception handling},
16191 * {@link ng.$rootScope.Scope#$digest executing watches}.
16195 * # Pseudo-Code of `$apply()`
16197 function $apply(expr) {
16199 return $eval(expr);
16201 $exceptionHandler(e);
16209 * Scope's `$apply()` method transitions through the following stages:
16211 * 1. The {@link guide/expression expression} is executed using the
16212 * {@link ng.$rootScope.Scope#$eval $eval()} method.
16213 * 2. Any exceptions from the execution of the expression are forwarded to the
16214 * {@link ng.$exceptionHandler $exceptionHandler} service.
16215 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the
16216 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
16219 * @param {(string|function())=} exp An angular expression to be executed.
16221 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16222 * - `function(scope)`: execute the function with current `scope` parameter.
16224 * @returns {*} The result of evaluating the expression.
16226 $apply: function(expr) {
16228 beginPhase('$apply');
16230 return this.$eval(expr);
16235 $exceptionHandler(e);
16238 $rootScope.$digest();
16240 $exceptionHandler(e);
16248 * @name $rootScope.Scope#$applyAsync
16252 * Schedule the invocation of $apply to occur at a later time. The actual time difference
16253 * varies across browsers, but is typically around ~10 milliseconds.
16255 * This can be used to queue up multiple expressions which need to be evaluated in the same
16258 * @param {(string|function())=} exp An angular expression to be executed.
16260 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16261 * - `function(scope)`: execute the function with current `scope` parameter.
16263 $applyAsync: function(expr) {
16265 expr && applyAsyncQueue.push($applyAsyncExpression);
16266 scheduleApplyAsync();
16268 function $applyAsyncExpression() {
16275 * @name $rootScope.Scope#$on
16279 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for
16280 * discussion of event life cycle.
16282 * The event listener function format is: `function(event, args...)`. The `event` object
16283 * passed into the listener has the following attributes:
16285 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or
16287 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the
16288 * event propagates through the scope hierarchy, this property is set to null.
16289 * - `name` - `{string}`: name of the event.
16290 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel
16291 * further event propagation (available only for events that were `$emit`-ed).
16292 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag
16294 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
16296 * @param {string} name Event name to listen on.
16297 * @param {function(event, ...args)} listener Function to call when the event is emitted.
16298 * @returns {function()} Returns a deregistration function for this listener.
16300 $on: function(name, listener) {
16301 var namedListeners = this.$$listeners[name];
16302 if (!namedListeners) {
16303 this.$$listeners[name] = namedListeners = [];
16305 namedListeners.push(listener);
16307 var current = this;
16309 if (!current.$$listenerCount[name]) {
16310 current.$$listenerCount[name] = 0;
16312 current.$$listenerCount[name]++;
16313 } while ((current = current.$parent));
16316 return function() {
16317 var indexOfListener = namedListeners.indexOf(listener);
16318 if (indexOfListener !== -1) {
16319 namedListeners[indexOfListener] = null;
16320 decrementListenerCount(self, 1, name);
16328 * @name $rootScope.Scope#$emit
16332 * Dispatches an event `name` upwards through the scope hierarchy notifying the
16333 * registered {@link ng.$rootScope.Scope#$on} listeners.
16335 * The event life cycle starts at the scope on which `$emit` was called. All
16336 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
16337 * notified. Afterwards, the event traverses upwards toward the root scope and calls all
16338 * registered listeners along the way. The event will stop propagating if one of the listeners
16341 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
16342 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
16344 * @param {string} name Event name to emit.
16345 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
16346 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}).
16348 $emit: function(name, args) {
16352 stopPropagation = false,
16355 targetScope: scope,
16356 stopPropagation: function() {stopPropagation = true;},
16357 preventDefault: function() {
16358 event.defaultPrevented = true;
16360 defaultPrevented: false
16362 listenerArgs = concat([event], arguments, 1),
16366 namedListeners = scope.$$listeners[name] || empty;
16367 event.currentScope = scope;
16368 for (i = 0, length = namedListeners.length; i < length; i++) {
16370 // if listeners were deregistered, defragment the array
16371 if (!namedListeners[i]) {
16372 namedListeners.splice(i, 1);
16378 //allow all listeners attached to the current scope to run
16379 namedListeners[i].apply(null, listenerArgs);
16381 $exceptionHandler(e);
16384 //if any listener on the current scope stops propagation, prevent bubbling
16385 if (stopPropagation) {
16386 event.currentScope = null;
16390 scope = scope.$parent;
16393 event.currentScope = null;
16401 * @name $rootScope.Scope#$broadcast
16405 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the
16406 * registered {@link ng.$rootScope.Scope#$on} listeners.
16408 * The event life cycle starts at the scope on which `$broadcast` was called. All
16409 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
16410 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current
16411 * scope and calls all registered listeners along the way. The event cannot be canceled.
16413 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
16414 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
16416 * @param {string} name Event name to broadcast.
16417 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
16418 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
16420 $broadcast: function(name, args) {
16426 targetScope: target,
16427 preventDefault: function() {
16428 event.defaultPrevented = true;
16430 defaultPrevented: false
16433 if (!target.$$listenerCount[name]) return event;
16435 var listenerArgs = concat([event], arguments, 1),
16436 listeners, i, length;
16438 //down while you can, then up and next sibling or up and next sibling until back at root
16439 while ((current = next)) {
16440 event.currentScope = current;
16441 listeners = current.$$listeners[name] || [];
16442 for (i = 0, length = listeners.length; i < length; i++) {
16443 // if listeners were deregistered, defragment the array
16444 if (!listeners[i]) {
16445 listeners.splice(i, 1);
16452 listeners[i].apply(null, listenerArgs);
16454 $exceptionHandler(e);
16458 // Insanity Warning: scope depth-first traversal
16459 // yes, this code is a bit crazy, but it works and we have tests to prove it!
16460 // this piece should be kept in sync with the traversal in $digest
16461 // (though it differs due to having the extra check for $$listenerCount)
16462 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
16463 (current !== target && current.$$nextSibling)))) {
16464 while (current !== target && !(next = current.$$nextSibling)) {
16465 current = current.$parent;
16470 event.currentScope = null;
16475 var $rootScope = new Scope();
16477 //The internal queues. Expose them on the $rootScope for debugging/testing purposes.
16478 var asyncQueue = $rootScope.$$asyncQueue = [];
16479 var postDigestQueue = $rootScope.$$postDigestQueue = [];
16480 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = [];
16485 function beginPhase(phase) {
16486 if ($rootScope.$$phase) {
16487 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase);
16490 $rootScope.$$phase = phase;
16493 function clearPhase() {
16494 $rootScope.$$phase = null;
16497 function incrementWatchersCount(current, count) {
16499 current.$$watchersCount += count;
16500 } while ((current = current.$parent));
16503 function decrementListenerCount(current, count, name) {
16505 current.$$listenerCount[name] -= count;
16507 if (current.$$listenerCount[name] === 0) {
16508 delete current.$$listenerCount[name];
16510 } while ((current = current.$parent));
16514 * function used as an initial value for watchers.
16515 * because it's unique we can easily tell it apart from other values
16517 function initWatchVal() {}
16519 function flushApplyAsync() {
16520 while (applyAsyncQueue.length) {
16522 applyAsyncQueue.shift()();
16524 $exceptionHandler(e);
16527 applyAsyncId = null;
16530 function scheduleApplyAsync() {
16531 if (applyAsyncId === null) {
16532 applyAsyncId = $browser.defer(function() {
16533 $rootScope.$apply(flushApplyAsync);
16542 * Private service to sanitize uris for links and images. Used by $compile and $sanitize.
16544 function $$SanitizeUriProvider() {
16545 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/,
16546 imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/;
16550 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
16551 * urls during a[href] sanitization.
16553 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
16555 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
16556 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
16557 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
16558 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
16560 * @param {RegExp=} regexp New regexp to whitelist urls with.
16561 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
16562 * chaining otherwise.
16564 this.aHrefSanitizationWhitelist = function(regexp) {
16565 if (isDefined(regexp)) {
16566 aHrefSanitizationWhitelist = regexp;
16569 return aHrefSanitizationWhitelist;
16575 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
16576 * urls during img[src] sanitization.
16578 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
16580 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
16581 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
16582 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
16583 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
16585 * @param {RegExp=} regexp New regexp to whitelist urls with.
16586 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
16587 * chaining otherwise.
16589 this.imgSrcSanitizationWhitelist = function(regexp) {
16590 if (isDefined(regexp)) {
16591 imgSrcSanitizationWhitelist = regexp;
16594 return imgSrcSanitizationWhitelist;
16597 this.$get = function() {
16598 return function sanitizeUri(uri, isImage) {
16599 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
16601 normalizedVal = urlResolve(uri).href;
16602 if (normalizedVal !== '' && !normalizedVal.match(regex)) {
16603 return 'unsafe:' + normalizedVal;
16610 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
16611 * Any commits to this file should be reviewed with security in mind. *
16612 * Changes to this file can potentially create security vulnerabilities. *
16613 * An approval from 2 Core members with history of modifying *
16614 * this file is required. *
16616 * Does the change somehow allow for arbitrary javascript to be executed? *
16617 * Or allows for someone to change the prototype of built-in objects? *
16618 * Or gives undesired access to variables likes document or window? *
16619 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
16621 var $sceMinErr = minErr('$sce');
16623 var SCE_CONTEXTS = {
16627 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a
16628 // url. (e.g. ng-include, script src, templateUrl)
16629 RESOURCE_URL: 'resourceUrl',
16633 // Helper functions follow.
16635 function adjustMatcher(matcher) {
16636 if (matcher === 'self') {
16638 } else if (isString(matcher)) {
16639 // Strings match exactly except for 2 wildcards - '*' and '**'.
16640 // '*' matches any character except those from the set ':/.?&'.
16641 // '**' matches any character (like .* in a RegExp).
16642 // More than 2 *'s raises an error as it's ill defined.
16643 if (matcher.indexOf('***') > -1) {
16644 throw $sceMinErr('iwcard',
16645 'Illegal sequence *** in string matcher. String: {0}', matcher);
16647 matcher = escapeForRegexp(matcher).
16648 replace('\\*\\*', '.*').
16649 replace('\\*', '[^:/.?&;]*');
16650 return new RegExp('^' + matcher + '$');
16651 } else if (isRegExp(matcher)) {
16652 // The only other type of matcher allowed is a Regexp.
16653 // Match entire URL / disallow partial matches.
16654 // Flags are reset (i.e. no global, ignoreCase or multiline)
16655 return new RegExp('^' + matcher.source + '$');
16657 throw $sceMinErr('imatcher',
16658 'Matchers may only be "self", string patterns or RegExp objects');
16663 function adjustMatchers(matchers) {
16664 var adjustedMatchers = [];
16665 if (isDefined(matchers)) {
16666 forEach(matchers, function(matcher) {
16667 adjustedMatchers.push(adjustMatcher(matcher));
16670 return adjustedMatchers;
16676 * @name $sceDelegate
16681 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict
16682 * Contextual Escaping (SCE)} services to AngularJS.
16684 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of
16685 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is
16686 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to
16687 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things
16688 * work because `$sce` delegates to `$sceDelegate` for these operations.
16690 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service.
16692 * The default instance of `$sceDelegate` should work out of the box with little pain. While you
16693 * can override it completely to change the behavior of `$sce`, the common case would
16694 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting
16695 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as
16696 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist
16697 * $sceDelegateProvider.resourceUrlWhitelist} and {@link
16698 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
16703 * @name $sceDelegateProvider
16706 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate
16707 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure
16708 * that the URLs used for sourcing Angular templates are safe. Refer {@link
16709 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and
16710 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
16712 * For the general details about this service in Angular, read the main page for {@link ng.$sce
16713 * Strict Contextual Escaping (SCE)}.
16715 * **Example**: Consider the following case. <a name="example"></a>
16717 * - your app is hosted at url `http://myapp.example.com/`
16718 * - but some of your templates are hosted on other domains you control such as
16719 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc.
16720 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`.
16722 * Here is what a secure configuration for this scenario might look like:
16725 * angular.module('myApp', []).config(function($sceDelegateProvider) {
16726 * $sceDelegateProvider.resourceUrlWhitelist([
16727 * // Allow same origin resource loads.
16729 * // Allow loading from our assets domain. Notice the difference between * and **.
16730 * 'http://srv*.assets.example.com/**'
16733 * // The blacklist overrides the whitelist so the open redirect here is blocked.
16734 * $sceDelegateProvider.resourceUrlBlacklist([
16735 * 'http://myapp.example.com/clickThru**'
16741 function $SceDelegateProvider() {
16742 this.SCE_CONTEXTS = SCE_CONTEXTS;
16744 // Resource URLs can also be trusted by policy.
16745 var resourceUrlWhitelist = ['self'],
16746 resourceUrlBlacklist = [];
16750 * @name $sceDelegateProvider#resourceUrlWhitelist
16753 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value
16754 * provided. This must be an array or null. A snapshot of this array is used so further
16755 * changes to the array are ignored.
16757 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
16758 * allowed in this array.
16760 * Note: **an empty whitelist array will block all URLs**!
16762 * @return {Array} the currently set whitelist array.
16764 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only
16765 * same origin resource requests.
16768 * Sets/Gets the whitelist of trusted resource URLs.
16770 this.resourceUrlWhitelist = function(value) {
16771 if (arguments.length) {
16772 resourceUrlWhitelist = adjustMatchers(value);
16774 return resourceUrlWhitelist;
16779 * @name $sceDelegateProvider#resourceUrlBlacklist
16782 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value
16783 * provided. This must be an array or null. A snapshot of this array is used so further
16784 * changes to the array are ignored.
16786 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
16787 * allowed in this array.
16789 * The typical usage for the blacklist is to **block
16790 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as
16791 * these would otherwise be trusted but actually return content from the redirected domain.
16793 * Finally, **the blacklist overrides the whitelist** and has the final say.
16795 * @return {Array} the currently set blacklist array.
16797 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there
16798 * is no blacklist.)
16801 * Sets/Gets the blacklist of trusted resource URLs.
16804 this.resourceUrlBlacklist = function(value) {
16805 if (arguments.length) {
16806 resourceUrlBlacklist = adjustMatchers(value);
16808 return resourceUrlBlacklist;
16811 this.$get = ['$injector', function($injector) {
16813 var htmlSanitizer = function htmlSanitizer(html) {
16814 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
16817 if ($injector.has('$sanitize')) {
16818 htmlSanitizer = $injector.get('$sanitize');
16822 function matchUrl(matcher, parsedUrl) {
16823 if (matcher === 'self') {
16824 return urlIsSameOrigin(parsedUrl);
16826 // definitely a regex. See adjustMatchers()
16827 return !!matcher.exec(parsedUrl.href);
16831 function isResourceUrlAllowedByPolicy(url) {
16832 var parsedUrl = urlResolve(url.toString());
16833 var i, n, allowed = false;
16834 // Ensure that at least one item from the whitelist allows this url.
16835 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) {
16836 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) {
16842 // Ensure that no item from the blacklist blocked this url.
16843 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) {
16844 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) {
16853 function generateHolderType(Base) {
16854 var holderType = function TrustedValueHolderType(trustedValue) {
16855 this.$$unwrapTrustedValue = function() {
16856 return trustedValue;
16860 holderType.prototype = new Base();
16862 holderType.prototype.valueOf = function sceValueOf() {
16863 return this.$$unwrapTrustedValue();
16865 holderType.prototype.toString = function sceToString() {
16866 return this.$$unwrapTrustedValue().toString();
16871 var trustedValueHolderBase = generateHolderType(),
16874 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase);
16875 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase);
16876 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase);
16877 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase);
16878 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]);
16882 * @name $sceDelegate#trustAs
16885 * Returns an object that is trusted by angular for use in specified strict
16886 * contextual escaping contexts (such as ng-bind-html, ng-include, any src
16887 * attribute interpolation, any dom event binding attribute interpolation
16888 * such as for onclick, etc.) that uses the provided value.
16889 * See {@link ng.$sce $sce} for enabling strict contextual escaping.
16891 * @param {string} type The kind of context in which this value is safe for use. e.g. url,
16892 * resourceUrl, html, js and css.
16893 * @param {*} value The value that that should be considered trusted/safe.
16894 * @returns {*} A value that can be used to stand in for the provided `value` in places
16895 * where Angular expects a $sce.trustAs() return value.
16897 function trustAs(type, trustedValue) {
16898 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
16899 if (!Constructor) {
16900 throw $sceMinErr('icontext',
16901 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}',
16902 type, trustedValue);
16904 if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') {
16905 return trustedValue;
16907 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting
16908 // mutable objects, we ensure here that the value passed in is actually a string.
16909 if (typeof trustedValue !== 'string') {
16910 throw $sceMinErr('itype',
16911 'Attempted to trust a non-string value in a content requiring a string: Context: {0}',
16914 return new Constructor(trustedValue);
16919 * @name $sceDelegate#valueOf
16922 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs
16923 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link
16924 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}.
16926 * If the passed parameter is not a value that had been returned by {@link
16927 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is.
16929 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}
16930 * call or anything else.
16931 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs
16932 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns
16933 * `value` unchanged.
16935 function valueOf(maybeTrusted) {
16936 if (maybeTrusted instanceof trustedValueHolderBase) {
16937 return maybeTrusted.$$unwrapTrustedValue();
16939 return maybeTrusted;
16945 * @name $sceDelegate#getTrusted
16948 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and
16949 * returns the originally supplied value if the queried context type is a supertype of the
16950 * created type. If this condition isn't satisfied, throws an exception.
16952 * @param {string} type The kind of context in which this value is to be used.
16953 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs
16954 * `$sceDelegate.trustAs`} call.
16955 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs
16956 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception.
16958 function getTrusted(type, maybeTrusted) {
16959 if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') {
16960 return maybeTrusted;
16962 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
16963 if (constructor && maybeTrusted instanceof constructor) {
16964 return maybeTrusted.$$unwrapTrustedValue();
16966 // If we get here, then we may only take one of two actions.
16967 // 1. sanitize the value for the requested type, or
16968 // 2. throw an exception.
16969 if (type === SCE_CONTEXTS.RESOURCE_URL) {
16970 if (isResourceUrlAllowedByPolicy(maybeTrusted)) {
16971 return maybeTrusted;
16973 throw $sceMinErr('insecurl',
16974 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}',
16975 maybeTrusted.toString());
16977 } else if (type === SCE_CONTEXTS.HTML) {
16978 return htmlSanitizer(maybeTrusted);
16980 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
16983 return { trustAs: trustAs,
16984 getTrusted: getTrusted,
16985 valueOf: valueOf };
16992 * @name $sceProvider
16995 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service.
16996 * - enable/disable Strict Contextual Escaping (SCE) in a module
16997 * - override the default implementation with a custom delegate
16999 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}.
17002 /* jshint maxlen: false*/
17011 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS.
17013 * # Strict Contextual Escaping
17015 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain
17016 * contexts to result in a value that is marked as safe to use for that context. One example of
17017 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer
17018 * to these contexts as privileged or SCE contexts.
17020 * As of version 1.2, Angular ships with SCE enabled by default.
17022 * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow
17023 * one to execute arbitrary javascript by the use of the expression() syntax. Refer
17024 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
17025 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>`
17026 * to the top of your HTML document.
17028 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for
17029 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier.
17031 * Here's an example of a binding in a privileged context:
17034 * <input ng-model="userHtml" aria-label="User input">
17035 * <div ng-bind-html="userHtml"></div>
17038 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE
17039 * disabled, this application allows the user to render arbitrary HTML into the DIV.
17040 * In a more realistic example, one may be rendering user comments, blog articles, etc. via
17041 * bindings. (HTML is just one example of a context where rendering user controlled input creates
17042 * security vulnerabilities.)
17044 * For the case of HTML, you might use a library, either on the client side, or on the server side,
17045 * to sanitize unsafe HTML before binding to the value and rendering it in the document.
17047 * How would you ensure that every place that used these types of bindings was bound to a value that
17048 * was sanitized by your library (or returned as safe for rendering by your server?) How can you
17049 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
17050 * properties/fields and forgot to update the binding to the sanitized value?
17052 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can
17053 * determine that something explicitly says it's safe to use a value for binding in that
17054 * context. You can then audit your code (a simple grep would do) to ensure that this is only done
17055 * for those values that you can easily tell are safe - because they were received from your server,
17056 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps
17057 * allowing only the files in a specific directory to do this. Ensuring that the internal API
17058 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task.
17060 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs}
17061 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to
17062 * obtain values that will be accepted by SCE / privileged contexts.
17065 * ## How does it work?
17067 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted
17068 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link
17069 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the
17070 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals.
17072 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link
17073 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly
17077 * var ngBindHtmlDirective = ['$sce', function($sce) {
17078 * return function(scope, element, attr) {
17079 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
17080 * element.html(value || '');
17086 * ## Impact on loading templates
17088 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as
17089 * `templateUrl`'s specified by {@link guide/directive directives}.
17091 * By default, Angular only loads templates from the same domain and protocol as the application
17092 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl
17093 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or
17094 * protocols, you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist
17095 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value.
17099 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
17100 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
17101 * policy apply in addition to this and may further restrict whether the template is successfully
17102 * loaded. This means that without the right CORS policy, loading templates from a different domain
17103 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some
17106 * ## This feels like too much overhead
17108 * It's important to remember that SCE only applies to interpolation expressions.
17110 * If your expressions are constant literals, they're automatically trusted and you don't need to
17111 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g.
17112 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works.
17114 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them
17115 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here.
17117 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load
17118 * templates in `ng-include` from your application's domain without having to even know about SCE.
17119 * It blocks loading templates from other domains or loading templates over http from an https
17120 * served document. You can change these by setting your own custom {@link
17121 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link
17122 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs.
17124 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an
17125 * application that's secure and can be audited to verify that with much more ease than bolting
17126 * security onto an application later.
17128 * <a name="contexts"></a>
17129 * ## What trusted context types are supported?
17131 * | Context | Notes |
17132 * |---------------------|----------------|
17133 * | `$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. |
17134 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. |
17135 * | `$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. |
17136 * | `$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. |
17137 * | `$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. |
17139 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a>
17141 * Each element in these arrays must be one of the following:
17144 * - The special **string**, `'self'`, can be used to match against all URLs of the **same
17145 * domain** as the application document using the **same protocol**.
17146 * - **String** (except the special value `'self'`)
17147 * - The string is matched against the full *normalized / absolute URL* of the resource
17148 * being tested (substring matches are not good enough.)
17149 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters
17150 * match themselves.
17151 * - `*`: matches zero or more occurrences of any character other than one of the following 6
17152 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use
17154 * - `**`: matches zero or more occurrences of *any* character. As such, it's not
17155 * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g.
17156 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might
17157 * not have been the intention.) Its usage at the very end of the path is ok. (e.g.
17158 * http://foo.example.com/templates/**).
17159 * - **RegExp** (*see caveat below*)
17160 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax
17161 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to
17162 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should
17163 * have good test coverage). For instance, the use of `.` in the regex is correct only in a
17164 * small number of cases. A `.` character in the regex used when matching the scheme or a
17165 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It
17166 * is highly recommended to use the string patterns and only fall back to regular expressions
17167 * as a last resort.
17168 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is
17169 * matched against the **entire** *normalized / absolute URL* of the resource being tested
17170 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags
17171 * present on the RegExp (such as multiline, global, ignoreCase) are ignored.
17172 * - If you are generating your JavaScript from some other templating engine (not
17173 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)),
17174 * remember to escape your regular expression (and be aware that you might need more than
17175 * one level of escaping depending on your templating engine and the way you interpolated
17176 * the value.) Do make use of your platform's escaping mechanism as it might be good
17177 * enough before coding your own. E.g. Ruby has
17178 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape)
17179 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape).
17180 * Javascript lacks a similar built in function for escaping. Take a look at Google
17181 * Closure library's [goog.string.regExpEscape(s)](
17182 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962).
17184 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example.
17186 * ## Show me an example using SCE.
17188 * <example module="mySceApp" deps="angular-sanitize.js">
17189 * <file name="index.html">
17190 * <div ng-controller="AppController as myCtrl">
17191 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br>
17192 * <b>User comments</b><br>
17193 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when
17194 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an
17196 * <div class="well">
17197 * <div ng-repeat="userComment in myCtrl.userComments">
17198 * <b>{{userComment.name}}</b>:
17199 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span>
17206 * <file name="script.js">
17207 * angular.module('mySceApp', ['ngSanitize'])
17208 * .controller('AppController', ['$http', '$templateCache', '$sce',
17209 * function($http, $templateCache, $sce) {
17211 * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) {
17212 * self.userComments = userComments;
17214 * self.explicitlyTrustedHtml = $sce.trustAsHtml(
17215 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' +
17216 * 'sanitization."">Hover over this text.</span>');
17220 * <file name="test_data.json">
17222 * { "name": "Alice",
17224 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>"
17227 * "htmlComment": "<i>Yes!</i> Am I the only other one?"
17232 * <file name="protractor.js" type="protractor">
17233 * describe('SCE doc demo', function() {
17234 * it('should sanitize untrusted values', function() {
17235 * expect(element.all(by.css('.htmlComment')).first().getInnerHtml())
17236 * .toBe('<span>Is <i>anyone</i> reading this?</span>');
17239 * it('should NOT sanitize explicitly trusted values', function() {
17240 * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe(
17241 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' +
17242 * 'sanitization."">Hover over this text.</span>');
17250 * ## Can I disable SCE completely?
17252 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits
17253 * for little coding overhead. It will be much harder to take an SCE disabled application and
17254 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE
17255 * for cases where you have a lot of existing code that was written before SCE was introduced and
17256 * you're migrating them a module at a time.
17258 * That said, here's how you can completely disable SCE:
17261 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
17262 * // Completely disable SCE. For demonstration purposes only!
17263 * // Do not use in new projects.
17264 * $sceProvider.enabled(false);
17269 /* jshint maxlen: 100 */
17271 function $SceProvider() {
17272 var enabled = true;
17276 * @name $sceProvider#enabled
17279 * @param {boolean=} value If provided, then enables/disables SCE.
17280 * @return {boolean} true if SCE is enabled, false otherwise.
17283 * Enables/disables SCE and returns the current value.
17285 this.enabled = function(value) {
17286 if (arguments.length) {
17293 /* Design notes on the default implementation for SCE.
17295 * The API contract for the SCE delegate
17296 * -------------------------------------
17297 * The SCE delegate object must provide the following 3 methods:
17299 * - trustAs(contextEnum, value)
17300 * This method is used to tell the SCE service that the provided value is OK to use in the
17301 * contexts specified by contextEnum. It must return an object that will be accepted by
17302 * getTrusted() for a compatible contextEnum and return this value.
17305 * For values that were not produced by trustAs(), return them as is. For values that were
17306 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if
17307 * trustAs is wrapping the given values into some type, this operation unwraps it when given
17310 * - getTrusted(contextEnum, value)
17311 * This function should return the a value that is safe to use in the context specified by
17312 * contextEnum or throw and exception otherwise.
17314 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be
17315 * opaque or wrapped in some holder object. That happens to be an implementation detail. For
17316 * instance, an implementation could maintain a registry of all trusted objects by context. In
17317 * such a case, trustAs() would return the same object that was passed in. getTrusted() would
17318 * return the same object passed in if it was found in the registry under a compatible context or
17319 * throw an exception otherwise. An implementation might only wrap values some of the time based
17320 * on some criteria. getTrusted() might return a value and not throw an exception for special
17321 * constants or objects even if not wrapped. All such implementations fulfill this contract.
17324 * A note on the inheritance model for SCE contexts
17325 * ------------------------------------------------
17326 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This
17327 * is purely an implementation details.
17329 * The contract is simply this:
17331 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value)
17332 * will also succeed.
17334 * Inheritance happens to capture this in a natural way. In some future, we
17335 * may not use inheritance anymore. That is OK because no code outside of
17336 * sce.js and sceSpecs.js would need to be aware of this detail.
17339 this.$get = ['$parse', '$sceDelegate', function(
17340 $parse, $sceDelegate) {
17341 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow
17342 // the "expression(javascript expression)" syntax which is insecure.
17343 if (enabled && msie < 8) {
17344 throw $sceMinErr('iequirks',
17345 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' +
17346 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
17347 'document. See http://docs.angularjs.org/api/ng.$sce for more information.');
17350 var sce = shallowCopy(SCE_CONTEXTS);
17354 * @name $sce#isEnabled
17357 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you
17358 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}.
17361 * Returns a boolean indicating if SCE is enabled.
17363 sce.isEnabled = function() {
17366 sce.trustAs = $sceDelegate.trustAs;
17367 sce.getTrusted = $sceDelegate.getTrusted;
17368 sce.valueOf = $sceDelegate.valueOf;
17371 sce.trustAs = sce.getTrusted = function(type, value) { return value; };
17372 sce.valueOf = identity;
17377 * @name $sce#parseAs
17380 * Converts Angular {@link guide/expression expression} into a function. This is like {@link
17381 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it
17382 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*,
17385 * @param {string} type The kind of SCE context in which this result will be used.
17386 * @param {string} expression String expression to compile.
17387 * @returns {function(context, locals)} a function which represents the compiled expression:
17389 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17390 * are evaluated against (typically a scope object).
17391 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17394 sce.parseAs = function sceParseAs(type, expr) {
17395 var parsed = $parse(expr);
17396 if (parsed.literal && parsed.constant) {
17399 return $parse(expr, function(value) {
17400 return sce.getTrusted(type, value);
17407 * @name $sce#trustAs
17410 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such,
17411 * returns an object that is trusted by angular for use in specified strict contextual
17412 * escaping contexts (such as ng-bind-html, ng-include, any src attribute
17413 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.)
17414 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual
17417 * @param {string} type The kind of context in which this value is safe for use. e.g. url,
17418 * resourceUrl, html, js and css.
17419 * @param {*} value The value that that should be considered trusted/safe.
17420 * @returns {*} A value that can be used to stand in for the provided `value` in places
17421 * where Angular expects a $sce.trustAs() return value.
17426 * @name $sce#trustAsHtml
17429 * Shorthand method. `$sce.trustAsHtml(value)` →
17430 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`}
17432 * @param {*} value The value to trustAs.
17433 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml
17434 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives
17435 * only accept expressions that are either literal constants or are the
17436 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17441 * @name $sce#trustAsUrl
17444 * Shorthand method. `$sce.trustAsUrl(value)` →
17445 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`}
17447 * @param {*} value The value to trustAs.
17448 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl
17449 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives
17450 * only accept expressions that are either literal constants or are the
17451 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17456 * @name $sce#trustAsResourceUrl
17459 * Shorthand method. `$sce.trustAsResourceUrl(value)` →
17460 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`}
17462 * @param {*} value The value to trustAs.
17463 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl
17464 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives
17465 * only accept expressions that are either literal constants or are the return
17466 * value of {@link ng.$sce#trustAs $sce.trustAs}.)
17471 * @name $sce#trustAsJs
17474 * Shorthand method. `$sce.trustAsJs(value)` →
17475 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`}
17477 * @param {*} value The value to trustAs.
17478 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs
17479 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives
17480 * only accept expressions that are either literal constants or are the
17481 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17486 * @name $sce#getTrusted
17489 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such,
17490 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the
17491 * originally supplied value if the queried context type is a supertype of the created type.
17492 * If this condition isn't satisfied, throws an exception.
17494 * @param {string} type The kind of context in which this value is to be used.
17495 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`}
17497 * @returns {*} The value the was originally provided to
17498 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context.
17499 * Otherwise, throws an exception.
17504 * @name $sce#getTrustedHtml
17507 * Shorthand method. `$sce.getTrustedHtml(value)` →
17508 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`}
17510 * @param {*} value The value to pass to `$sce.getTrusted`.
17511 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)`
17516 * @name $sce#getTrustedCss
17519 * Shorthand method. `$sce.getTrustedCss(value)` →
17520 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`}
17522 * @param {*} value The value to pass to `$sce.getTrusted`.
17523 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)`
17528 * @name $sce#getTrustedUrl
17531 * Shorthand method. `$sce.getTrustedUrl(value)` →
17532 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`}
17534 * @param {*} value The value to pass to `$sce.getTrusted`.
17535 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)`
17540 * @name $sce#getTrustedResourceUrl
17543 * Shorthand method. `$sce.getTrustedResourceUrl(value)` →
17544 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`}
17546 * @param {*} value The value to pass to `$sceDelegate.getTrusted`.
17547 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)`
17552 * @name $sce#getTrustedJs
17555 * Shorthand method. `$sce.getTrustedJs(value)` →
17556 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`}
17558 * @param {*} value The value to pass to `$sce.getTrusted`.
17559 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)`
17564 * @name $sce#parseAsHtml
17567 * Shorthand method. `$sce.parseAsHtml(expression string)` →
17568 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`}
17570 * @param {string} expression String expression to compile.
17571 * @returns {function(context, locals)} a function which represents the compiled expression:
17573 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17574 * are evaluated against (typically a scope object).
17575 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17581 * @name $sce#parseAsCss
17584 * Shorthand method. `$sce.parseAsCss(value)` →
17585 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`}
17587 * @param {string} expression String expression to compile.
17588 * @returns {function(context, locals)} a function which represents the compiled expression:
17590 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17591 * are evaluated against (typically a scope object).
17592 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17598 * @name $sce#parseAsUrl
17601 * Shorthand method. `$sce.parseAsUrl(value)` →
17602 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`}
17604 * @param {string} expression String expression to compile.
17605 * @returns {function(context, locals)} a function which represents the compiled expression:
17607 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17608 * are evaluated against (typically a scope object).
17609 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17615 * @name $sce#parseAsResourceUrl
17618 * Shorthand method. `$sce.parseAsResourceUrl(value)` →
17619 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`}
17621 * @param {string} expression String expression to compile.
17622 * @returns {function(context, locals)} a function which represents the compiled expression:
17624 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17625 * are evaluated against (typically a scope object).
17626 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17632 * @name $sce#parseAsJs
17635 * Shorthand method. `$sce.parseAsJs(value)` →
17636 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`}
17638 * @param {string} expression String expression to compile.
17639 * @returns {function(context, locals)} a function which represents the compiled expression:
17641 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17642 * are evaluated against (typically a scope object).
17643 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17647 // Shorthand delegations.
17648 var parse = sce.parseAs,
17649 getTrusted = sce.getTrusted,
17650 trustAs = sce.trustAs;
17652 forEach(SCE_CONTEXTS, function(enumValue, name) {
17653 var lName = lowercase(name);
17654 sce[camelCase("parse_as_" + lName)] = function(expr) {
17655 return parse(enumValue, expr);
17657 sce[camelCase("get_trusted_" + lName)] = function(value) {
17658 return getTrusted(enumValue, value);
17660 sce[camelCase("trust_as_" + lName)] = function(value) {
17661 return trustAs(enumValue, value);
17670 * !!! This is an undocumented "private" service !!!
17673 * @requires $window
17674 * @requires $document
17676 * @property {boolean} history Does the browser support html5 history api ?
17677 * @property {boolean} transitions Does the browser support CSS transition events ?
17678 * @property {boolean} animations Does the browser support CSS animation events ?
17681 * This is very simple implementation of testing browser's features.
17683 function $SnifferProvider() {
17684 this.$get = ['$window', '$document', function($window, $document) {
17685 var eventSupport = {},
17687 toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
17688 boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
17689 document = $document[0] || {},
17691 vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/,
17692 bodyStyle = document.body && document.body.style,
17693 transitions = false,
17694 animations = false,
17698 for (var prop in bodyStyle) {
17699 if (match = vendorRegex.exec(prop)) {
17700 vendorPrefix = match[0];
17701 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1);
17706 if (!vendorPrefix) {
17707 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit';
17710 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle));
17711 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle));
17713 if (android && (!transitions || !animations)) {
17714 transitions = isString(bodyStyle.webkitTransition);
17715 animations = isString(bodyStyle.webkitAnimation);
17721 // Android has history.pushState, but it does not update location correctly
17722 // so let's not use the history API at all.
17723 // http://code.google.com/p/android/issues/detail?id=17471
17724 // https://github.com/angular/angular.js/issues/904
17726 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has
17727 // so let's not use the history API also
17728 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined
17730 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee),
17732 hasEvent: function(event) {
17733 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
17734 // it. In particular the event is not fired when backspace or delete key are pressed or
17735 // when cut operation is performed.
17736 // IE10+ implements 'input' event but it erroneously fires under various situations,
17737 // e.g. when placeholder changes, or a form is focused.
17738 if (event === 'input' && msie <= 11) return false;
17740 if (isUndefined(eventSupport[event])) {
17741 var divElm = document.createElement('div');
17742 eventSupport[event] = 'on' + event in divElm;
17745 return eventSupport[event];
17748 vendorPrefix: vendorPrefix,
17749 transitions: transitions,
17750 animations: animations,
17756 var $compileMinErr = minErr('$compile');
17760 * @name $templateRequest
17763 * The `$templateRequest` service runs security checks then downloads the provided template using
17764 * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request
17765 * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the
17766 * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the
17767 * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted
17768 * when `tpl` is of type string and `$templateCache` has the matching entry.
17770 * @param {string|TrustedResourceUrl} tpl The HTTP request template URL
17771 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty
17773 * @return {Promise} a promise for the HTTP response data of the given URL.
17775 * @property {number} totalPendingRequests total amount of pending template requests being downloaded.
17777 function $TemplateRequestProvider() {
17778 this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) {
17779 function handleRequestFn(tpl, ignoreRequestError) {
17780 handleRequestFn.totalPendingRequests++;
17782 // We consider the template cache holds only trusted templates, so
17783 // there's no need to go through whitelisting again for keys that already
17784 // are included in there. This also makes Angular accept any script
17785 // directive, no matter its name. However, we still need to unwrap trusted
17787 if (!isString(tpl) || !$templateCache.get(tpl)) {
17788 tpl = $sce.getTrustedResourceUrl(tpl);
17791 var transformResponse = $http.defaults && $http.defaults.transformResponse;
17793 if (isArray(transformResponse)) {
17794 transformResponse = transformResponse.filter(function(transformer) {
17795 return transformer !== defaultHttpResponseTransform;
17797 } else if (transformResponse === defaultHttpResponseTransform) {
17798 transformResponse = null;
17801 var httpOptions = {
17802 cache: $templateCache,
17803 transformResponse: transformResponse
17806 return $http.get(tpl, httpOptions)
17807 ['finally'](function() {
17808 handleRequestFn.totalPendingRequests--;
17810 .then(function(response) {
17811 $templateCache.put(tpl, response.data);
17812 return response.data;
17815 function handleError(resp) {
17816 if (!ignoreRequestError) {
17817 throw $compileMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})',
17818 tpl, resp.status, resp.statusText);
17820 return $q.reject(resp);
17824 handleRequestFn.totalPendingRequests = 0;
17826 return handleRequestFn;
17830 function $$TestabilityProvider() {
17831 this.$get = ['$rootScope', '$browser', '$location',
17832 function($rootScope, $browser, $location) {
17835 * @name $testability
17838 * The private $$testability service provides a collection of methods for use when debugging
17839 * or by automated test and debugging tools.
17841 var testability = {};
17844 * @name $$testability#findBindings
17847 * Returns an array of elements that are bound (via ng-bind or {{}})
17848 * to expressions matching the input.
17850 * @param {Element} element The element root to search from.
17851 * @param {string} expression The binding expression to match.
17852 * @param {boolean} opt_exactMatch If true, only returns exact matches
17853 * for the expression. Filters and whitespace are ignored.
17855 testability.findBindings = function(element, expression, opt_exactMatch) {
17856 var bindings = element.getElementsByClassName('ng-binding');
17858 forEach(bindings, function(binding) {
17859 var dataBinding = angular.element(binding).data('$binding');
17861 forEach(dataBinding, function(bindingName) {
17862 if (opt_exactMatch) {
17863 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)');
17864 if (matcher.test(bindingName)) {
17865 matches.push(binding);
17868 if (bindingName.indexOf(expression) != -1) {
17869 matches.push(binding);
17879 * @name $$testability#findModels
17882 * Returns an array of elements that are two-way found via ng-model to
17883 * expressions matching the input.
17885 * @param {Element} element The element root to search from.
17886 * @param {string} expression The model expression to match.
17887 * @param {boolean} opt_exactMatch If true, only returns exact matches
17888 * for the expression.
17890 testability.findModels = function(element, expression, opt_exactMatch) {
17891 var prefixes = ['ng-', 'data-ng-', 'ng\\:'];
17892 for (var p = 0; p < prefixes.length; ++p) {
17893 var attributeEquals = opt_exactMatch ? '=' : '*=';
17894 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]';
17895 var elements = element.querySelectorAll(selector);
17896 if (elements.length) {
17903 * @name $$testability#getLocation
17906 * Shortcut for getting the location in a browser agnostic way. Returns
17907 * the path, search, and hash. (e.g. /path?a=b#hash)
17909 testability.getLocation = function() {
17910 return $location.url();
17914 * @name $$testability#setLocation
17917 * Shortcut for navigating to a location without doing a full page reload.
17919 * @param {string} url The location url (path, search and hash,
17920 * e.g. /path?a=b#hash) to go to.
17922 testability.setLocation = function(url) {
17923 if (url !== $location.url()) {
17924 $location.url(url);
17925 $rootScope.$digest();
17930 * @name $$testability#whenStable
17933 * Calls the callback when $timeout and $http requests are completed.
17935 * @param {function} callback
17937 testability.whenStable = function(callback) {
17938 $browser.notifyWhenNoOutstandingRequests(callback);
17941 return testability;
17945 function $TimeoutProvider() {
17946 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler',
17947 function($rootScope, $browser, $q, $$q, $exceptionHandler) {
17949 var deferreds = {};
17957 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch
17958 * block and delegates any exceptions to
17959 * {@link ng.$exceptionHandler $exceptionHandler} service.
17961 * The return value of calling `$timeout` is a promise, which will be resolved when
17962 * the delay has passed and the timeout function, if provided, is executed.
17964 * To cancel a timeout request, call `$timeout.cancel(promise)`.
17966 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
17967 * synchronously flush the queue of deferred functions.
17969 * If you only want a promise that will be resolved after some specified delay
17970 * then you can call `$timeout` without the `fn` function.
17972 * @param {function()=} fn A function, whose execution should be delayed.
17973 * @param {number=} [delay=0] Delay in milliseconds.
17974 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
17975 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
17976 * @param {...*=} Pass additional parameters to the executed function.
17977 * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this
17978 * promise will be resolved with is the return value of the `fn` function.
17981 function timeout(fn, delay, invokeApply) {
17982 if (!isFunction(fn)) {
17983 invokeApply = delay;
17988 var args = sliceArgs(arguments, 3),
17989 skipApply = (isDefined(invokeApply) && !invokeApply),
17990 deferred = (skipApply ? $$q : $q).defer(),
17991 promise = deferred.promise,
17994 timeoutId = $browser.defer(function() {
17996 deferred.resolve(fn.apply(null, args));
17998 deferred.reject(e);
17999 $exceptionHandler(e);
18002 delete deferreds[promise.$$timeoutId];
18005 if (!skipApply) $rootScope.$apply();
18008 promise.$$timeoutId = timeoutId;
18009 deferreds[timeoutId] = deferred;
18017 * @name $timeout#cancel
18020 * Cancels a task associated with the `promise`. As a result of this, the promise will be
18021 * resolved with a rejection.
18023 * @param {Promise=} promise Promise returned by the `$timeout` function.
18024 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
18027 timeout.cancel = function(promise) {
18028 if (promise && promise.$$timeoutId in deferreds) {
18029 deferreds[promise.$$timeoutId].reject('canceled');
18030 delete deferreds[promise.$$timeoutId];
18031 return $browser.defer.cancel(promise.$$timeoutId);
18040 // NOTE: The usage of window and document instead of $window and $document here is
18041 // deliberate. This service depends on the specific behavior of anchor nodes created by the
18042 // browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and
18043 // cause us to break tests. In addition, when the browser resolves a URL for XHR, it
18044 // doesn't know about mocked locations and resolves URLs to the real document - which is
18045 // exactly the behavior needed here. There is little value is mocking these out for this
18047 var urlParsingNode = document.createElement("a");
18048 var originUrl = urlResolve(window.location.href);
18053 * Implementation Notes for non-IE browsers
18054 * ----------------------------------------
18055 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM,
18056 * results both in the normalizing and parsing of the URL. Normalizing means that a relative
18057 * URL will be resolved into an absolute URL in the context of the application document.
18058 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related
18059 * properties are all populated to reflect the normalized URL. This approach has wide
18060 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See
18061 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
18063 * Implementation Notes for IE
18064 * ---------------------------
18065 * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other
18066 * browsers. However, the parsed components will not be set if the URL assigned did not specify
18067 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We
18068 * work around that by performing the parsing in a 2nd step by taking a previously normalized
18069 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the
18070 * properties such as protocol, hostname, port, etc.
18073 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement
18074 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
18075 * http://url.spec.whatwg.org/#urlutils
18076 * https://github.com/angular/angular.js/pull/2902
18077 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/
18080 * @param {string} url The URL to be parsed.
18081 * @description Normalizes and parses a URL.
18082 * @returns {object} Returns the normalized URL as a dictionary.
18084 * | member name | Description |
18085 * |---------------|----------------|
18086 * | href | A normalized version of the provided URL if it was not an absolute URL |
18087 * | protocol | The protocol including the trailing colon |
18088 * | host | The host and port (if the port is non-default) of the normalizedUrl |
18089 * | search | The search params, minus the question mark |
18090 * | hash | The hash string, minus the hash symbol
18091 * | hostname | The hostname
18092 * | port | The port, without ":"
18093 * | pathname | The pathname, beginning with "/"
18096 function urlResolve(url) {
18100 // Normalize before parse. Refer Implementation Notes on why this is
18101 // done in two steps on IE.
18102 urlParsingNode.setAttribute("href", href);
18103 href = urlParsingNode.href;
18106 urlParsingNode.setAttribute('href', href);
18108 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
18110 href: urlParsingNode.href,
18111 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
18112 host: urlParsingNode.host,
18113 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '',
18114 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
18115 hostname: urlParsingNode.hostname,
18116 port: urlParsingNode.port,
18117 pathname: (urlParsingNode.pathname.charAt(0) === '/')
18118 ? urlParsingNode.pathname
18119 : '/' + urlParsingNode.pathname
18124 * Parse a request URL and determine whether this is a same-origin request as the application document.
18126 * @param {string|object} requestUrl The url of the request as a string that will be resolved
18127 * or a parsed URL object.
18128 * @returns {boolean} Whether the request is for the same origin as the application document.
18130 function urlIsSameOrigin(requestUrl) {
18131 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl;
18132 return (parsed.protocol === originUrl.protocol &&
18133 parsed.host === originUrl.host);
18141 * A reference to the browser's `window` object. While `window`
18142 * is globally available in JavaScript, it causes testability problems, because
18143 * it is a global variable. In angular we always refer to it through the
18144 * `$window` service, so it may be overridden, removed or mocked for testing.
18146 * Expressions, like the one defined for the `ngClick` directive in the example
18147 * below, are evaluated with respect to the current scope. Therefore, there is
18148 * no risk of inadvertently coding in a dependency on a global value in such an
18152 <example module="windowExample">
18153 <file name="index.html">
18155 angular.module('windowExample', [])
18156 .controller('ExampleController', ['$scope', '$window', function($scope, $window) {
18157 $scope.greeting = 'Hello, World!';
18158 $scope.doGreeting = function(greeting) {
18159 $window.alert(greeting);
18163 <div ng-controller="ExampleController">
18164 <input type="text" ng-model="greeting" aria-label="greeting" />
18165 <button ng-click="doGreeting(greeting)">ALERT</button>
18168 <file name="protractor.js" type="protractor">
18169 it('should display the greeting in the input box', function() {
18170 element(by.model('greeting')).sendKeys('Hello, E2E Tests');
18171 // If we click the button it will block the test runner
18172 // element(':button').click();
18177 function $WindowProvider() {
18178 this.$get = valueFn(window);
18182 * @name $$cookieReader
18183 * @requires $document
18186 * This is a private service for reading cookies used by $http and ngCookies
18188 * @return {Object} a key/value map of the current cookies
18190 function $$CookieReader($document) {
18191 var rawDocument = $document[0] || {};
18192 var lastCookies = {};
18193 var lastCookieString = '';
18195 function safeDecodeURIComponent(str) {
18197 return decodeURIComponent(str);
18203 return function() {
18204 var cookieArray, cookie, i, index, name;
18205 var currentCookieString = rawDocument.cookie || '';
18207 if (currentCookieString !== lastCookieString) {
18208 lastCookieString = currentCookieString;
18209 cookieArray = lastCookieString.split('; ');
18212 for (i = 0; i < cookieArray.length; i++) {
18213 cookie = cookieArray[i];
18214 index = cookie.indexOf('=');
18215 if (index > 0) { //ignore nameless cookies
18216 name = safeDecodeURIComponent(cookie.substring(0, index));
18217 // the first value that is seen for a cookie is the most
18218 // specific one. values for the same cookie name that
18219 // follow are for less specific paths.
18220 if (isUndefined(lastCookies[name])) {
18221 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
18226 return lastCookies;
18230 $$CookieReader.$inject = ['$document'];
18232 function $$CookieReaderProvider() {
18233 this.$get = $$CookieReader;
18236 /* global currencyFilter: true,
18238 filterFilter: true,
18240 limitToFilter: true,
18241 lowercaseFilter: true,
18242 numberFilter: true,
18243 orderByFilter: true,
18244 uppercaseFilter: true,
18249 * @name $filterProvider
18252 * Filters are just functions which transform input to an output. However filters need to be
18253 * Dependency Injected. To achieve this a filter definition consists of a factory function which is
18254 * annotated with dependencies and is responsible for creating a filter function.
18256 * <div class="alert alert-warning">
18257 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
18258 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
18259 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
18260 * (`myapp_subsection_filterx`).
18264 * // Filter registration
18265 * function MyModule($provide, $filterProvider) {
18266 * // create a service to demonstrate injection (not always needed)
18267 * $provide.value('greet', function(name){
18268 * return 'Hello ' + name + '!';
18271 * // register a filter factory which uses the
18272 * // greet service to demonstrate DI.
18273 * $filterProvider.register('greet', function(greet){
18274 * // return the filter function which uses the greet service
18275 * // to generate salutation
18276 * return function(text) {
18277 * // filters need to be forgiving so check input validity
18278 * return text && greet(text) || text;
18284 * The filter function is registered with the `$injector` under the filter name suffix with
18288 * it('should be the same instance', inject(
18289 * function($filterProvider) {
18290 * $filterProvider.register('reverse', function(){
18294 * function($filter, reverseFilter) {
18295 * expect($filter('reverse')).toBe(reverseFilter);
18300 * For more information about how angular filters work, and how to create your own filters, see
18301 * {@link guide/filter Filters} in the Angular Developer Guide.
18309 * Filters are used for formatting data displayed to the user.
18311 * The general syntax in templates is as follows:
18313 * {{ expression [| filter_name[:parameter_value] ... ] }}
18315 * @param {String} name Name of the filter function to retrieve
18316 * @return {Function} the filter function
18318 <example name="$filter" module="filterExample">
18319 <file name="index.html">
18320 <div ng-controller="MainCtrl">
18321 <h3>{{ originalText }}</h3>
18322 <h3>{{ filteredText }}</h3>
18326 <file name="script.js">
18327 angular.module('filterExample', [])
18328 .controller('MainCtrl', function($scope, $filter) {
18329 $scope.originalText = 'hello';
18330 $scope.filteredText = $filter('uppercase')($scope.originalText);
18335 $FilterProvider.$inject = ['$provide'];
18336 function $FilterProvider($provide) {
18337 var suffix = 'Filter';
18341 * @name $filterProvider#register
18342 * @param {string|Object} name Name of the filter function, or an object map of filters where
18343 * the keys are the filter names and the values are the filter factories.
18345 * <div class="alert alert-warning">
18346 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
18347 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
18348 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
18349 * (`myapp_subsection_filterx`).
18351 * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered.
18352 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map
18353 * of the registered filter instances.
18355 function register(name, factory) {
18356 if (isObject(name)) {
18358 forEach(name, function(filter, key) {
18359 filters[key] = register(key, filter);
18363 return $provide.factory(name + suffix, factory);
18366 this.register = register;
18368 this.$get = ['$injector', function($injector) {
18369 return function(name) {
18370 return $injector.get(name + suffix);
18374 ////////////////////////////////////////
18377 currencyFilter: false,
18379 filterFilter: false,
18381 limitToFilter: false,
18382 lowercaseFilter: false,
18383 numberFilter: false,
18384 orderByFilter: false,
18385 uppercaseFilter: false,
18388 register('currency', currencyFilter);
18389 register('date', dateFilter);
18390 register('filter', filterFilter);
18391 register('json', jsonFilter);
18392 register('limitTo', limitToFilter);
18393 register('lowercase', lowercaseFilter);
18394 register('number', numberFilter);
18395 register('orderBy', orderByFilter);
18396 register('uppercase', uppercaseFilter);
18405 * Selects a subset of items from `array` and returns it as a new array.
18407 * @param {Array} array The source array.
18408 * @param {string|Object|function()} expression The predicate to be used for selecting items from
18413 * - `string`: The string is used for matching against the contents of the `array`. All strings or
18414 * objects with string properties in `array` that match this string will be returned. This also
18415 * applies to nested object properties.
18416 * The predicate can be negated by prefixing the string with `!`.
18418 * - `Object`: A pattern object can be used to filter specific properties on objects contained
18419 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
18420 * which have property `name` containing "M" and property `phone` containing "1". A special
18421 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any
18422 * property of the object or its nested object properties. That's equivalent to the simple
18423 * substring match with a `string` as described above. The predicate can be negated by prefixing
18424 * the string with `!`.
18425 * For example `{name: "!M"}` predicate will return an array of items which have property `name`
18426 * not containing "M".
18428 * Note that a named property will match properties on the same level only, while the special
18429 * `$` property will match properties on the same level or deeper. E.g. an array item like
18430 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but
18431 * **will** be matched by `{$: 'John'}`.
18433 * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters.
18434 * The function is called for each element of the array, with the element, its index, and
18435 * the entire array itself as arguments.
18437 * The final result is an array of those elements that the predicate returned true for.
18439 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in
18440 * determining if the expected value (from the filter expression) and actual value (from
18441 * the object in the array) should be considered a match.
18445 * - `function(actual, expected)`:
18446 * The function will be given the object value and the predicate value to compare and
18447 * should return true if both values should be considered equal.
18449 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`.
18450 * This is essentially strict comparison of expected and actual.
18452 * - `false|undefined`: A short hand for a function which will look for a substring match in case
18455 * Primitive values are converted to strings. Objects are not compared against primitives,
18456 * unless they have a custom `toString` method (e.g. `Date` objects).
18460 <file name="index.html">
18461 <div ng-init="friends = [{name:'John', phone:'555-1276'},
18462 {name:'Mary', phone:'800-BIG-MARY'},
18463 {name:'Mike', phone:'555-4321'},
18464 {name:'Adam', phone:'555-5678'},
18465 {name:'Julie', phone:'555-8765'},
18466 {name:'Juliette', phone:'555-5678'}]"></div>
18468 <label>Search: <input ng-model="searchText"></label>
18469 <table id="searchTextResults">
18470 <tr><th>Name</th><th>Phone</th></tr>
18471 <tr ng-repeat="friend in friends | filter:searchText">
18472 <td>{{friend.name}}</td>
18473 <td>{{friend.phone}}</td>
18477 <label>Any: <input ng-model="search.$"></label> <br>
18478 <label>Name only <input ng-model="search.name"></label><br>
18479 <label>Phone only <input ng-model="search.phone"></label><br>
18480 <label>Equality <input type="checkbox" ng-model="strict"></label><br>
18481 <table id="searchObjResults">
18482 <tr><th>Name</th><th>Phone</th></tr>
18483 <tr ng-repeat="friendObj in friends | filter:search:strict">
18484 <td>{{friendObj.name}}</td>
18485 <td>{{friendObj.phone}}</td>
18489 <file name="protractor.js" type="protractor">
18490 var expectFriendNames = function(expectedNames, key) {
18491 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) {
18492 arr.forEach(function(wd, i) {
18493 expect(wd.getText()).toMatch(expectedNames[i]);
18498 it('should search across all fields when filtering with a string', function() {
18499 var searchText = element(by.model('searchText'));
18500 searchText.clear();
18501 searchText.sendKeys('m');
18502 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend');
18504 searchText.clear();
18505 searchText.sendKeys('76');
18506 expectFriendNames(['John', 'Julie'], 'friend');
18509 it('should search in specific fields when filtering with a predicate object', function() {
18510 var searchAny = element(by.model('search.$'));
18512 searchAny.sendKeys('i');
18513 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj');
18515 it('should use a equal comparison when comparator is true', function() {
18516 var searchName = element(by.model('search.name'));
18517 var strict = element(by.model('strict'));
18518 searchName.clear();
18519 searchName.sendKeys('Julie');
18521 expectFriendNames(['Julie'], 'friendObj');
18526 function filterFilter() {
18527 return function(array, expression, comparator) {
18528 if (!isArrayLike(array)) {
18529 if (array == null) {
18532 throw minErr('filter')('notarray', 'Expected array but received: {0}', array);
18536 var expressionType = getTypeForFilter(expression);
18538 var matchAgainstAnyProp;
18540 switch (expressionType) {
18542 predicateFn = expression;
18548 matchAgainstAnyProp = true;
18552 predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp);
18558 return Array.prototype.filter.call(array, predicateFn);
18562 // Helper functions for `filterFilter`
18563 function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
18564 var shouldMatchPrimitives = isObject(expression) && ('$' in expression);
18567 if (comparator === true) {
18568 comparator = equals;
18569 } else if (!isFunction(comparator)) {
18570 comparator = function(actual, expected) {
18571 if (isUndefined(actual)) {
18572 // No substring matching against `undefined`
18575 if ((actual === null) || (expected === null)) {
18576 // No substring matching against `null`; only match against `null`
18577 return actual === expected;
18579 if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) {
18580 // Should not compare primitives against objects, unless they have custom `toString` method
18584 actual = lowercase('' + actual);
18585 expected = lowercase('' + expected);
18586 return actual.indexOf(expected) !== -1;
18590 predicateFn = function(item) {
18591 if (shouldMatchPrimitives && !isObject(item)) {
18592 return deepCompare(item, expression.$, comparator, false);
18594 return deepCompare(item, expression, comparator, matchAgainstAnyProp);
18597 return predicateFn;
18600 function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) {
18601 var actualType = getTypeForFilter(actual);
18602 var expectedType = getTypeForFilter(expected);
18604 if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
18605 return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
18606 } else if (isArray(actual)) {
18607 // In case `actual` is an array, consider it a match
18608 // if ANY of it's items matches `expected`
18609 return actual.some(function(item) {
18610 return deepCompare(item, expected, comparator, matchAgainstAnyProp);
18614 switch (actualType) {
18617 if (matchAgainstAnyProp) {
18618 for (key in actual) {
18619 if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) {
18623 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false);
18624 } else if (expectedType === 'object') {
18625 for (key in expected) {
18626 var expectedVal = expected[key];
18627 if (isFunction(expectedVal) || isUndefined(expectedVal)) {
18631 var matchAnyProperty = key === '$';
18632 var actualVal = matchAnyProperty ? actual : actual[key];
18633 if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) {
18639 return comparator(actual, expected);
18645 return comparator(actual, expected);
18649 // Used for easily differentiating between `null` and actual `object`
18650 function getTypeForFilter(val) {
18651 return (val === null) ? 'null' : typeof val;
18660 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
18661 * symbol for current locale is used.
18663 * @param {number} amount Input to filter.
18664 * @param {string=} symbol Currency symbol or identifier to be displayed.
18665 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale
18666 * @returns {string} Formatted number.
18670 <example module="currencyExample">
18671 <file name="index.html">
18673 angular.module('currencyExample', [])
18674 .controller('ExampleController', ['$scope', function($scope) {
18675 $scope.amount = 1234.56;
18678 <div ng-controller="ExampleController">
18679 <input type="number" ng-model="amount" aria-label="amount"> <br>
18680 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br>
18681 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span>
18682 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span>
18685 <file name="protractor.js" type="protractor">
18686 it('should init with 1234.56', function() {
18687 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56');
18688 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56');
18689 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235');
18691 it('should update', function() {
18692 if (browser.params.browser == 'safari') {
18693 // Safari does not understand the minus key. See
18694 // https://github.com/angular/protractor/issues/481
18697 element(by.model('amount')).clear();
18698 element(by.model('amount')).sendKeys('-1234');
18699 expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00');
18700 expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00');
18701 expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234');
18706 currencyFilter.$inject = ['$locale'];
18707 function currencyFilter($locale) {
18708 var formats = $locale.NUMBER_FORMATS;
18709 return function(amount, currencySymbol, fractionSize) {
18710 if (isUndefined(currencySymbol)) {
18711 currencySymbol = formats.CURRENCY_SYM;
18714 if (isUndefined(fractionSize)) {
18715 fractionSize = formats.PATTERNS[1].maxFrac;
18718 // if null or undefined pass it through
18719 return (amount == null)
18721 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize).
18722 replace(/\u00A4/g, currencySymbol);
18732 * Formats a number as text.
18734 * If the input is null or undefined, it will just be returned.
18735 * If the input is infinite (Infinity/-Infinity) the Infinity symbol '∞' is returned.
18736 * If the input is not a number an empty string is returned.
18739 * @param {number|string} number Number to format.
18740 * @param {(number|string)=} fractionSize Number of decimal places to round the number to.
18741 * If this is not provided then the fraction size is computed from the current locale's number
18742 * formatting pattern. In the case of the default locale, it will be 3.
18743 * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
18746 <example module="numberFilterExample">
18747 <file name="index.html">
18749 angular.module('numberFilterExample', [])
18750 .controller('ExampleController', ['$scope', function($scope) {
18751 $scope.val = 1234.56789;
18754 <div ng-controller="ExampleController">
18755 <label>Enter number: <input ng-model='val'></label><br>
18756 Default formatting: <span id='number-default'>{{val | number}}</span><br>
18757 No fractions: <span>{{val | number:0}}</span><br>
18758 Negative number: <span>{{-val | number:4}}</span>
18761 <file name="protractor.js" type="protractor">
18762 it('should format numbers', function() {
18763 expect(element(by.id('number-default')).getText()).toBe('1,234.568');
18764 expect(element(by.binding('val | number:0')).getText()).toBe('1,235');
18765 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679');
18768 it('should update', function() {
18769 element(by.model('val')).clear();
18770 element(by.model('val')).sendKeys('3374.333');
18771 expect(element(by.id('number-default')).getText()).toBe('3,374.333');
18772 expect(element(by.binding('val | number:0')).getText()).toBe('3,374');
18773 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330');
18780 numberFilter.$inject = ['$locale'];
18781 function numberFilter($locale) {
18782 var formats = $locale.NUMBER_FORMATS;
18783 return function(number, fractionSize) {
18785 // if null or undefined pass it through
18786 return (number == null)
18788 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP,
18793 var DECIMAL_SEP = '.';
18794 function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
18795 if (isObject(number)) return '';
18797 var isNegative = number < 0;
18798 number = Math.abs(number);
18800 var isInfinity = number === Infinity;
18801 if (!isInfinity && !isFinite(number)) return '';
18803 var numStr = number + '',
18805 hasExponent = false,
18808 if (isInfinity) formatedText = '\u221e';
18810 if (!isInfinity && numStr.indexOf('e') !== -1) {
18811 var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
18812 if (match && match[2] == '-' && match[3] > fractionSize + 1) {
18815 formatedText = numStr;
18816 hasExponent = true;
18820 if (!isInfinity && !hasExponent) {
18821 var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
18823 // determine fractionSize if it is not specified
18824 if (isUndefined(fractionSize)) {
18825 fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac);
18828 // safely round numbers in JS without hitting imprecisions of floating-point arithmetics
18830 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
18831 number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
18833 var fraction = ('' + number).split(DECIMAL_SEP);
18834 var whole = fraction[0];
18835 fraction = fraction[1] || '';
18838 lgroup = pattern.lgSize,
18839 group = pattern.gSize;
18841 if (whole.length >= (lgroup + group)) {
18842 pos = whole.length - lgroup;
18843 for (i = 0; i < pos; i++) {
18844 if ((pos - i) % group === 0 && i !== 0) {
18845 formatedText += groupSep;
18847 formatedText += whole.charAt(i);
18851 for (i = pos; i < whole.length; i++) {
18852 if ((whole.length - i) % lgroup === 0 && i !== 0) {
18853 formatedText += groupSep;
18855 formatedText += whole.charAt(i);
18858 // format fraction part.
18859 while (fraction.length < fractionSize) {
18863 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize);
18865 if (fractionSize > 0 && number < 1) {
18866 formatedText = number.toFixed(fractionSize);
18867 number = parseFloat(formatedText);
18868 formatedText = formatedText.replace(DECIMAL_SEP, decimalSep);
18872 if (number === 0) {
18873 isNegative = false;
18876 parts.push(isNegative ? pattern.negPre : pattern.posPre,
18878 isNegative ? pattern.negSuf : pattern.posSuf);
18879 return parts.join('');
18882 function padNumber(num, digits, trim) {
18889 while (num.length < digits) num = '0' + num;
18891 num = num.substr(num.length - digits);
18897 function dateGetter(name, size, offset, trim) {
18898 offset = offset || 0;
18899 return function(date) {
18900 var value = date['get' + name]();
18901 if (offset > 0 || value > -offset) {
18904 if (value === 0 && offset == -12) value = 12;
18905 return padNumber(value, size, trim);
18909 function dateStrGetter(name, shortForm) {
18910 return function(date, formats) {
18911 var value = date['get' + name]();
18912 var get = uppercase(shortForm ? ('SHORT' + name) : name);
18914 return formats[get][value];
18918 function timeZoneGetter(date, formats, offset) {
18919 var zone = -1 * offset;
18920 var paddedZone = (zone >= 0) ? "+" : "";
18922 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) +
18923 padNumber(Math.abs(zone % 60), 2);
18928 function getFirstThursdayOfYear(year) {
18929 // 0 = index of January
18930 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay();
18931 // 4 = index of Thursday (+1 to account for 1st = 5)
18932 // 11 = index of *next* Thursday (+1 account for 1st = 12)
18933 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst);
18936 function getThursdayThisWeek(datetime) {
18937 return new Date(datetime.getFullYear(), datetime.getMonth(),
18938 // 4 = index of Thursday
18939 datetime.getDate() + (4 - datetime.getDay()));
18942 function weekGetter(size) {
18943 return function(date) {
18944 var firstThurs = getFirstThursdayOfYear(date.getFullYear()),
18945 thisThurs = getThursdayThisWeek(date);
18947 var diff = +thisThurs - +firstThurs,
18948 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week
18950 return padNumber(result, size);
18954 function ampmGetter(date, formats) {
18955 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
18958 function eraGetter(date, formats) {
18959 return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1];
18962 function longEraGetter(date, formats) {
18963 return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1];
18966 var DATE_FORMATS = {
18967 yyyy: dateGetter('FullYear', 4),
18968 yy: dateGetter('FullYear', 2, 0, true),
18969 y: dateGetter('FullYear', 1),
18970 MMMM: dateStrGetter('Month'),
18971 MMM: dateStrGetter('Month', true),
18972 MM: dateGetter('Month', 2, 1),
18973 M: dateGetter('Month', 1, 1),
18974 dd: dateGetter('Date', 2),
18975 d: dateGetter('Date', 1),
18976 HH: dateGetter('Hours', 2),
18977 H: dateGetter('Hours', 1),
18978 hh: dateGetter('Hours', 2, -12),
18979 h: dateGetter('Hours', 1, -12),
18980 mm: dateGetter('Minutes', 2),
18981 m: dateGetter('Minutes', 1),
18982 ss: dateGetter('Seconds', 2),
18983 s: dateGetter('Seconds', 1),
18984 // while ISO 8601 requires fractions to be prefixed with `.` or `,`
18985 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions
18986 sss: dateGetter('Milliseconds', 3),
18987 EEEE: dateStrGetter('Day'),
18988 EEE: dateStrGetter('Day', true),
18996 GGGG: longEraGetter
18999 var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,
19000 NUMBER_STRING = /^\-?\d+$/;
19008 * Formats `date` to a string based on the requested `format`.
19010 * `format` string can be composed of the following elements:
19012 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
19013 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
19014 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
19015 * * `'MMMM'`: Month in year (January-December)
19016 * * `'MMM'`: Month in year (Jan-Dec)
19017 * * `'MM'`: Month in year, padded (01-12)
19018 * * `'M'`: Month in year (1-12)
19019 * * `'dd'`: Day in month, padded (01-31)
19020 * * `'d'`: Day in month (1-31)
19021 * * `'EEEE'`: Day in Week,(Sunday-Saturday)
19022 * * `'EEE'`: Day in Week, (Sun-Sat)
19023 * * `'HH'`: Hour in day, padded (00-23)
19024 * * `'H'`: Hour in day (0-23)
19025 * * `'hh'`: Hour in AM/PM, padded (01-12)
19026 * * `'h'`: Hour in AM/PM, (1-12)
19027 * * `'mm'`: Minute in hour, padded (00-59)
19028 * * `'m'`: Minute in hour (0-59)
19029 * * `'ss'`: Second in minute, padded (00-59)
19030 * * `'s'`: Second in minute (0-59)
19031 * * `'sss'`: Millisecond in second, padded (000-999)
19032 * * `'a'`: AM/PM marker
19033 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
19034 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year
19035 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year
19036 * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD')
19037 * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini')
19039 * `format` string can also be one of the following predefined
19040 * {@link guide/i18n localizable formats}:
19042 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
19043 * (e.g. Sep 3, 2010 12:05:08 PM)
19044 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM)
19045 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale
19046 * (e.g. Friday, September 3, 2010)
19047 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010)
19048 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010)
19049 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
19050 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM)
19051 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM)
19053 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g.
19054 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence
19055 * (e.g. `"h 'o''clock'"`).
19057 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
19058 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its
19059 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is
19060 * specified in the string input, the time is considered to be in the local timezone.
19061 * @param {string=} format Formatting rules (see Description). If not specified,
19062 * `mediumDate` is used.
19063 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the
19064 * continental US time zone abbreviations, but for general use, use a time zone offset, for
19065 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
19066 * If not specified, the timezone of the browser will be used.
19067 * @returns {string} Formatted string or the input if input is not recognized as date/millis.
19071 <file name="index.html">
19072 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>:
19073 <span>{{1288323623006 | date:'medium'}}</span><br>
19074 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
19075 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br>
19076 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
19077 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br>
19078 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>:
19079 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br>
19081 <file name="protractor.js" type="protractor">
19082 it('should format date', function() {
19083 expect(element(by.binding("1288323623006 | date:'medium'")).getText()).
19084 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
19085 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()).
19086 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/);
19087 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()).
19088 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
19089 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()).
19090 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/);
19095 dateFilter.$inject = ['$locale'];
19096 function dateFilter($locale) {
19099 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
19100 // 1 2 3 4 5 6 7 8 9 10 11
19101 function jsonStringToDate(string) {
19103 if (match = string.match(R_ISO8601_STR)) {
19104 var date = new Date(0),
19107 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear,
19108 timeSetter = match[8] ? date.setUTCHours : date.setHours;
19111 tzHour = toInt(match[9] + match[10]);
19112 tzMin = toInt(match[9] + match[11]);
19114 dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
19115 var h = toInt(match[4] || 0) - tzHour;
19116 var m = toInt(match[5] || 0) - tzMin;
19117 var s = toInt(match[6] || 0);
19118 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
19119 timeSetter.call(date, h, m, s, ms);
19126 return function(date, format, timezone) {
19131 format = format || 'mediumDate';
19132 format = $locale.DATETIME_FORMATS[format] || format;
19133 if (isString(date)) {
19134 date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date);
19137 if (isNumber(date)) {
19138 date = new Date(date);
19141 if (!isDate(date) || !isFinite(date.getTime())) {
19146 match = DATE_FORMATS_SPLIT.exec(format);
19148 parts = concat(parts, match, 1);
19149 format = parts.pop();
19151 parts.push(format);
19156 var dateTimezoneOffset = date.getTimezoneOffset();
19158 dateTimezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
19159 date = convertTimezoneToLocal(date, timezone, true);
19161 forEach(parts, function(value) {
19162 fn = DATE_FORMATS[value];
19163 text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset)
19164 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
19178 * Allows you to convert a JavaScript object into JSON string.
19180 * This filter is mostly useful for debugging. When using the double curly {{value}} notation
19181 * the binding is automatically converted to JSON.
19183 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
19184 * @param {number=} spacing The number of spaces to use per indentation, defaults to 2.
19185 * @returns {string} JSON string.
19190 <file name="index.html">
19191 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre>
19192 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre>
19194 <file name="protractor.js" type="protractor">
19195 it('should jsonify filtered objects', function() {
19196 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
19197 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
19203 function jsonFilter() {
19204 return function(object, spacing) {
19205 if (isUndefined(spacing)) {
19208 return toJson(object, spacing);
19218 * Converts string to lowercase.
19219 * @see angular.lowercase
19221 var lowercaseFilter = valueFn(lowercase);
19229 * Converts string to uppercase.
19230 * @see angular.uppercase
19232 var uppercaseFilter = valueFn(uppercase);
19240 * Creates a new array or string containing only a specified number of elements. The elements
19241 * are taken from either the beginning or the end of the source array, string or number, as specified by
19242 * the value and sign (positive or negative) of `limit`. If a number is used as input, it is
19243 * converted to a string.
19245 * @param {Array|string|number} input Source array, string or number to be limited.
19246 * @param {string|number} limit The length of the returned array or string. If the `limit` number
19247 * is positive, `limit` number of items from the beginning of the source array/string are copied.
19248 * If the number is negative, `limit` number of items from the end of the source array/string
19249 * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined,
19250 * the input will be returned unchanged.
19251 * @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin`
19252 * indicates an offset from the end of `input`. Defaults to `0`.
19253 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
19254 * had less than `limit` elements.
19257 <example module="limitToExample">
19258 <file name="index.html">
19260 angular.module('limitToExample', [])
19261 .controller('ExampleController', ['$scope', function($scope) {
19262 $scope.numbers = [1,2,3,4,5,6,7,8,9];
19263 $scope.letters = "abcdefghi";
19264 $scope.longNumber = 2345432342;
19265 $scope.numLimit = 3;
19266 $scope.letterLimit = 3;
19267 $scope.longNumberLimit = 3;
19270 <div ng-controller="ExampleController">
19272 Limit {{numbers}} to:
19273 <input type="number" step="1" ng-model="numLimit">
19275 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p>
19277 Limit {{letters}} to:
19278 <input type="number" step="1" ng-model="letterLimit">
19280 <p>Output letters: {{ letters | limitTo:letterLimit }}</p>
19282 Limit {{longNumber}} to:
19283 <input type="number" step="1" ng-model="longNumberLimit">
19285 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p>
19288 <file name="protractor.js" type="protractor">
19289 var numLimitInput = element(by.model('numLimit'));
19290 var letterLimitInput = element(by.model('letterLimit'));
19291 var longNumberLimitInput = element(by.model('longNumberLimit'));
19292 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit'));
19293 var limitedLetters = element(by.binding('letters | limitTo:letterLimit'));
19294 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit'));
19296 it('should limit the number array to first three items', function() {
19297 expect(numLimitInput.getAttribute('value')).toBe('3');
19298 expect(letterLimitInput.getAttribute('value')).toBe('3');
19299 expect(longNumberLimitInput.getAttribute('value')).toBe('3');
19300 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]');
19301 expect(limitedLetters.getText()).toEqual('Output letters: abc');
19302 expect(limitedLongNumber.getText()).toEqual('Output long number: 234');
19305 // There is a bug in safari and protractor that doesn't like the minus key
19306 // it('should update the output when -3 is entered', function() {
19307 // numLimitInput.clear();
19308 // numLimitInput.sendKeys('-3');
19309 // letterLimitInput.clear();
19310 // letterLimitInput.sendKeys('-3');
19311 // longNumberLimitInput.clear();
19312 // longNumberLimitInput.sendKeys('-3');
19313 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
19314 // expect(limitedLetters.getText()).toEqual('Output letters: ghi');
19315 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342');
19318 it('should not exceed the maximum size of input array', function() {
19319 numLimitInput.clear();
19320 numLimitInput.sendKeys('100');
19321 letterLimitInput.clear();
19322 letterLimitInput.sendKeys('100');
19323 longNumberLimitInput.clear();
19324 longNumberLimitInput.sendKeys('100');
19325 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]');
19326 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi');
19327 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342');
19332 function limitToFilter() {
19333 return function(input, limit, begin) {
19334 if (Math.abs(Number(limit)) === Infinity) {
19335 limit = Number(limit);
19337 limit = toInt(limit);
19339 if (isNaN(limit)) return input;
19341 if (isNumber(input)) input = input.toString();
19342 if (!isArray(input) && !isString(input)) return input;
19344 begin = (!begin || isNaN(begin)) ? 0 : toInt(begin);
19345 begin = (begin < 0) ? Math.max(0, input.length + begin) : begin;
19348 return input.slice(begin, begin + limit);
19351 return input.slice(limit, input.length);
19353 return input.slice(Math.max(0, begin + limit), begin);
19365 * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically
19366 * for strings and numerically for numbers. Note: if you notice numbers are not being sorted
19367 * as expected, make sure they are actually being saved as numbers and not strings.
19369 * @param {Array} array The array to sort.
19370 * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be
19371 * used by the comparator to determine the order of elements.
19375 * - `function`: Getter function. The result of this function will be sorted using the
19376 * `<`, `===`, `>` operator.
19377 * - `string`: An Angular expression. The result of this expression is used to compare elements
19378 * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by
19379 * 3 first characters of a property called `name`). The result of a constant expression
19380 * is interpreted as a property name to be used in comparisons (for example `"special name"`
19381 * to sort object by the value of their `special name` property). An expression can be
19382 * optionally prefixed with `+` or `-` to control ascending or descending sort order
19383 * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array
19384 * element itself is used to compare where sorting.
19385 * - `Array`: An array of function or string predicates. The first predicate in the array
19386 * is used for sorting, but when two items are equivalent, the next predicate is used.
19388 * If the predicate is missing or empty then it defaults to `'+'`.
19390 * @param {boolean=} reverse Reverse the order of the array.
19391 * @returns {Array} Sorted copy of the source array.
19395 * The example below demonstrates a simple ngRepeat, where the data is sorted
19396 * by age in descending order (predicate is set to `'-age'`).
19397 * `reverse` is not set, which means it defaults to `false`.
19398 <example module="orderByExample">
19399 <file name="index.html">
19401 angular.module('orderByExample', [])
19402 .controller('ExampleController', ['$scope', function($scope) {
19404 [{name:'John', phone:'555-1212', age:10},
19405 {name:'Mary', phone:'555-9876', age:19},
19406 {name:'Mike', phone:'555-4321', age:21},
19407 {name:'Adam', phone:'555-5678', age:35},
19408 {name:'Julie', phone:'555-8765', age:29}];
19411 <div ng-controller="ExampleController">
19412 <table class="friend">
19415 <th>Phone Number</th>
19418 <tr ng-repeat="friend in friends | orderBy:'-age'">
19419 <td>{{friend.name}}</td>
19420 <td>{{friend.phone}}</td>
19421 <td>{{friend.age}}</td>
19428 * The predicate and reverse parameters can be controlled dynamically through scope properties,
19429 * as shown in the next example.
19431 <example module="orderByExample">
19432 <file name="index.html">
19434 angular.module('orderByExample', [])
19435 .controller('ExampleController', ['$scope', function($scope) {
19437 [{name:'John', phone:'555-1212', age:10},
19438 {name:'Mary', phone:'555-9876', age:19},
19439 {name:'Mike', phone:'555-4321', age:21},
19440 {name:'Adam', phone:'555-5678', age:35},
19441 {name:'Julie', phone:'555-8765', age:29}];
19442 $scope.predicate = 'age';
19443 $scope.reverse = true;
19444 $scope.order = function(predicate) {
19445 $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
19446 $scope.predicate = predicate;
19450 <style type="text/css">
19454 .sortorder.reverse:after {
19458 <div ng-controller="ExampleController">
19459 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
19461 [ <a href="" ng-click="predicate=''">unsorted</a> ]
19462 <table class="friend">
19465 <a href="" ng-click="order('name')">Name</a>
19466 <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
19469 <a href="" ng-click="order('phone')">Phone Number</a>
19470 <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span>
19473 <a href="" ng-click="order('age')">Age</a>
19474 <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span>
19477 <tr ng-repeat="friend in friends | orderBy:predicate:reverse">
19478 <td>{{friend.name}}</td>
19479 <td>{{friend.phone}}</td>
19480 <td>{{friend.age}}</td>
19487 * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the
19488 * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the
19489 * desired parameters.
19494 <example module="orderByExample">
19495 <file name="index.html">
19496 <div ng-controller="ExampleController">
19497 <table class="friend">
19499 <th><a href="" ng-click="reverse=false;order('name', false)">Name</a>
19500 (<a href="" ng-click="order('-name',false)">^</a>)</th>
19501 <th><a href="" ng-click="reverse=!reverse;order('phone', reverse)">Phone Number</a></th>
19502 <th><a href="" ng-click="reverse=!reverse;order('age',reverse)">Age</a></th>
19504 <tr ng-repeat="friend in friends">
19505 <td>{{friend.name}}</td>
19506 <td>{{friend.phone}}</td>
19507 <td>{{friend.age}}</td>
19513 <file name="script.js">
19514 angular.module('orderByExample', [])
19515 .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) {
19516 var orderBy = $filter('orderBy');
19518 { name: 'John', phone: '555-1212', age: 10 },
19519 { name: 'Mary', phone: '555-9876', age: 19 },
19520 { name: 'Mike', phone: '555-4321', age: 21 },
19521 { name: 'Adam', phone: '555-5678', age: 35 },
19522 { name: 'Julie', phone: '555-8765', age: 29 }
19524 $scope.order = function(predicate, reverse) {
19525 $scope.friends = orderBy($scope.friends, predicate, reverse);
19527 $scope.order('-age',false);
19532 orderByFilter.$inject = ['$parse'];
19533 function orderByFilter($parse) {
19534 return function(array, sortPredicate, reverseOrder) {
19536 if (!(isArrayLike(array))) return array;
19538 if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; }
19539 if (sortPredicate.length === 0) { sortPredicate = ['+']; }
19541 var predicates = processPredicates(sortPredicate, reverseOrder);
19542 // Add a predicate at the end that evaluates to the element index. This makes the
19543 // sort stable as it works as a tie-breaker when all the input predicates cannot
19544 // distinguish between two elements.
19545 predicates.push({ get: function() { return {}; }, descending: reverseOrder ? -1 : 1});
19547 // The next three lines are a version of a Swartzian Transform idiom from Perl
19548 // (sometimes called the Decorate-Sort-Undecorate idiom)
19549 // See https://en.wikipedia.org/wiki/Schwartzian_transform
19550 var compareValues = Array.prototype.map.call(array, getComparisonObject);
19551 compareValues.sort(doComparison);
19552 array = compareValues.map(function(item) { return item.value; });
19556 function getComparisonObject(value, index) {
19559 predicateValues: predicates.map(function(predicate) {
19560 return getPredicateValue(predicate.get(value), index);
19565 function doComparison(v1, v2) {
19567 for (var index=0, length = predicates.length; index < length; ++index) {
19568 result = compare(v1.predicateValues[index], v2.predicateValues[index]) * predicates[index].descending;
19575 function processPredicates(sortPredicate, reverseOrder) {
19576 reverseOrder = reverseOrder ? -1 : 1;
19577 return sortPredicate.map(function(predicate) {
19578 var descending = 1, get = identity;
19580 if (isFunction(predicate)) {
19582 } else if (isString(predicate)) {
19583 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) {
19584 descending = predicate.charAt(0) == '-' ? -1 : 1;
19585 predicate = predicate.substring(1);
19587 if (predicate !== '') {
19588 get = $parse(predicate);
19589 if (get.constant) {
19591 get = function(value) { return value[key]; };
19595 return { get: get, descending: descending * reverseOrder };
19599 function isPrimitive(value) {
19600 switch (typeof value) {
19601 case 'number': /* falls through */
19602 case 'boolean': /* falls through */
19610 function objectValue(value, index) {
19611 // If `valueOf` is a valid function use that
19612 if (typeof value.valueOf === 'function') {
19613 value = value.valueOf();
19614 if (isPrimitive(value)) return value;
19616 // If `toString` is a valid function and not the one from `Object.prototype` use that
19617 if (hasCustomToString(value)) {
19618 value = value.toString();
19619 if (isPrimitive(value)) return value;
19621 // We have a basic object so we use the position of the object in the collection
19625 function getPredicateValue(value, index) {
19626 var type = typeof value;
19627 if (value === null) {
19630 } else if (type === 'string') {
19631 value = value.toLowerCase();
19632 } else if (type === 'object') {
19633 value = objectValue(value, index);
19635 return { value: value, type: type };
19638 function compare(v1, v2) {
19640 if (v1.type === v2.type) {
19641 if (v1.value !== v2.value) {
19642 result = v1.value < v2.value ? -1 : 1;
19645 result = v1.type < v2.type ? -1 : 1;
19651 function ngDirective(directive) {
19652 if (isFunction(directive)) {
19657 directive.restrict = directive.restrict || 'AC';
19658 return valueFn(directive);
19667 * Modifies the default behavior of the html A tag so that the default action is prevented when
19668 * the href attribute is empty.
19670 * This change permits the easy creation of action links with the `ngClick` directive
19671 * without changing the location or causing page reloads, e.g.:
19672 * `<a href="" ng-click="list.addItem()">Add Item</a>`
19674 var htmlAnchorDirective = valueFn({
19676 compile: function(element, attr) {
19677 if (!attr.href && !attr.xlinkHref) {
19678 return function(scope, element) {
19679 // If the linked element is not an anchor tag anymore, do nothing
19680 if (element[0].nodeName.toLowerCase() !== 'a') return;
19682 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
19683 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
19684 'xlink:href' : 'href';
19685 element.on('click', function(event) {
19686 // if we have no href url, then don't navigate anywhere.
19687 if (!element.attr(href)) {
19688 event.preventDefault();
19703 * Using Angular markup like `{{hash}}` in an href attribute will
19704 * make the link go to the wrong URL if the user clicks it before
19705 * Angular has a chance to replace the `{{hash}}` markup with its
19706 * value. Until Angular replaces the markup the link will be broken
19707 * and will most likely return a 404 error. The `ngHref` directive
19708 * solves this problem.
19710 * The wrong way to write it:
19712 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
19715 * The correct way to write it:
19717 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
19721 * @param {template} ngHref any string which can contain `{{}}` markup.
19724 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes
19725 * in links and their different behaviors:
19727 <file name="index.html">
19728 <input ng-model="value" /><br />
19729 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br />
19730 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br />
19731 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br />
19732 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br />
19733 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br />
19734 <a id="link-6" ng-href="{{value}}">link</a> (link, change location)
19736 <file name="protractor.js" type="protractor">
19737 it('should execute ng-click but not reload when href without value', function() {
19738 element(by.id('link-1')).click();
19739 expect(element(by.model('value')).getAttribute('value')).toEqual('1');
19740 expect(element(by.id('link-1')).getAttribute('href')).toBe('');
19743 it('should execute ng-click but not reload when href empty string', function() {
19744 element(by.id('link-2')).click();
19745 expect(element(by.model('value')).getAttribute('value')).toEqual('2');
19746 expect(element(by.id('link-2')).getAttribute('href')).toBe('');
19749 it('should execute ng-click and change url when ng-href specified', function() {
19750 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/);
19752 element(by.id('link-3')).click();
19754 // At this point, we navigate away from an Angular page, so we need
19755 // to use browser.driver to get the base webdriver.
19757 browser.wait(function() {
19758 return browser.driver.getCurrentUrl().then(function(url) {
19759 return url.match(/\/123$/);
19761 }, 5000, 'page should navigate to /123');
19764 it('should execute ng-click but not reload when href empty string and name specified', function() {
19765 element(by.id('link-4')).click();
19766 expect(element(by.model('value')).getAttribute('value')).toEqual('4');
19767 expect(element(by.id('link-4')).getAttribute('href')).toBe('');
19770 it('should execute ng-click but not reload when no href but name specified', function() {
19771 element(by.id('link-5')).click();
19772 expect(element(by.model('value')).getAttribute('value')).toEqual('5');
19773 expect(element(by.id('link-5')).getAttribute('href')).toBe(null);
19776 it('should only change url when only ng-href', function() {
19777 element(by.model('value')).clear();
19778 element(by.model('value')).sendKeys('6');
19779 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/);
19781 element(by.id('link-6')).click();
19783 // At this point, we navigate away from an Angular page, so we need
19784 // to use browser.driver to get the base webdriver.
19785 browser.wait(function() {
19786 return browser.driver.getCurrentUrl().then(function(url) {
19787 return url.match(/\/6$/);
19789 }, 5000, 'page should navigate to /6');
19802 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't
19803 * work right: The browser will fetch from the URL with the literal
19804 * text `{{hash}}` until Angular replaces the expression inside
19805 * `{{hash}}`. The `ngSrc` directive solves this problem.
19807 * The buggy way to write it:
19809 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/>
19812 * The correct way to write it:
19814 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" />
19818 * @param {template} ngSrc any string which can contain `{{}}` markup.
19828 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't
19829 * work right: The browser will fetch from the URL with the literal
19830 * text `{{hash}}` until Angular replaces the expression inside
19831 * `{{hash}}`. The `ngSrcset` directive solves this problem.
19833 * The buggy way to write it:
19835 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/>
19838 * The correct way to write it:
19840 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" />
19844 * @param {template} ngSrcset any string which can contain `{{}}` markup.
19855 * This directive sets the `disabled` attribute on the element if the
19856 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy.
19858 * A special directive is necessary because we cannot use interpolation inside the `disabled`
19859 * attribute. The following example would make the button enabled on Chrome/Firefox
19860 * but not on older IEs:
19863 * <!-- See below for an example of ng-disabled being used correctly -->
19864 * <div ng-init="isDisabled = false">
19865 * <button disabled="{{isDisabled}}">Disabled</button>
19869 * This is because the HTML specification does not require browsers to preserve the values of
19870 * boolean attributes such as `disabled` (Their presence means true and their absence means false.)
19871 * If we put an Angular interpolation expression into such an attribute then the
19872 * binding information would be lost when the browser removes the attribute.
19876 <file name="index.html">
19877 <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/>
19878 <button ng-model="button" ng-disabled="checked">Button</button>
19880 <file name="protractor.js" type="protractor">
19881 it('should toggle button', function() {
19882 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
19883 element(by.model('checked')).click();
19884 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy();
19890 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
19891 * then the `disabled` attribute will be set on the element
19902 * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy.
19904 * Note that this directive should not be used together with {@link ngModel `ngModel`},
19905 * as this can lead to unexpected behavior.
19907 * ### Why do we need `ngChecked`?
19909 * The HTML specification does not require browsers to preserve the values of boolean attributes
19910 * such as checked. (Their presence means true and their absence means false.)
19911 * If we put an Angular interpolation expression into such an attribute then the
19912 * binding information would be lost when the browser removes the attribute.
19913 * The `ngChecked` directive solves this problem for the `checked` attribute.
19914 * This complementary directive is not removed by the browser and so provides
19915 * a permanent reliable place to store the binding information.
19918 <file name="index.html">
19919 <label>Check me to check both: <input type="checkbox" ng-model="master"></label><br/>
19920 <input id="checkSlave" type="checkbox" ng-checked="master" aria-label="Slave input">
19922 <file name="protractor.js" type="protractor">
19923 it('should check both checkBoxes', function() {
19924 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy();
19925 element(by.model('master')).click();
19926 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy();
19932 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy,
19933 * then the `checked` attribute will be set on the element
19944 * The HTML specification does not require browsers to preserve the values of boolean attributes
19945 * such as readonly. (Their presence means true and their absence means false.)
19946 * If we put an Angular interpolation expression into such an attribute then the
19947 * binding information would be lost when the browser removes the attribute.
19948 * The `ngReadonly` directive solves this problem for the `readonly` attribute.
19949 * This complementary directive is not removed by the browser and so provides
19950 * a permanent reliable place to store the binding information.
19953 <file name="index.html">
19954 <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/>
19955 <input type="text" ng-readonly="checked" value="I'm Angular" aria-label="Readonly field" />
19957 <file name="protractor.js" type="protractor">
19958 it('should toggle readonly attr', function() {
19959 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy();
19960 element(by.model('checked')).click();
19961 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy();
19967 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy,
19968 * then special attribute "readonly" will be set on the element
19979 * The HTML specification does not require browsers to preserve the values of boolean attributes
19980 * such as selected. (Their presence means true and their absence means false.)
19981 * If we put an Angular interpolation expression into such an attribute then the
19982 * binding information would be lost when the browser removes the attribute.
19983 * The `ngSelected` directive solves this problem for the `selected` attribute.
19984 * This complementary directive is not removed by the browser and so provides
19985 * a permanent reliable place to store the binding information.
19989 <file name="index.html">
19990 <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/>
19991 <select aria-label="ngSelected demo">
19992 <option>Hello!</option>
19993 <option id="greet" ng-selected="selected">Greetings!</option>
19996 <file name="protractor.js" type="protractor">
19997 it('should select Greetings!', function() {
19998 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
19999 element(by.model('selected')).click();
20000 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy();
20006 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy,
20007 * then special attribute "selected" will be set on the element
20017 * The HTML specification does not require browsers to preserve the values of boolean attributes
20018 * such as open. (Their presence means true and their absence means false.)
20019 * If we put an Angular interpolation expression into such an attribute then the
20020 * binding information would be lost when the browser removes the attribute.
20021 * The `ngOpen` directive solves this problem for the `open` attribute.
20022 * This complementary directive is not removed by the browser and so provides
20023 * a permanent reliable place to store the binding information.
20026 <file name="index.html">
20027 <label>Check me check multiple: <input type="checkbox" ng-model="open"></label><br/>
20028 <details id="details" ng-open="open">
20029 <summary>Show/Hide me</summary>
20032 <file name="protractor.js" type="protractor">
20033 it('should toggle open', function() {
20034 expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
20035 element(by.model('open')).click();
20036 expect(element(by.id('details')).getAttribute('open')).toBeTruthy();
20042 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy,
20043 * then special attribute "open" will be set on the element
20046 var ngAttributeAliasDirectives = {};
20048 // boolean attrs are evaluated
20049 forEach(BOOLEAN_ATTR, function(propName, attrName) {
20050 // binding to multiple is not supported
20051 if (propName == "multiple") return;
20053 function defaultLinkFn(scope, element, attr) {
20054 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
20055 attr.$set(attrName, !!value);
20059 var normalized = directiveNormalize('ng-' + attrName);
20060 var linkFn = defaultLinkFn;
20062 if (propName === 'checked') {
20063 linkFn = function(scope, element, attr) {
20064 // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input
20065 if (attr.ngModel !== attr[normalized]) {
20066 defaultLinkFn(scope, element, attr);
20071 ngAttributeAliasDirectives[normalized] = function() {
20080 // aliased input attrs are evaluated
20081 forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) {
20082 ngAttributeAliasDirectives[ngAttr] = function() {
20085 link: function(scope, element, attr) {
20086 //special case ngPattern when a literal regular expression value
20087 //is used as the expression (this way we don't have to watch anything).
20088 if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") {
20089 var match = attr.ngPattern.match(REGEX_STRING_REGEXP);
20091 attr.$set("ngPattern", new RegExp(match[1], match[2]));
20096 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) {
20097 attr.$set(ngAttr, value);
20104 // ng-src, ng-srcset, ng-href are interpolated
20105 forEach(['src', 'srcset', 'href'], function(attrName) {
20106 var normalized = directiveNormalize('ng-' + attrName);
20107 ngAttributeAliasDirectives[normalized] = function() {
20109 priority: 99, // it needs to run after the attributes are interpolated
20110 link: function(scope, element, attr) {
20111 var propName = attrName,
20114 if (attrName === 'href' &&
20115 toString.call(element.prop('href')) === '[object SVGAnimatedString]') {
20116 name = 'xlinkHref';
20117 attr.$attr[name] = 'xlink:href';
20121 attr.$observe(normalized, function(value) {
20123 if (attrName === 'href') {
20124 attr.$set(name, null);
20129 attr.$set(name, value);
20131 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
20132 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
20133 // to set the property as well to achieve the desired effect.
20134 // we use attr[attrName] value since $set can sanitize the url.
20135 if (msie && propName) element.prop(propName, attr[name]);
20142 /* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true
20144 var nullFormCtrl = {
20146 $$renameControl: nullFormRenameControl,
20147 $removeControl: noop,
20148 $setValidity: noop,
20150 $setPristine: noop,
20151 $setSubmitted: noop
20153 SUBMITTED_CLASS = 'ng-submitted';
20155 function nullFormRenameControl(control, name) {
20156 control.$name = name;
20161 * @name form.FormController
20163 * @property {boolean} $pristine True if user has not interacted with the form yet.
20164 * @property {boolean} $dirty True if user has already interacted with the form.
20165 * @property {boolean} $valid True if all of the containing forms and controls are valid.
20166 * @property {boolean} $invalid True if at least one containing control or form is invalid.
20167 * @property {boolean} $pending True if at least one containing control or form is pending.
20168 * @property {boolean} $submitted True if user has submitted the form even if its invalid.
20170 * @property {Object} $error Is an object hash, containing references to controls or
20171 * forms with failing validators, where:
20173 * - keys are validation tokens (error names),
20174 * - values are arrays of controls or forms that have a failing validator for given error name.
20176 * Built-in validation tokens:
20188 * - `datetimelocal`
20194 * `FormController` keeps track of all its controls and nested forms as well as the state of them,
20195 * such as being valid/invalid or dirty/pristine.
20197 * Each {@link ng.directive:form form} directive creates an instance
20198 * of `FormController`.
20201 //asks for $scope to fool the BC controller module
20202 FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate'];
20203 function FormController(element, attrs, $scope, $animate, $interpolate) {
20209 form.$$success = {};
20210 form.$pending = undefined;
20211 form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope);
20212 form.$dirty = false;
20213 form.$pristine = true;
20214 form.$valid = true;
20215 form.$invalid = false;
20216 form.$submitted = false;
20217 form.$$parentForm = nullFormCtrl;
20221 * @name form.FormController#$rollbackViewValue
20224 * Rollback all form controls pending updates to the `$modelValue`.
20226 * Updates may be pending by a debounced event or because the input is waiting for a some future
20227 * event defined in `ng-model-options`. This method is typically needed by the reset button of
20228 * a form that uses `ng-model-options` to pend updates.
20230 form.$rollbackViewValue = function() {
20231 forEach(controls, function(control) {
20232 control.$rollbackViewValue();
20238 * @name form.FormController#$commitViewValue
20241 * Commit all form controls pending updates to the `$modelValue`.
20243 * Updates may be pending by a debounced event or because the input is waiting for a some future
20244 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController`
20245 * usually handles calling this in response to input events.
20247 form.$commitViewValue = function() {
20248 forEach(controls, function(control) {
20249 control.$commitViewValue();
20255 * @name form.FormController#$addControl
20256 * @param {object} control control object, either a {@link form.FormController} or an
20257 * {@link ngModel.NgModelController}
20260 * Register a control with the form. Input elements using ngModelController do this automatically
20261 * when they are linked.
20263 * Note that the current state of the control will not be reflected on the new parent form. This
20264 * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine`
20267 * However, if the method is used programmatically, for example by adding dynamically created controls,
20268 * or controls that have been previously removed without destroying their corresponding DOM element,
20269 * it's the developers responsiblity to make sure the current state propagates to the parent form.
20271 * For example, if an input control is added that is already `$dirty` and has `$error` properties,
20272 * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form.
20274 form.$addControl = function(control) {
20275 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored
20276 // and not added to the scope. Now we throw an error.
20277 assertNotHasOwnProperty(control.$name, 'input');
20278 controls.push(control);
20280 if (control.$name) {
20281 form[control.$name] = control;
20284 control.$$parentForm = form;
20287 // Private API: rename a form control
20288 form.$$renameControl = function(control, newName) {
20289 var oldName = control.$name;
20291 if (form[oldName] === control) {
20292 delete form[oldName];
20294 form[newName] = control;
20295 control.$name = newName;
20300 * @name form.FormController#$removeControl
20301 * @param {object} control control object, either a {@link form.FormController} or an
20302 * {@link ngModel.NgModelController}
20305 * Deregister a control from the form.
20307 * Input elements using ngModelController do this automatically when they are destroyed.
20309 * Note that only the removed control's validation state (`$errors`etc.) will be removed from the
20310 * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be
20311 * different from case to case. For example, removing the only `$dirty` control from a form may or
20312 * may not mean that the form is still `$dirty`.
20314 form.$removeControl = function(control) {
20315 if (control.$name && form[control.$name] === control) {
20316 delete form[control.$name];
20318 forEach(form.$pending, function(value, name) {
20319 form.$setValidity(name, null, control);
20321 forEach(form.$error, function(value, name) {
20322 form.$setValidity(name, null, control);
20324 forEach(form.$$success, function(value, name) {
20325 form.$setValidity(name, null, control);
20328 arrayRemove(controls, control);
20329 control.$$parentForm = nullFormCtrl;
20335 * @name form.FormController#$setValidity
20338 * Sets the validity of a form control.
20340 * This method will also propagate to parent forms.
20342 addSetValidityMethod({
20345 set: function(object, property, controller) {
20346 var list = object[property];
20348 object[property] = [controller];
20350 var index = list.indexOf(controller);
20351 if (index === -1) {
20352 list.push(controller);
20356 unset: function(object, property, controller) {
20357 var list = object[property];
20361 arrayRemove(list, controller);
20362 if (list.length === 0) {
20363 delete object[property];
20371 * @name form.FormController#$setDirty
20374 * Sets the form to a dirty state.
20376 * This method can be called to add the 'ng-dirty' class and set the form to a dirty
20377 * state (ng-dirty class). This method will also propagate to parent forms.
20379 form.$setDirty = function() {
20380 $animate.removeClass(element, PRISTINE_CLASS);
20381 $animate.addClass(element, DIRTY_CLASS);
20382 form.$dirty = true;
20383 form.$pristine = false;
20384 form.$$parentForm.$setDirty();
20389 * @name form.FormController#$setPristine
20392 * Sets the form to its pristine state.
20394 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine
20395 * state (ng-pristine class). This method will also propagate to all the controls contained
20398 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
20399 * saving or resetting it.
20401 form.$setPristine = function() {
20402 $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS);
20403 form.$dirty = false;
20404 form.$pristine = true;
20405 form.$submitted = false;
20406 forEach(controls, function(control) {
20407 control.$setPristine();
20413 * @name form.FormController#$setUntouched
20416 * Sets the form to its untouched state.
20418 * This method can be called to remove the 'ng-touched' class and set the form controls to their
20419 * untouched state (ng-untouched class).
20421 * Setting a form controls back to their untouched state is often useful when setting the form
20422 * back to its pristine state.
20424 form.$setUntouched = function() {
20425 forEach(controls, function(control) {
20426 control.$setUntouched();
20432 * @name form.FormController#$setSubmitted
20435 * Sets the form to its submitted state.
20437 form.$setSubmitted = function() {
20438 $animate.addClass(element, SUBMITTED_CLASS);
20439 form.$submitted = true;
20440 form.$$parentForm.$setSubmitted();
20450 * Nestable alias of {@link ng.directive:form `form`} directive. HTML
20451 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
20452 * sub-group of controls needs to be determined.
20454 * Note: the purpose of `ngForm` is to group controls,
20455 * but not to be a replacement for the `<form>` tag with all of its capabilities
20456 * (e.g. posting to the server, ...).
20458 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into
20459 * related scope, under this name.
20469 * Directive that instantiates
20470 * {@link form.FormController FormController}.
20472 * If the `name` attribute is specified, the form controller is published onto the current scope under
20475 * # Alias: {@link ng.directive:ngForm `ngForm`}
20477 * In Angular, forms can be nested. This means that the outer form is valid when all of the child
20478 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so
20479 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to
20480 * `<form>` but can be nested. This allows you to have nested forms, which is very useful when
20481 * using Angular validation directives in forms that are dynamically generated using the
20482 * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name`
20483 * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an
20484 * `ngForm` directive and nest these in an outer `form` element.
20488 * - `ng-valid` is set if the form is valid.
20489 * - `ng-invalid` is set if the form is invalid.
20490 * - `ng-pending` is set if the form is pending.
20491 * - `ng-pristine` is set if the form is pristine.
20492 * - `ng-dirty` is set if the form is dirty.
20493 * - `ng-submitted` is set if the form was submitted.
20495 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
20498 * # Submitting a form and preventing the default action
20500 * Since the role of forms in client-side Angular applications is different than in classical
20501 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full
20502 * page reload that sends the data to the server. Instead some javascript logic should be triggered
20503 * to handle the form submission in an application-specific way.
20505 * For this reason, Angular prevents the default action (form submission to the server) unless the
20506 * `<form>` element has an `action` attribute specified.
20508 * You can use one of the following two ways to specify what javascript method should be called when
20509 * a form is submitted:
20511 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element
20512 * - {@link ng.directive:ngClick ngClick} directive on the first
20513 * button or input field of type submit (input[type=submit])
20515 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit}
20516 * or {@link ng.directive:ngClick ngClick} directives.
20517 * This is because of the following form submission rules in the HTML specification:
20519 * - If a form has only one input field then hitting enter in this field triggers form submit
20521 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter
20522 * doesn't trigger submit
20523 * - if a form has one or more input fields and one or more buttons or input[type=submit] then
20524 * hitting enter in any of the input fields will trigger the click handler on the *first* button or
20525 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
20527 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is
20528 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
20529 * to have access to the updated model.
20531 * ## Animation Hooks
20533 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed.
20534 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any
20535 * other validations that are performed within the form. Animations in ngForm are similar to how
20536 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well
20537 * as JS animations.
20539 * The following example shows a simple way to utilize CSS transitions to style a form element
20540 * that has been rendered as invalid after it has been validated:
20543 * //be sure to include ngAnimate as a module to hook into more
20544 * //advanced animations
20546 * transition:0.5s linear all;
20547 * background: white;
20549 * .my-form.ng-invalid {
20556 <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample">
20557 <file name="index.html">
20559 angular.module('formExample', [])
20560 .controller('FormController', ['$scope', function($scope) {
20561 $scope.userType = 'guest';
20566 transition:all linear 0.5s;
20567 background: transparent;
20569 .my-form.ng-invalid {
20573 <form name="myForm" ng-controller="FormController" class="my-form">
20574 userType: <input name="input" ng-model="userType" required>
20575 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br>
20576 <code>userType = {{userType}}</code><br>
20577 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br>
20578 <code>myForm.input.$error = {{myForm.input.$error}}</code><br>
20579 <code>myForm.$valid = {{myForm.$valid}}</code><br>
20580 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br>
20583 <file name="protractor.js" type="protractor">
20584 it('should initialize to model', function() {
20585 var userType = element(by.binding('userType'));
20586 var valid = element(by.binding('myForm.input.$valid'));
20588 expect(userType.getText()).toContain('guest');
20589 expect(valid.getText()).toContain('true');
20592 it('should be invalid if empty', function() {
20593 var userType = element(by.binding('userType'));
20594 var valid = element(by.binding('myForm.input.$valid'));
20595 var userInput = element(by.model('userType'));
20598 userInput.sendKeys('');
20600 expect(userType.getText()).toEqual('userType =');
20601 expect(valid.getText()).toContain('false');
20606 * @param {string=} name Name of the form. If specified, the form controller will be published into
20607 * related scope, under this name.
20609 var formDirectiveFactory = function(isNgForm) {
20610 return ['$timeout', '$parse', function($timeout, $parse) {
20611 var formDirective = {
20613 restrict: isNgForm ? 'EAC' : 'E',
20614 require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form
20615 controller: FormController,
20616 compile: function ngFormCompile(formElement, attr) {
20617 // Setup initial state of the control
20618 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);
20620 var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false);
20623 pre: function ngFormPreLink(scope, formElement, attr, ctrls) {
20624 var controller = ctrls[0];
20626 // if `action` attr is not present on the form, prevent the default action (submission)
20627 if (!('action' in attr)) {
20628 // we can't use jq events because if a form is destroyed during submission the default
20629 // action is not prevented. see #1238
20631 // IE 9 is not affected because it doesn't fire a submit event and try to do a full
20632 // page reload if the form was destroyed by submission of the form via a click handler
20633 // on a button in the form. Looks like an IE9 specific bug.
20634 var handleFormSubmission = function(event) {
20635 scope.$apply(function() {
20636 controller.$commitViewValue();
20637 controller.$setSubmitted();
20640 event.preventDefault();
20643 addEventListenerFn(formElement[0], 'submit', handleFormSubmission);
20645 // unregister the preventDefault listener so that we don't not leak memory but in a
20646 // way that will achieve the prevention of the default action.
20647 formElement.on('$destroy', function() {
20648 $timeout(function() {
20649 removeEventListenerFn(formElement[0], 'submit', handleFormSubmission);
20654 var parentFormCtrl = ctrls[1] || controller.$$parentForm;
20655 parentFormCtrl.$addControl(controller);
20657 var setter = nameAttr ? getSetter(controller.$name) : noop;
20660 setter(scope, controller);
20661 attr.$observe(nameAttr, function(newValue) {
20662 if (controller.$name === newValue) return;
20663 setter(scope, undefined);
20664 controller.$$parentForm.$$renameControl(controller, newValue);
20665 setter = getSetter(controller.$name);
20666 setter(scope, controller);
20669 formElement.on('$destroy', function() {
20670 controller.$$parentForm.$removeControl(controller);
20671 setter(scope, undefined);
20672 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
20679 return formDirective;
20681 function getSetter(expression) {
20682 if (expression === '') {
20683 //create an assignable expression, so forms with an empty name can be renamed later
20684 return $parse('this[""]').assign;
20686 return $parse(expression).assign || noop;
20691 var formDirective = formDirectiveFactory();
20692 var ngFormDirective = formDirectiveFactory(true);
20694 /* global VALID_CLASS: false,
20695 INVALID_CLASS: false,
20696 PRISTINE_CLASS: false,
20697 DIRTY_CLASS: false,
20698 UNTOUCHED_CLASS: false,
20699 TOUCHED_CLASS: false,
20700 ngModelMinErr: false,
20703 // Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231
20704 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)/;
20705 // See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987)
20706 var URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.?+=&%@\-/]*)?$/;
20707 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;
20708 var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/;
20709 var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/;
20710 var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
20711 var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/;
20712 var MONTH_REGEXP = /^(\d{4})-(\d\d)$/;
20713 var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
20719 * @name input[text]
20722 * Standard HTML text input with angular data binding, inherited by most of the `input` elements.
20725 * @param {string} ngModel Assignable angular expression to data-bind to.
20726 * @param {string=} name Property name of the form under which the control is published.
20727 * @param {string=} required Adds `required` validation error key if the value is not entered.
20728 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20729 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20730 * `required` when you want to data-bind to the `required` attribute.
20731 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
20733 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
20734 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
20736 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
20737 * that contains the regular expression body that will be converted to a regular expression
20738 * as in the ngPattern directive.
20739 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
20740 * a RegExp found by evaluating the Angular expression given in the attribute value.
20741 * If the expression evaluates to a RegExp object, then this is used directly.
20742 * If the expression evaluates to a string, then it will be converted to a RegExp
20743 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
20744 * `new RegExp('^abc$')`.<br />
20745 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
20746 * start at the index of the last search's match, thus not taking the whole input value into
20748 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20749 * interaction with the input element.
20750 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
20751 * This parameter is ignored for input[type=password] controls, which will never trim the
20755 <example name="text-input-directive" module="textInputExample">
20756 <file name="index.html">
20758 angular.module('textInputExample', [])
20759 .controller('ExampleController', ['$scope', function($scope) {
20762 word: /^\s*\w*\s*$/
20766 <form name="myForm" ng-controller="ExampleController">
20767 <label>Single word:
20768 <input type="text" name="input" ng-model="example.text"
20769 ng-pattern="example.word" required ng-trim="false">
20772 <span class="error" ng-show="myForm.input.$error.required">
20774 <span class="error" ng-show="myForm.input.$error.pattern">
20775 Single word only!</span>
20777 <tt>text = {{example.text}}</tt><br/>
20778 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20779 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20780 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20781 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20784 <file name="protractor.js" type="protractor">
20785 var text = element(by.binding('example.text'));
20786 var valid = element(by.binding('myForm.input.$valid'));
20787 var input = element(by.model('example.text'));
20789 it('should initialize to model', function() {
20790 expect(text.getText()).toContain('guest');
20791 expect(valid.getText()).toContain('true');
20794 it('should be invalid if empty', function() {
20796 input.sendKeys('');
20798 expect(text.getText()).toEqual('text =');
20799 expect(valid.getText()).toContain('false');
20802 it('should be invalid if multi word', function() {
20804 input.sendKeys('hello world');
20806 expect(valid.getText()).toContain('false');
20811 'text': textInputType,
20815 * @name input[date]
20818 * Input with date validation and transformation. In browsers that do not yet support
20819 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601
20820 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many
20821 * modern browsers do not yet support this input type, it is important to provide cues to users on the
20822 * expected input format via a placeholder or label.
20824 * The model must always be a Date object, otherwise Angular will throw an error.
20825 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
20827 * The timezone to be used to read/write the `Date` instance in the model can be defined using
20828 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
20830 * @param {string} ngModel Assignable angular expression to data-bind to.
20831 * @param {string=} name Property name of the form under which the control is published.
20832 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
20833 * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
20834 * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5
20835 * constraint validation.
20836 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be
20837 * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
20838 * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5
20839 * constraint validation.
20840 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string
20841 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
20842 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string
20843 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
20844 * @param {string=} required Sets `required` validation error key if the value is not entered.
20845 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20846 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20847 * `required` when you want to data-bind to the `required` attribute.
20848 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20849 * interaction with the input element.
20852 <example name="date-input-directive" module="dateInputExample">
20853 <file name="index.html">
20855 angular.module('dateInputExample', [])
20856 .controller('DateController', ['$scope', function($scope) {
20858 value: new Date(2013, 9, 22)
20862 <form name="myForm" ng-controller="DateController as dateCtrl">
20863 <label for="exampleInput">Pick a date in 2013:</label>
20864 <input type="date" id="exampleInput" name="input" ng-model="example.value"
20865 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required />
20867 <span class="error" ng-show="myForm.input.$error.required">
20869 <span class="error" ng-show="myForm.input.$error.date">
20870 Not a valid date!</span>
20872 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/>
20873 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20874 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20875 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20876 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20879 <file name="protractor.js" type="protractor">
20880 var value = element(by.binding('example.value | date: "yyyy-MM-dd"'));
20881 var valid = element(by.binding('myForm.input.$valid'));
20882 var input = element(by.model('example.value'));
20884 // currently protractor/webdriver does not support
20885 // sending keys to all known HTML5 input controls
20886 // for various browsers (see https://github.com/angular/protractor/issues/562).
20887 function setInput(val) {
20888 // set the value of the element and force validation.
20889 var scr = "var ipt = document.getElementById('exampleInput'); " +
20890 "ipt.value = '" + val + "';" +
20891 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
20892 browser.executeScript(scr);
20895 it('should initialize to model', function() {
20896 expect(value.getText()).toContain('2013-10-22');
20897 expect(valid.getText()).toContain('myForm.input.$valid = true');
20900 it('should be invalid if empty', function() {
20902 expect(value.getText()).toEqual('value =');
20903 expect(valid.getText()).toContain('myForm.input.$valid = false');
20906 it('should be invalid if over max', function() {
20907 setInput('2015-01-01');
20908 expect(value.getText()).toContain('');
20909 expect(valid.getText()).toContain('myForm.input.$valid = false');
20914 'date': createDateInputType('date', DATE_REGEXP,
20915 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']),
20920 * @name input[datetime-local]
20923 * Input with datetime validation and transformation. In browsers that do not yet support
20924 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
20925 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`.
20927 * The model must always be a Date object, otherwise Angular will throw an error.
20928 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
20930 * The timezone to be used to read/write the `Date` instance in the model can be defined using
20931 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
20933 * @param {string} ngModel Assignable angular expression to data-bind to.
20934 * @param {string=} name Property name of the form under which the control is published.
20935 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
20936 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
20937 * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
20938 * Note that `min` will also add native HTML5 constraint validation.
20939 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
20940 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
20941 * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
20942 * Note that `max` will also add native HTML5 constraint validation.
20943 * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string
20944 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
20945 * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string
20946 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
20947 * @param {string=} required Sets `required` validation error key if the value is not entered.
20948 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20949 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20950 * `required` when you want to data-bind to the `required` attribute.
20951 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20952 * interaction with the input element.
20955 <example name="datetimelocal-input-directive" module="dateExample">
20956 <file name="index.html">
20958 angular.module('dateExample', [])
20959 .controller('DateController', ['$scope', function($scope) {
20961 value: new Date(2010, 11, 28, 14, 57)
20965 <form name="myForm" ng-controller="DateController as dateCtrl">
20966 <label for="exampleInput">Pick a date between in 2013:</label>
20967 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value"
20968 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required />
20970 <span class="error" ng-show="myForm.input.$error.required">
20972 <span class="error" ng-show="myForm.input.$error.datetimelocal">
20973 Not a valid date!</span>
20975 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/>
20976 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20977 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20978 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20979 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20982 <file name="protractor.js" type="protractor">
20983 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"'));
20984 var valid = element(by.binding('myForm.input.$valid'));
20985 var input = element(by.model('example.value'));
20987 // currently protractor/webdriver does not support
20988 // sending keys to all known HTML5 input controls
20989 // for various browsers (https://github.com/angular/protractor/issues/562).
20990 function setInput(val) {
20991 // set the value of the element and force validation.
20992 var scr = "var ipt = document.getElementById('exampleInput'); " +
20993 "ipt.value = '" + val + "';" +
20994 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
20995 browser.executeScript(scr);
20998 it('should initialize to model', function() {
20999 expect(value.getText()).toContain('2010-12-28T14:57:00');
21000 expect(valid.getText()).toContain('myForm.input.$valid = true');
21003 it('should be invalid if empty', function() {
21005 expect(value.getText()).toEqual('value =');
21006 expect(valid.getText()).toContain('myForm.input.$valid = false');
21009 it('should be invalid if over max', function() {
21010 setInput('2015-01-01T23:59:00');
21011 expect(value.getText()).toContain('');
21012 expect(valid.getText()).toContain('myForm.input.$valid = false');
21017 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP,
21018 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']),
21019 'yyyy-MM-ddTHH:mm:ss.sss'),
21023 * @name input[time]
21026 * Input with time validation and transformation. In browsers that do not yet support
21027 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
21028 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a
21029 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`.
21031 * The model must always be a Date object, otherwise Angular will throw an error.
21032 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
21034 * The timezone to be used to read/write the `Date` instance in the model can be defined using
21035 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
21037 * @param {string} ngModel Assignable angular expression to data-bind to.
21038 * @param {string=} name Property name of the form under which the control is published.
21039 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21040 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
21041 * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add
21042 * native HTML5 constraint validation.
21043 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21044 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
21045 * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add
21046 * native HTML5 constraint validation.
21047 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the
21048 * `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
21049 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the
21050 * `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
21051 * @param {string=} required Sets `required` validation error key if the value is not entered.
21052 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21053 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21054 * `required` when you want to data-bind to the `required` attribute.
21055 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21056 * interaction with the input element.
21059 <example name="time-input-directive" module="timeExample">
21060 <file name="index.html">
21062 angular.module('timeExample', [])
21063 .controller('DateController', ['$scope', function($scope) {
21065 value: new Date(1970, 0, 1, 14, 57, 0)
21069 <form name="myForm" ng-controller="DateController as dateCtrl">
21070 <label for="exampleInput">Pick a between 8am and 5pm:</label>
21071 <input type="time" id="exampleInput" name="input" ng-model="example.value"
21072 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required />
21074 <span class="error" ng-show="myForm.input.$error.required">
21076 <span class="error" ng-show="myForm.input.$error.time">
21077 Not a valid date!</span>
21079 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/>
21080 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21081 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21082 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21083 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21086 <file name="protractor.js" type="protractor">
21087 var value = element(by.binding('example.value | date: "HH:mm:ss"'));
21088 var valid = element(by.binding('myForm.input.$valid'));
21089 var input = element(by.model('example.value'));
21091 // currently protractor/webdriver does not support
21092 // sending keys to all known HTML5 input controls
21093 // for various browsers (https://github.com/angular/protractor/issues/562).
21094 function setInput(val) {
21095 // set the value of the element and force validation.
21096 var scr = "var ipt = document.getElementById('exampleInput'); " +
21097 "ipt.value = '" + val + "';" +
21098 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21099 browser.executeScript(scr);
21102 it('should initialize to model', function() {
21103 expect(value.getText()).toContain('14:57:00');
21104 expect(valid.getText()).toContain('myForm.input.$valid = true');
21107 it('should be invalid if empty', function() {
21109 expect(value.getText()).toEqual('value =');
21110 expect(valid.getText()).toContain('myForm.input.$valid = false');
21113 it('should be invalid if over max', function() {
21114 setInput('23:59:00');
21115 expect(value.getText()).toContain('');
21116 expect(valid.getText()).toContain('myForm.input.$valid = false');
21121 'time': createDateInputType('time', TIME_REGEXP,
21122 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']),
21127 * @name input[week]
21130 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support
21131 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
21132 * week format (yyyy-W##), for example: `2013-W02`.
21134 * The model must always be a Date object, otherwise Angular will throw an error.
21135 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
21137 * The timezone to be used to read/write the `Date` instance in the model can be defined using
21138 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
21140 * @param {string} ngModel Assignable angular expression to data-bind to.
21141 * @param {string=} name Property name of the form under which the control is published.
21142 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21143 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
21144 * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add
21145 * native HTML5 constraint validation.
21146 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21147 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
21148 * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add
21149 * native HTML5 constraint validation.
21150 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
21151 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
21152 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
21153 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
21154 * @param {string=} required Sets `required` validation error key if the value is not entered.
21155 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21156 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21157 * `required` when you want to data-bind to the `required` attribute.
21158 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21159 * interaction with the input element.
21162 <example name="week-input-directive" module="weekExample">
21163 <file name="index.html">
21165 angular.module('weekExample', [])
21166 .controller('DateController', ['$scope', function($scope) {
21168 value: new Date(2013, 0, 3)
21172 <form name="myForm" ng-controller="DateController as dateCtrl">
21173 <label>Pick a date between in 2013:
21174 <input id="exampleInput" type="week" name="input" ng-model="example.value"
21175 placeholder="YYYY-W##" min="2012-W32"
21176 max="2013-W52" required />
21179 <span class="error" ng-show="myForm.input.$error.required">
21181 <span class="error" ng-show="myForm.input.$error.week">
21182 Not a valid date!</span>
21184 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/>
21185 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21186 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21187 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21188 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21191 <file name="protractor.js" type="protractor">
21192 var value = element(by.binding('example.value | date: "yyyy-Www"'));
21193 var valid = element(by.binding('myForm.input.$valid'));
21194 var input = element(by.model('example.value'));
21196 // currently protractor/webdriver does not support
21197 // sending keys to all known HTML5 input controls
21198 // for various browsers (https://github.com/angular/protractor/issues/562).
21199 function setInput(val) {
21200 // set the value of the element and force validation.
21201 var scr = "var ipt = document.getElementById('exampleInput'); " +
21202 "ipt.value = '" + val + "';" +
21203 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21204 browser.executeScript(scr);
21207 it('should initialize to model', function() {
21208 expect(value.getText()).toContain('2013-W01');
21209 expect(valid.getText()).toContain('myForm.input.$valid = true');
21212 it('should be invalid if empty', function() {
21214 expect(value.getText()).toEqual('value =');
21215 expect(valid.getText()).toContain('myForm.input.$valid = false');
21218 it('should be invalid if over max', function() {
21219 setInput('2015-W01');
21220 expect(value.getText()).toContain('');
21221 expect(valid.getText()).toContain('myForm.input.$valid = false');
21226 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'),
21230 * @name input[month]
21233 * Input with month validation and transformation. In browsers that do not yet support
21234 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
21235 * month format (yyyy-MM), for example: `2009-01`.
21237 * The model must always be a Date object, otherwise Angular will throw an error.
21238 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
21239 * If the model is not set to the first of the month, the next view to model update will set it
21240 * to the first of the month.
21242 * The timezone to be used to read/write the `Date` instance in the model can be defined using
21243 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
21245 * @param {string} ngModel Assignable angular expression to data-bind to.
21246 * @param {string=} name Property name of the form under which the control is published.
21247 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21248 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
21249 * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add
21250 * native HTML5 constraint validation.
21251 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21252 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
21253 * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add
21254 * native HTML5 constraint validation.
21255 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
21256 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
21257 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
21258 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
21260 * @param {string=} required Sets `required` validation error key if the value is not entered.
21261 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21262 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21263 * `required` when you want to data-bind to the `required` attribute.
21264 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21265 * interaction with the input element.
21268 <example name="month-input-directive" module="monthExample">
21269 <file name="index.html">
21271 angular.module('monthExample', [])
21272 .controller('DateController', ['$scope', function($scope) {
21274 value: new Date(2013, 9, 1)
21278 <form name="myForm" ng-controller="DateController as dateCtrl">
21279 <label for="exampleInput">Pick a month in 2013:</label>
21280 <input id="exampleInput" type="month" name="input" ng-model="example.value"
21281 placeholder="yyyy-MM" min="2013-01" max="2013-12" required />
21283 <span class="error" ng-show="myForm.input.$error.required">
21285 <span class="error" ng-show="myForm.input.$error.month">
21286 Not a valid month!</span>
21288 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/>
21289 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21290 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21291 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21292 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21295 <file name="protractor.js" type="protractor">
21296 var value = element(by.binding('example.value | date: "yyyy-MM"'));
21297 var valid = element(by.binding('myForm.input.$valid'));
21298 var input = element(by.model('example.value'));
21300 // currently protractor/webdriver does not support
21301 // sending keys to all known HTML5 input controls
21302 // for various browsers (https://github.com/angular/protractor/issues/562).
21303 function setInput(val) {
21304 // set the value of the element and force validation.
21305 var scr = "var ipt = document.getElementById('exampleInput'); " +
21306 "ipt.value = '" + val + "';" +
21307 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21308 browser.executeScript(scr);
21311 it('should initialize to model', function() {
21312 expect(value.getText()).toContain('2013-10');
21313 expect(valid.getText()).toContain('myForm.input.$valid = true');
21316 it('should be invalid if empty', function() {
21318 expect(value.getText()).toEqual('value =');
21319 expect(valid.getText()).toContain('myForm.input.$valid = false');
21322 it('should be invalid if over max', function() {
21323 setInput('2015-01');
21324 expect(value.getText()).toContain('');
21325 expect(valid.getText()).toContain('myForm.input.$valid = false');
21330 'month': createDateInputType('month', MONTH_REGEXP,
21331 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']),
21336 * @name input[number]
21339 * Text input with number validation and transformation. Sets the `number` validation
21340 * error if not a valid number.
21342 * <div class="alert alert-warning">
21343 * The model must always be of type `number` otherwise Angular will throw an error.
21344 * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt}
21345 * error docs for more information and an example of how to convert your model if necessary.
21348 * ## Issues with HTML5 constraint validation
21350 * In browsers that follow the
21351 * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29),
21352 * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}.
21353 * If a non-number is entered in the input, the browser will report the value as an empty string,
21354 * which means the view / model values in `ngModel` and subsequently the scope value
21355 * will also be an empty string.
21358 * @param {string} ngModel Assignable angular expression to data-bind to.
21359 * @param {string=} name Property name of the form under which the control is published.
21360 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21361 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21362 * @param {string=} required Sets `required` validation error key if the value is not entered.
21363 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21364 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21365 * `required` when you want to data-bind to the `required` attribute.
21366 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21368 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21369 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21371 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21372 * that contains the regular expression body that will be converted to a regular expression
21373 * as in the ngPattern directive.
21374 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21375 * a RegExp found by evaluating the Angular expression given in the attribute value.
21376 * If the expression evaluates to a RegExp object, then this is used directly.
21377 * If the expression evaluates to a string, then it will be converted to a RegExp
21378 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21379 * `new RegExp('^abc$')`.<br />
21380 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21381 * start at the index of the last search's match, thus not taking the whole input value into
21383 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21384 * interaction with the input element.
21387 <example name="number-input-directive" module="numberExample">
21388 <file name="index.html">
21390 angular.module('numberExample', [])
21391 .controller('ExampleController', ['$scope', function($scope) {
21397 <form name="myForm" ng-controller="ExampleController">
21399 <input type="number" name="input" ng-model="example.value"
21400 min="0" max="99" required>
21403 <span class="error" ng-show="myForm.input.$error.required">
21405 <span class="error" ng-show="myForm.input.$error.number">
21406 Not valid number!</span>
21408 <tt>value = {{example.value}}</tt><br/>
21409 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21410 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21411 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21412 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21415 <file name="protractor.js" type="protractor">
21416 var value = element(by.binding('example.value'));
21417 var valid = element(by.binding('myForm.input.$valid'));
21418 var input = element(by.model('example.value'));
21420 it('should initialize to model', function() {
21421 expect(value.getText()).toContain('12');
21422 expect(valid.getText()).toContain('true');
21425 it('should be invalid if empty', function() {
21427 input.sendKeys('');
21428 expect(value.getText()).toEqual('value =');
21429 expect(valid.getText()).toContain('false');
21432 it('should be invalid if over max', function() {
21434 input.sendKeys('123');
21435 expect(value.getText()).toEqual('value =');
21436 expect(valid.getText()).toContain('false');
21441 'number': numberInputType,
21449 * Text input with URL validation. Sets the `url` validation error key if the content is not a
21452 * <div class="alert alert-warning">
21453 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex
21454 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify
21455 * the built-in validators (see the {@link guide/forms Forms guide})
21458 * @param {string} ngModel Assignable angular expression to data-bind to.
21459 * @param {string=} name Property name of the form under which the control is published.
21460 * @param {string=} required Sets `required` validation error key if the value is not entered.
21461 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21462 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21463 * `required` when you want to data-bind to the `required` attribute.
21464 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21466 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21467 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21469 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21470 * that contains the regular expression body that will be converted to a regular expression
21471 * as in the ngPattern directive.
21472 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21473 * a RegExp found by evaluating the Angular expression given in the attribute value.
21474 * If the expression evaluates to a RegExp object, then this is used directly.
21475 * If the expression evaluates to a string, then it will be converted to a RegExp
21476 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21477 * `new RegExp('^abc$')`.<br />
21478 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21479 * start at the index of the last search's match, thus not taking the whole input value into
21481 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21482 * interaction with the input element.
21485 <example name="url-input-directive" module="urlExample">
21486 <file name="index.html">
21488 angular.module('urlExample', [])
21489 .controller('ExampleController', ['$scope', function($scope) {
21491 text: 'http://google.com'
21495 <form name="myForm" ng-controller="ExampleController">
21497 <input type="url" name="input" ng-model="url.text" required>
21500 <span class="error" ng-show="myForm.input.$error.required">
21502 <span class="error" ng-show="myForm.input.$error.url">
21503 Not valid url!</span>
21505 <tt>text = {{url.text}}</tt><br/>
21506 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21507 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21508 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21509 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21510 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/>
21513 <file name="protractor.js" type="protractor">
21514 var text = element(by.binding('url.text'));
21515 var valid = element(by.binding('myForm.input.$valid'));
21516 var input = element(by.model('url.text'));
21518 it('should initialize to model', function() {
21519 expect(text.getText()).toContain('http://google.com');
21520 expect(valid.getText()).toContain('true');
21523 it('should be invalid if empty', function() {
21525 input.sendKeys('');
21527 expect(text.getText()).toEqual('text =');
21528 expect(valid.getText()).toContain('false');
21531 it('should be invalid if not url', function() {
21533 input.sendKeys('box');
21535 expect(valid.getText()).toContain('false');
21540 'url': urlInputType,
21545 * @name input[email]
21548 * Text input with email validation. Sets the `email` validation error key if not a valid email
21551 * <div class="alert alert-warning">
21552 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex
21553 * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can
21554 * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide})
21557 * @param {string} ngModel Assignable angular expression to data-bind to.
21558 * @param {string=} name Property name of the form under which the control is published.
21559 * @param {string=} required Sets `required` validation error key if the value is not entered.
21560 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21561 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21562 * `required` when you want to data-bind to the `required` attribute.
21563 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21565 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21566 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21568 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21569 * that contains the regular expression body that will be converted to a regular expression
21570 * as in the ngPattern directive.
21571 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21572 * a RegExp found by evaluating the Angular expression given in the attribute value.
21573 * If the expression evaluates to a RegExp object, then this is used directly.
21574 * If the expression evaluates to a string, then it will be converted to a RegExp
21575 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21576 * `new RegExp('^abc$')`.<br />
21577 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21578 * start at the index of the last search's match, thus not taking the whole input value into
21580 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21581 * interaction with the input element.
21584 <example name="email-input-directive" module="emailExample">
21585 <file name="index.html">
21587 angular.module('emailExample', [])
21588 .controller('ExampleController', ['$scope', function($scope) {
21590 text: 'me@example.com'
21594 <form name="myForm" ng-controller="ExampleController">
21596 <input type="email" name="input" ng-model="email.text" required>
21599 <span class="error" ng-show="myForm.input.$error.required">
21601 <span class="error" ng-show="myForm.input.$error.email">
21602 Not valid email!</span>
21604 <tt>text = {{email.text}}</tt><br/>
21605 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21606 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21607 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21608 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21609 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/>
21612 <file name="protractor.js" type="protractor">
21613 var text = element(by.binding('email.text'));
21614 var valid = element(by.binding('myForm.input.$valid'));
21615 var input = element(by.model('email.text'));
21617 it('should initialize to model', function() {
21618 expect(text.getText()).toContain('me@example.com');
21619 expect(valid.getText()).toContain('true');
21622 it('should be invalid if empty', function() {
21624 input.sendKeys('');
21625 expect(text.getText()).toEqual('text =');
21626 expect(valid.getText()).toContain('false');
21629 it('should be invalid if not email', function() {
21631 input.sendKeys('xxx');
21633 expect(valid.getText()).toContain('false');
21638 'email': emailInputType,
21643 * @name input[radio]
21646 * HTML radio button.
21648 * @param {string} ngModel Assignable angular expression to data-bind to.
21649 * @param {string} value The value to which the `ngModel` expression should be set when selected.
21650 * Note that `value` only supports `string` values, i.e. the scope model needs to be a string,
21651 * too. Use `ngValue` if you need complex models (`number`, `object`, ...).
21652 * @param {string=} name Property name of the form under which the control is published.
21653 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21654 * interaction with the input element.
21655 * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio
21656 * is selected. Should be used instead of the `value` attribute if you need
21657 * a non-string `ngModel` (`boolean`, `array`, ...).
21660 <example name="radio-input-directive" module="radioExample">
21661 <file name="index.html">
21663 angular.module('radioExample', [])
21664 .controller('ExampleController', ['$scope', function($scope) {
21668 $scope.specialValue = {
21674 <form name="myForm" ng-controller="ExampleController">
21676 <input type="radio" ng-model="color.name" value="red">
21680 <input type="radio" ng-model="color.name" ng-value="specialValue">
21684 <input type="radio" ng-model="color.name" value="blue">
21687 <tt>color = {{color.name | json}}</tt><br/>
21689 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
21691 <file name="protractor.js" type="protractor">
21692 it('should change state', function() {
21693 var color = element(by.binding('color.name'));
21695 expect(color.getText()).toContain('blue');
21697 element.all(by.model('color.name')).get(0).click();
21699 expect(color.getText()).toContain('red');
21704 'radio': radioInputType,
21709 * @name input[checkbox]
21714 * @param {string} ngModel Assignable angular expression to data-bind to.
21715 * @param {string=} name Property name of the form under which the control is published.
21716 * @param {expression=} ngTrueValue The value to which the expression should be set when selected.
21717 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected.
21718 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21719 * interaction with the input element.
21722 <example name="checkbox-input-directive" module="checkboxExample">
21723 <file name="index.html">
21725 angular.module('checkboxExample', [])
21726 .controller('ExampleController', ['$scope', function($scope) {
21727 $scope.checkboxModel = {
21733 <form name="myForm" ng-controller="ExampleController">
21735 <input type="checkbox" ng-model="checkboxModel.value1">
21738 <input type="checkbox" ng-model="checkboxModel.value2"
21739 ng-true-value="'YES'" ng-false-value="'NO'">
21741 <tt>value1 = {{checkboxModel.value1}}</tt><br/>
21742 <tt>value2 = {{checkboxModel.value2}}</tt><br/>
21745 <file name="protractor.js" type="protractor">
21746 it('should change state', function() {
21747 var value1 = element(by.binding('checkboxModel.value1'));
21748 var value2 = element(by.binding('checkboxModel.value2'));
21750 expect(value1.getText()).toContain('true');
21751 expect(value2.getText()).toContain('YES');
21753 element(by.model('checkboxModel.value1')).click();
21754 element(by.model('checkboxModel.value2')).click();
21756 expect(value1.getText()).toContain('false');
21757 expect(value2.getText()).toContain('NO');
21762 'checkbox': checkboxInputType,
21771 function stringBasedInputType(ctrl) {
21772 ctrl.$formatters.push(function(value) {
21773 return ctrl.$isEmpty(value) ? value : value.toString();
21777 function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
21778 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
21779 stringBasedInputType(ctrl);
21782 function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
21783 var type = lowercase(element[0].type);
21785 // In composition mode, users are still inputing intermediate text buffer,
21786 // hold the listener until composition is done.
21787 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
21788 if (!$sniffer.android) {
21789 var composing = false;
21791 element.on('compositionstart', function(data) {
21795 element.on('compositionend', function() {
21801 var listener = function(ev) {
21803 $browser.defer.cancel(timeout);
21806 if (composing) return;
21807 var value = element.val(),
21808 event = ev && ev.type;
21810 // By default we will trim the value
21811 // If the attribute ng-trim exists we will avoid trimming
21812 // If input type is 'password', the value is never trimmed
21813 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) {
21814 value = trim(value);
21817 // If a control is suffering from bad input (due to native validators), browsers discard its
21818 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the
21819 // control's value is the same empty value twice in a row.
21820 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) {
21821 ctrl.$setViewValue(value, event);
21825 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the
21826 // input event on backspace, delete or cut
21827 if ($sniffer.hasEvent('input')) {
21828 element.on('input', listener);
21832 var deferListener = function(ev, input, origValue) {
21834 timeout = $browser.defer(function() {
21836 if (!input || input.value !== origValue) {
21843 element.on('keydown', function(event) {
21844 var key = event.keyCode;
21847 // command modifiers arrows
21848 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
21850 deferListener(event, this, this.value);
21853 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
21854 if ($sniffer.hasEvent('paste')) {
21855 element.on('paste cut', deferListener);
21859 // if user paste into input using mouse on older browser
21860 // or form autocomplete on newer browser, we need "change" event to catch it
21861 element.on('change', listener);
21863 ctrl.$render = function() {
21864 // Workaround for Firefox validation #12102.
21865 var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue;
21866 if (element.val() !== value) {
21867 element.val(value);
21872 function weekParser(isoWeek, existingDate) {
21873 if (isDate(isoWeek)) {
21877 if (isString(isoWeek)) {
21878 WEEK_REGEXP.lastIndex = 0;
21879 var parts = WEEK_REGEXP.exec(isoWeek);
21881 var year = +parts[1],
21887 firstThurs = getFirstThursdayOfYear(year),
21888 addDays = (week - 1) * 7;
21890 if (existingDate) {
21891 hours = existingDate.getHours();
21892 minutes = existingDate.getMinutes();
21893 seconds = existingDate.getSeconds();
21894 milliseconds = existingDate.getMilliseconds();
21897 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds);
21904 function createDateParser(regexp, mapping) {
21905 return function(iso, date) {
21912 if (isString(iso)) {
21913 // When a date is JSON'ified to wraps itself inside of an extra
21914 // set of double quotes. This makes the date parsing code unable
21915 // to match the date string and parse it as a date.
21916 if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') {
21917 iso = iso.substring(1, iso.length - 1);
21919 if (ISO_DATE_REGEXP.test(iso)) {
21920 return new Date(iso);
21922 regexp.lastIndex = 0;
21923 parts = regexp.exec(iso);
21929 yyyy: date.getFullYear(),
21930 MM: date.getMonth() + 1,
21931 dd: date.getDate(),
21932 HH: date.getHours(),
21933 mm: date.getMinutes(),
21934 ss: date.getSeconds(),
21935 sss: date.getMilliseconds() / 1000
21938 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 };
21941 forEach(parts, function(part, index) {
21942 if (index < mapping.length) {
21943 map[mapping[index]] = +part;
21946 return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0);
21954 function createDateInputType(type, regexp, parseDate, format) {
21955 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
21956 badInputChecker(scope, element, attr, ctrl);
21957 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
21958 var timezone = ctrl && ctrl.$options && ctrl.$options.timezone;
21961 ctrl.$$parserName = type;
21962 ctrl.$parsers.push(function(value) {
21963 if (ctrl.$isEmpty(value)) return null;
21964 if (regexp.test(value)) {
21965 // Note: We cannot read ctrl.$modelValue, as there might be a different
21966 // parser/formatter in the processing chain so that the model
21967 // contains some different data format!
21968 var parsedDate = parseDate(value, previousDate);
21970 parsedDate = convertTimezoneToLocal(parsedDate, timezone);
21977 ctrl.$formatters.push(function(value) {
21978 if (value && !isDate(value)) {
21979 throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
21981 if (isValidDate(value)) {
21982 previousDate = value;
21983 if (previousDate && timezone) {
21984 previousDate = convertTimezoneToLocal(previousDate, timezone, true);
21986 return $filter('date')(value, format, timezone);
21988 previousDate = null;
21993 if (isDefined(attr.min) || attr.ngMin) {
21995 ctrl.$validators.min = function(value) {
21996 return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal;
21998 attr.$observe('min', function(val) {
21999 minVal = parseObservedDateValue(val);
22004 if (isDefined(attr.max) || attr.ngMax) {
22006 ctrl.$validators.max = function(value) {
22007 return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal;
22009 attr.$observe('max', function(val) {
22010 maxVal = parseObservedDateValue(val);
22015 function isValidDate(value) {
22016 // Invalid Date: getTime() returns NaN
22017 return value && !(value.getTime && value.getTime() !== value.getTime());
22020 function parseObservedDateValue(val) {
22021 return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val;
22026 function badInputChecker(scope, element, attr, ctrl) {
22027 var node = element[0];
22028 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity);
22029 if (nativeValidation) {
22030 ctrl.$parsers.push(function(value) {
22031 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {};
22032 // Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430):
22033 // - also sets validity.badInput (should only be validity.typeMismatch).
22034 // - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email)
22035 // - can ignore this case as we can still read out the erroneous email...
22036 return validity.badInput && !validity.typeMismatch ? undefined : value;
22041 function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
22042 badInputChecker(scope, element, attr, ctrl);
22043 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
22045 ctrl.$$parserName = 'number';
22046 ctrl.$parsers.push(function(value) {
22047 if (ctrl.$isEmpty(value)) return null;
22048 if (NUMBER_REGEXP.test(value)) return parseFloat(value);
22052 ctrl.$formatters.push(function(value) {
22053 if (!ctrl.$isEmpty(value)) {
22054 if (!isNumber(value)) {
22055 throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value);
22057 value = value.toString();
22062 if (isDefined(attr.min) || attr.ngMin) {
22064 ctrl.$validators.min = function(value) {
22065 return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal;
22068 attr.$observe('min', function(val) {
22069 if (isDefined(val) && !isNumber(val)) {
22070 val = parseFloat(val, 10);
22072 minVal = isNumber(val) && !isNaN(val) ? val : undefined;
22073 // TODO(matsko): implement validateLater to reduce number of validations
22078 if (isDefined(attr.max) || attr.ngMax) {
22080 ctrl.$validators.max = function(value) {
22081 return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal;
22084 attr.$observe('max', function(val) {
22085 if (isDefined(val) && !isNumber(val)) {
22086 val = parseFloat(val, 10);
22088 maxVal = isNumber(val) && !isNaN(val) ? val : undefined;
22089 // TODO(matsko): implement validateLater to reduce number of validations
22095 function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
22096 // Note: no badInputChecker here by purpose as `url` is only a validation
22097 // in browsers, i.e. we can always read out input.value even if it is not valid!
22098 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
22099 stringBasedInputType(ctrl);
22101 ctrl.$$parserName = 'url';
22102 ctrl.$validators.url = function(modelValue, viewValue) {
22103 var value = modelValue || viewValue;
22104 return ctrl.$isEmpty(value) || URL_REGEXP.test(value);
22108 function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
22109 // Note: no badInputChecker here by purpose as `url` is only a validation
22110 // in browsers, i.e. we can always read out input.value even if it is not valid!
22111 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
22112 stringBasedInputType(ctrl);
22114 ctrl.$$parserName = 'email';
22115 ctrl.$validators.email = function(modelValue, viewValue) {
22116 var value = modelValue || viewValue;
22117 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value);
22121 function radioInputType(scope, element, attr, ctrl) {
22122 // make the name unique, if not defined
22123 if (isUndefined(attr.name)) {
22124 element.attr('name', nextUid());
22127 var listener = function(ev) {
22128 if (element[0].checked) {
22129 ctrl.$setViewValue(attr.value, ev && ev.type);
22133 element.on('click', listener);
22135 ctrl.$render = function() {
22136 var value = attr.value;
22137 element[0].checked = (value == ctrl.$viewValue);
22140 attr.$observe('value', ctrl.$render);
22143 function parseConstantExpr($parse, context, name, expression, fallback) {
22145 if (isDefined(expression)) {
22146 parseFn = $parse(expression);
22147 if (!parseFn.constant) {
22148 throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' +
22149 '`{1}`.', name, expression);
22151 return parseFn(context);
22156 function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
22157 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true);
22158 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false);
22160 var listener = function(ev) {
22161 ctrl.$setViewValue(element[0].checked, ev && ev.type);
22164 element.on('click', listener);
22166 ctrl.$render = function() {
22167 element[0].checked = ctrl.$viewValue;
22170 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false`
22171 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert
22172 // it to a boolean.
22173 ctrl.$isEmpty = function(value) {
22174 return value === false;
22177 ctrl.$formatters.push(function(value) {
22178 return equals(value, trueValue);
22181 ctrl.$parsers.push(function(value) {
22182 return value ? trueValue : falseValue;
22193 * HTML textarea element control with angular data-binding. The data-binding and validation
22194 * properties of this element are exactly the same as those of the
22195 * {@link ng.directive:input input element}.
22197 * @param {string} ngModel Assignable angular expression to data-bind to.
22198 * @param {string=} name Property name of the form under which the control is published.
22199 * @param {string=} required Sets `required` validation error key if the value is not entered.
22200 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
22201 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
22202 * `required` when you want to data-bind to the `required` attribute.
22203 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
22205 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
22206 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
22208 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
22209 * a RegExp found by evaluating the Angular expression given in the attribute value.
22210 * If the expression evaluates to a RegExp object, then this is used directly.
22211 * If the expression evaluates to a string, then it will be converted to a RegExp
22212 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
22213 * `new RegExp('^abc$')`.<br />
22214 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
22215 * start at the index of the last search's match, thus not taking the whole input value into
22217 * @param {string=} ngChange Angular expression to be executed when input changes due to user
22218 * interaction with the input element.
22219 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
22229 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding,
22230 * input state control, and validation.
22231 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers.
22233 * <div class="alert alert-warning">
22234 * **Note:** Not every feature offered is available for all input types.
22235 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`.
22238 * @param {string} ngModel Assignable angular expression to data-bind to.
22239 * @param {string=} name Property name of the form under which the control is published.
22240 * @param {string=} required Sets `required` validation error key if the value is not entered.
22241 * @param {boolean=} ngRequired Sets `required` attribute if set to true
22242 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
22244 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
22245 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
22247 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
22248 * a RegExp found by evaluating the Angular expression given in the attribute value.
22249 * If the expression evaluates to a RegExp object, then this is used directly.
22250 * If the expression evaluates to a string, then it will be converted to a RegExp
22251 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
22252 * `new RegExp('^abc$')`.<br />
22253 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
22254 * start at the index of the last search's match, thus not taking the whole input value into
22256 * @param {string=} ngChange Angular expression to be executed when input changes due to user
22257 * interaction with the input element.
22258 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
22259 * This parameter is ignored for input[type=password] controls, which will never trim the
22263 <example name="input-directive" module="inputExample">
22264 <file name="index.html">
22266 angular.module('inputExample', [])
22267 .controller('ExampleController', ['$scope', function($scope) {
22268 $scope.user = {name: 'guest', last: 'visitor'};
22271 <div ng-controller="ExampleController">
22272 <form name="myForm">
22275 <input type="text" name="userName" ng-model="user.name" required>
22278 <span class="error" ng-show="myForm.userName.$error.required">
22283 <input type="text" name="lastName" ng-model="user.last"
22284 ng-minlength="3" ng-maxlength="10">
22287 <span class="error" ng-show="myForm.lastName.$error.minlength">
22289 <span class="error" ng-show="myForm.lastName.$error.maxlength">
22294 <tt>user = {{user}}</tt><br/>
22295 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/>
22296 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/>
22297 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/>
22298 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/>
22299 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
22300 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
22301 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/>
22302 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/>
22305 <file name="protractor.js" type="protractor">
22306 var user = element(by.exactBinding('user'));
22307 var userNameValid = element(by.binding('myForm.userName.$valid'));
22308 var lastNameValid = element(by.binding('myForm.lastName.$valid'));
22309 var lastNameError = element(by.binding('myForm.lastName.$error'));
22310 var formValid = element(by.binding('myForm.$valid'));
22311 var userNameInput = element(by.model('user.name'));
22312 var userLastInput = element(by.model('user.last'));
22314 it('should initialize to model', function() {
22315 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}');
22316 expect(userNameValid.getText()).toContain('true');
22317 expect(formValid.getText()).toContain('true');
22320 it('should be invalid if empty when required', function() {
22321 userNameInput.clear();
22322 userNameInput.sendKeys('');
22324 expect(user.getText()).toContain('{"last":"visitor"}');
22325 expect(userNameValid.getText()).toContain('false');
22326 expect(formValid.getText()).toContain('false');
22329 it('should be valid if empty when min length is set', function() {
22330 userLastInput.clear();
22331 userLastInput.sendKeys('');
22333 expect(user.getText()).toContain('{"name":"guest","last":""}');
22334 expect(lastNameValid.getText()).toContain('true');
22335 expect(formValid.getText()).toContain('true');
22338 it('should be invalid if less than required min length', function() {
22339 userLastInput.clear();
22340 userLastInput.sendKeys('xx');
22342 expect(user.getText()).toContain('{"name":"guest"}');
22343 expect(lastNameValid.getText()).toContain('false');
22344 expect(lastNameError.getText()).toContain('minlength');
22345 expect(formValid.getText()).toContain('false');
22348 it('should be invalid if longer than max length', function() {
22349 userLastInput.clear();
22350 userLastInput.sendKeys('some ridiculously long name');
22352 expect(user.getText()).toContain('{"name":"guest"}');
22353 expect(lastNameValid.getText()).toContain('false');
22354 expect(lastNameError.getText()).toContain('maxlength');
22355 expect(formValid.getText()).toContain('false');
22360 var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
22361 function($browser, $sniffer, $filter, $parse) {
22364 require: ['?ngModel'],
22366 pre: function(scope, element, attr, ctrls) {
22368 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
22369 $browser, $filter, $parse);
22378 var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
22384 * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`},
22385 * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to
22388 * `ngValue` is useful when dynamically generating lists of radio buttons using
22389 * {@link ngRepeat `ngRepeat`}, as shown below.
22391 * Likewise, `ngValue` can be used to generate `<option>` elements for
22392 * the {@link select `select`} element. In that case however, only strings are supported
22393 * for the `value `attribute, so the resulting `ngModel` will always be a string.
22394 * Support for `select` models with non-string values is available via `ngOptions`.
22397 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute
22398 * of the `input` element
22401 <example name="ngValue-directive" module="valueExample">
22402 <file name="index.html">
22404 angular.module('valueExample', [])
22405 .controller('ExampleController', ['$scope', function($scope) {
22406 $scope.names = ['pizza', 'unicorns', 'robots'];
22407 $scope.my = { favorite: 'unicorns' };
22410 <form ng-controller="ExampleController">
22411 <h2>Which is your favorite?</h2>
22412 <label ng-repeat="name in names" for="{{name}}">
22414 <input type="radio"
22415 ng-model="my.favorite"
22420 <div>You chose {{my.favorite}}</div>
22423 <file name="protractor.js" type="protractor">
22424 var favorite = element(by.binding('my.favorite'));
22426 it('should initialize to model', function() {
22427 expect(favorite.getText()).toContain('unicorns');
22429 it('should bind the values to the inputs', function() {
22430 element.all(by.model('my.favorite')).get(0).click();
22431 expect(favorite.getText()).toContain('pizza');
22436 var ngValueDirective = function() {
22440 compile: function(tpl, tplAttr) {
22441 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) {
22442 return function ngValueConstantLink(scope, elm, attr) {
22443 attr.$set('value', scope.$eval(attr.ngValue));
22446 return function ngValueLink(scope, elm, attr) {
22447 scope.$watch(attr.ngValue, function valueWatchAction(value) {
22448 attr.$set('value', value);
22462 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element
22463 * with the value of a given expression, and to update the text content when the value of that
22464 * expression changes.
22466 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like
22467 * `{{ expression }}` which is similar but less verbose.
22469 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily
22470 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an
22471 * element attribute, it makes the bindings invisible to the user while the page is loading.
22473 * An alternative solution to this problem would be using the
22474 * {@link ng.directive:ngCloak ngCloak} directive.
22478 * @param {expression} ngBind {@link guide/expression Expression} to evaluate.
22481 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly.
22482 <example module="bindExample">
22483 <file name="index.html">
22485 angular.module('bindExample', [])
22486 .controller('ExampleController', ['$scope', function($scope) {
22487 $scope.name = 'Whirled';
22490 <div ng-controller="ExampleController">
22491 <label>Enter name: <input type="text" ng-model="name"></label><br>
22492 Hello <span ng-bind="name"></span>!
22495 <file name="protractor.js" type="protractor">
22496 it('should check ng-bind', function() {
22497 var nameInput = element(by.model('name'));
22499 expect(element(by.binding('name')).getText()).toBe('Whirled');
22501 nameInput.sendKeys('world');
22502 expect(element(by.binding('name')).getText()).toBe('world');
22507 var ngBindDirective = ['$compile', function($compile) {
22510 compile: function ngBindCompile(templateElement) {
22511 $compile.$$addBindingClass(templateElement);
22512 return function ngBindLink(scope, element, attr) {
22513 $compile.$$addBindingInfo(element, attr.ngBind);
22514 element = element[0];
22515 scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
22516 element.textContent = isUndefined(value) ? '' : value;
22526 * @name ngBindTemplate
22529 * The `ngBindTemplate` directive specifies that the element
22530 * text content should be replaced with the interpolation of the template
22531 * in the `ngBindTemplate` attribute.
22532 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}`
22533 * expressions. This directive is needed since some HTML elements
22534 * (such as TITLE and OPTION) cannot contain SPAN elements.
22537 * @param {string} ngBindTemplate template of form
22538 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval.
22541 * Try it here: enter text in text box and watch the greeting change.
22542 <example module="bindExample">
22543 <file name="index.html">
22545 angular.module('bindExample', [])
22546 .controller('ExampleController', ['$scope', function($scope) {
22547 $scope.salutation = 'Hello';
22548 $scope.name = 'World';
22551 <div ng-controller="ExampleController">
22552 <label>Salutation: <input type="text" ng-model="salutation"></label><br>
22553 <label>Name: <input type="text" ng-model="name"></label><br>
22554 <pre ng-bind-template="{{salutation}} {{name}}!"></pre>
22557 <file name="protractor.js" type="protractor">
22558 it('should check ng-bind', function() {
22559 var salutationElem = element(by.binding('salutation'));
22560 var salutationInput = element(by.model('salutation'));
22561 var nameInput = element(by.model('name'));
22563 expect(salutationElem.getText()).toBe('Hello World!');
22565 salutationInput.clear();
22566 salutationInput.sendKeys('Greetings');
22568 nameInput.sendKeys('user');
22570 expect(salutationElem.getText()).toBe('Greetings user!');
22575 var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) {
22577 compile: function ngBindTemplateCompile(templateElement) {
22578 $compile.$$addBindingClass(templateElement);
22579 return function ngBindTemplateLink(scope, element, attr) {
22580 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
22581 $compile.$$addBindingInfo(element, interpolateFn.expressions);
22582 element = element[0];
22583 attr.$observe('ngBindTemplate', function(value) {
22584 element.textContent = isUndefined(value) ? '' : value;
22597 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default,
22598 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service.
22599 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link
22600 * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize}
22601 * in your module's dependencies, you need to include "angular-sanitize.js" in your application.
22603 * You may also bypass sanitization for values you know are safe. To do so, bind to
22604 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
22605 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}.
22607 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you
22608 * will have an exception (instead of an exploit.)
22611 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
22615 <example module="bindHtmlExample" deps="angular-sanitize.js">
22616 <file name="index.html">
22617 <div ng-controller="ExampleController">
22618 <p ng-bind-html="myHTML"></p>
22622 <file name="script.js">
22623 angular.module('bindHtmlExample', ['ngSanitize'])
22624 .controller('ExampleController', ['$scope', function($scope) {
22626 'I am an <code>HTML</code>string with ' +
22627 '<a href="#">links!</a> and other <em>stuff</em>';
22631 <file name="protractor.js" type="protractor">
22632 it('should check ng-bind-html', function() {
22633 expect(element(by.binding('myHTML')).getText()).toBe(
22634 'I am an HTMLstring with links! and other stuff');
22639 var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) {
22642 compile: function ngBindHtmlCompile(tElement, tAttrs) {
22643 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml);
22644 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) {
22645 return (value || '').toString();
22647 $compile.$$addBindingClass(tElement);
22649 return function ngBindHtmlLink(scope, element, attr) {
22650 $compile.$$addBindingInfo(element, attr.ngBindHtml);
22652 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() {
22653 // we re-evaluate the expr because we want a TrustedValueHolderType
22654 // for $sce, not a string
22655 element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || '');
22667 * Evaluate the given expression when the user changes the input.
22668 * The expression is evaluated immediately, unlike the JavaScript onchange event
22669 * which only triggers at the end of a change (usually, when the user leaves the
22670 * form element or presses the return key).
22672 * The `ngChange` expression is only evaluated when a change in the input value causes
22673 * a new value to be committed to the model.
22675 * It will not be evaluated:
22676 * * if the value returned from the `$parsers` transformation pipeline has not changed
22677 * * if the input has continued to be invalid since the model will stay `null`
22678 * * if the model is changed programmatically and not by a change to the input value
22681 * Note, this directive requires `ngModel` to be present.
22684 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change
22688 * <example name="ngChange-directive" module="changeExample">
22689 * <file name="index.html">
22691 * angular.module('changeExample', [])
22692 * .controller('ExampleController', ['$scope', function($scope) {
22693 * $scope.counter = 0;
22694 * $scope.change = function() {
22695 * $scope.counter++;
22699 * <div ng-controller="ExampleController">
22700 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
22701 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
22702 * <label for="ng-change-example2">Confirmed</label><br />
22703 * <tt>debug = {{confirmed}}</tt><br/>
22704 * <tt>counter = {{counter}}</tt><br/>
22707 * <file name="protractor.js" type="protractor">
22708 * var counter = element(by.binding('counter'));
22709 * var debug = element(by.binding('confirmed'));
22711 * it('should evaluate the expression if changing from view', function() {
22712 * expect(counter.getText()).toContain('0');
22714 * element(by.id('ng-change-example1')).click();
22716 * expect(counter.getText()).toContain('1');
22717 * expect(debug.getText()).toContain('true');
22720 * it('should not evaluate the expression if changing from model', function() {
22721 * element(by.id('ng-change-example2')).click();
22723 * expect(counter.getText()).toContain('0');
22724 * expect(debug.getText()).toContain('true');
22729 var ngChangeDirective = valueFn({
22731 require: 'ngModel',
22732 link: function(scope, element, attr, ctrl) {
22733 ctrl.$viewChangeListeners.push(function() {
22734 scope.$eval(attr.ngChange);
22739 function classDirective(name, selector) {
22740 name = 'ngClass' + name;
22741 return ['$animate', function($animate) {
22744 link: function(scope, element, attr) {
22747 scope.$watch(attr[name], ngClassWatchAction, true);
22749 attr.$observe('class', function(value) {
22750 ngClassWatchAction(scope.$eval(attr[name]));
22754 if (name !== 'ngClass') {
22755 scope.$watch('$index', function($index, old$index) {
22756 // jshint bitwise: false
22757 var mod = $index & 1;
22758 if (mod !== (old$index & 1)) {
22759 var classes = arrayClasses(scope.$eval(attr[name]));
22761 addClasses(classes) :
22762 removeClasses(classes);
22767 function addClasses(classes) {
22768 var newClasses = digestClassCounts(classes, 1);
22769 attr.$addClass(newClasses);
22772 function removeClasses(classes) {
22773 var newClasses = digestClassCounts(classes, -1);
22774 attr.$removeClass(newClasses);
22777 function digestClassCounts(classes, count) {
22778 // Use createMap() to prevent class assumptions involving property
22779 // names in Object.prototype
22780 var classCounts = element.data('$classCounts') || createMap();
22781 var classesToUpdate = [];
22782 forEach(classes, function(className) {
22783 if (count > 0 || classCounts[className]) {
22784 classCounts[className] = (classCounts[className] || 0) + count;
22785 if (classCounts[className] === +(count > 0)) {
22786 classesToUpdate.push(className);
22790 element.data('$classCounts', classCounts);
22791 return classesToUpdate.join(' ');
22794 function updateClasses(oldClasses, newClasses) {
22795 var toAdd = arrayDifference(newClasses, oldClasses);
22796 var toRemove = arrayDifference(oldClasses, newClasses);
22797 toAdd = digestClassCounts(toAdd, 1);
22798 toRemove = digestClassCounts(toRemove, -1);
22799 if (toAdd && toAdd.length) {
22800 $animate.addClass(element, toAdd);
22802 if (toRemove && toRemove.length) {
22803 $animate.removeClass(element, toRemove);
22807 function ngClassWatchAction(newVal) {
22808 if (selector === true || scope.$index % 2 === selector) {
22809 var newClasses = arrayClasses(newVal || []);
22811 addClasses(newClasses);
22812 } else if (!equals(newVal,oldVal)) {
22813 var oldClasses = arrayClasses(oldVal);
22814 updateClasses(oldClasses, newClasses);
22817 oldVal = shallowCopy(newVal);
22822 function arrayDifference(tokens1, tokens2) {
22826 for (var i = 0; i < tokens1.length; i++) {
22827 var token = tokens1[i];
22828 for (var j = 0; j < tokens2.length; j++) {
22829 if (token == tokens2[j]) continue outer;
22831 values.push(token);
22836 function arrayClasses(classVal) {
22838 if (isArray(classVal)) {
22839 forEach(classVal, function(v) {
22840 classes = classes.concat(arrayClasses(v));
22843 } else if (isString(classVal)) {
22844 return classVal.split(' ');
22845 } else if (isObject(classVal)) {
22846 forEach(classVal, function(v, k) {
22848 classes = classes.concat(k.split(' '));
22864 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding
22865 * an expression that represents all classes to be added.
22867 * The directive operates in three different ways, depending on which of three types the expression
22870 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class
22873 * 2. If the expression evaluates to an object, then for each key-value pair of the
22874 * object with a truthy value the corresponding key is used as a class name.
22876 * 3. If the expression evaluates to an array, each element of the array should either be a string as in
22877 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array
22878 * to give you more control over what CSS classes appear. See the code below for an example of this.
22881 * The directive won't add duplicate classes if a particular class was already set.
22883 * When the expression changes, the previously added classes are removed and only then are the
22884 * new classes added.
22887 * **add** - happens just before the class is applied to the elements
22889 * **remove** - happens just before the class is removed from the element
22892 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result
22893 * of the evaluation can be a string representing space delimited class
22894 * names, an array, or a map of class names to boolean values. In the case of a map, the
22895 * names of the properties whose values are truthy will be added as css classes to the
22898 * @example Example that demonstrates basic bindings via ngClass directive.
22900 <file name="index.html">
22901 <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p>
22903 <input type="checkbox" ng-model="deleted">
22904 deleted (apply "strike" class)
22907 <input type="checkbox" ng-model="important">
22908 important (apply "bold" class)
22911 <input type="checkbox" ng-model="error">
22912 error (apply "has-error" class)
22915 <p ng-class="style">Using String Syntax</p>
22916 <input type="text" ng-model="style"
22917 placeholder="Type: bold strike red" aria-label="Type: bold strike red">
22919 <p ng-class="[style1, style2, style3]">Using Array Syntax</p>
22920 <input ng-model="style1"
22921 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br>
22922 <input ng-model="style2"
22923 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br>
22924 <input ng-model="style3"
22925 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br>
22927 <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p>
22928 <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br>
22929 <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label>
22931 <file name="style.css">
22933 text-decoration: line-through;
22943 background-color: yellow;
22949 <file name="protractor.js" type="protractor">
22950 var ps = element.all(by.css('p'));
22952 it('should let you toggle the class', function() {
22954 expect(ps.first().getAttribute('class')).not.toMatch(/bold/);
22955 expect(ps.first().getAttribute('class')).not.toMatch(/has-error/);
22957 element(by.model('important')).click();
22958 expect(ps.first().getAttribute('class')).toMatch(/bold/);
22960 element(by.model('error')).click();
22961 expect(ps.first().getAttribute('class')).toMatch(/has-error/);
22964 it('should let you toggle string example', function() {
22965 expect(ps.get(1).getAttribute('class')).toBe('');
22966 element(by.model('style')).clear();
22967 element(by.model('style')).sendKeys('red');
22968 expect(ps.get(1).getAttribute('class')).toBe('red');
22971 it('array example should have 3 classes', function() {
22972 expect(ps.get(2).getAttribute('class')).toBe('');
22973 element(by.model('style1')).sendKeys('bold');
22974 element(by.model('style2')).sendKeys('strike');
22975 element(by.model('style3')).sendKeys('red');
22976 expect(ps.get(2).getAttribute('class')).toBe('bold strike red');
22979 it('array with map example should have 2 classes', function() {
22980 expect(ps.last().getAttribute('class')).toBe('');
22981 element(by.model('style4')).sendKeys('bold');
22982 element(by.model('warning')).click();
22983 expect(ps.last().getAttribute('class')).toBe('bold orange');
22990 The example below demonstrates how to perform animations using ngClass.
22992 <example module="ngAnimate" deps="angular-animate.js" animations="true">
22993 <file name="index.html">
22994 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'">
22995 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''">
22997 <span class="base-class" ng-class="myVar">Sample Text</span>
22999 <file name="style.css">
23001 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
23004 .base-class.my-class {
23009 <file name="protractor.js" type="protractor">
23010 it('should check ng-class', function() {
23011 expect(element(by.css('.base-class')).getAttribute('class')).not.
23012 toMatch(/my-class/);
23014 element(by.id('setbtn')).click();
23016 expect(element(by.css('.base-class')).getAttribute('class')).
23017 toMatch(/my-class/);
23019 element(by.id('clearbtn')).click();
23021 expect(element(by.css('.base-class')).getAttribute('class')).not.
23022 toMatch(/my-class/);
23028 ## ngClass and pre-existing CSS3 Transitions/Animations
23029 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure.
23030 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder
23031 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure
23032 to view the step by step details of {@link $animate#addClass $animate.addClass} and
23033 {@link $animate#removeClass $animate.removeClass}.
23035 var ngClassDirective = classDirective('', true);
23043 * The `ngClassOdd` and `ngClassEven` directives work exactly as
23044 * {@link ng.directive:ngClass ngClass}, except they work in
23045 * conjunction with `ngRepeat` and take effect only on odd (even) rows.
23047 * This directive can be applied only within the scope of an
23048 * {@link ng.directive:ngRepeat ngRepeat}.
23051 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result
23052 * of the evaluation can be a string representing space delimited class names or an array.
23056 <file name="index.html">
23057 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
23058 <li ng-repeat="name in names">
23059 <span ng-class-odd="'odd'" ng-class-even="'even'">
23065 <file name="style.css">
23073 <file name="protractor.js" type="protractor">
23074 it('should check ng-class-odd and ng-class-even', function() {
23075 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
23077 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
23083 var ngClassOddDirective = classDirective('Odd', 0);
23087 * @name ngClassEven
23091 * The `ngClassOdd` and `ngClassEven` directives work exactly as
23092 * {@link ng.directive:ngClass ngClass}, except they work in
23093 * conjunction with `ngRepeat` and take effect only on odd (even) rows.
23095 * This directive can be applied only within the scope of an
23096 * {@link ng.directive:ngRepeat ngRepeat}.
23099 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The
23100 * result of the evaluation can be a string representing space delimited class names or an array.
23104 <file name="index.html">
23105 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
23106 <li ng-repeat="name in names">
23107 <span ng-class-odd="'odd'" ng-class-even="'even'">
23108 {{name}}
23113 <file name="style.css">
23121 <file name="protractor.js" type="protractor">
23122 it('should check ng-class-odd and ng-class-even', function() {
23123 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
23125 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
23131 var ngClassEvenDirective = classDirective('Even', 1);
23139 * The `ngCloak` directive is used to prevent the Angular html template from being briefly
23140 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this
23141 * directive to avoid the undesirable flicker effect caused by the html template display.
23143 * The directive can be applied to the `<body>` element, but the preferred usage is to apply
23144 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering
23145 * of the browser view.
23147 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and
23148 * `angular.min.js`.
23149 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
23152 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
23153 * display: none !important;
23157 * When this css rule is loaded by the browser, all html elements (including their children) that
23158 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive
23159 * during the compilation of the template it deletes the `ngCloak` element attribute, making
23160 * the compiled element visible.
23162 * For the best result, the `angular.js` script must be loaded in the head section of the html
23163 * document; alternatively, the css rule above must be included in the external stylesheet of the
23170 <file name="index.html">
23171 <div id="template1" ng-cloak>{{ 'hello' }}</div>
23172 <div id="template2" class="ng-cloak">{{ 'world' }}</div>
23174 <file name="protractor.js" type="protractor">
23175 it('should remove the template directive and css class', function() {
23176 expect($('#template1').getAttribute('ng-cloak')).
23178 expect($('#template2').getAttribute('ng-cloak')).
23185 var ngCloakDirective = ngDirective({
23186 compile: function(element, attr) {
23187 attr.$set('ngCloak', undefined);
23188 element.removeClass('ng-cloak');
23194 * @name ngController
23197 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular
23198 * supports the principles behind the Model-View-Controller design pattern.
23200 * MVC components in angular:
23202 * * Model — Models are the properties of a scope; scopes are attached to the DOM where scope properties
23203 * are accessed through bindings.
23204 * * View — The template (HTML with data bindings) that is rendered into the View.
23205 * * Controller — The `ngController` directive specifies a Controller class; the class contains business
23206 * logic behind the application to decorate the scope with functions and values
23208 * Note that you can also attach controllers to the DOM by declaring it in a route definition
23209 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller
23210 * again using `ng-controller` in the template itself. This will cause the controller to be attached
23211 * and executed twice.
23216 * @param {expression} ngController Name of a constructor function registered with the current
23217 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression}
23218 * that on the current scope evaluates to a constructor function.
23220 * The controller instance can be published into a scope property by specifying
23221 * `ng-controller="as propertyName"`.
23223 * If the current `$controllerProvider` is configured to use globals (via
23224 * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may
23225 * also be the name of a globally accessible constructor function (not recommended).
23228 * Here is a simple form for editing user contact information. Adding, removing, clearing, and
23229 * greeting are methods declared on the controller (see source tab). These methods can
23230 * easily be called from the angular markup. Any changes to the data are automatically reflected
23231 * in the View without the need for a manual update.
23233 * Two different declaration styles are included below:
23235 * * one binds methods and properties directly onto the controller using `this`:
23236 * `ng-controller="SettingsController1 as settings"`
23237 * * one injects `$scope` into the controller:
23238 * `ng-controller="SettingsController2"`
23240 * The second option is more common in the Angular community, and is generally used in boilerplates
23241 * and in this guide. However, there are advantages to binding properties directly to the controller
23242 * and avoiding scope.
23244 * * Using `controller as` makes it obvious which controller you are accessing in the template when
23245 * multiple controllers apply to an element.
23246 * * If you are writing your controllers as classes you have easier access to the properties and
23247 * methods, which will appear on the scope, from inside the controller code.
23248 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal
23249 * inheritance masking primitives.
23251 * This example demonstrates the `controller as` syntax.
23253 * <example name="ngControllerAs" module="controllerAsExample">
23254 * <file name="index.html">
23255 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings">
23256 * <label>Name: <input type="text" ng-model="settings.name"/></label>
23257 * <button ng-click="settings.greet()">greet</button><br/>
23260 * <li ng-repeat="contact in settings.contacts">
23261 * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}">
23262 * <option>phone</option>
23263 * <option>email</option>
23265 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
23266 * <button ng-click="settings.clearContact(contact)">clear</button>
23267 * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button>
23269 * <li><button ng-click="settings.addContact()">add</button></li>
23273 * <file name="app.js">
23274 * angular.module('controllerAsExample', [])
23275 * .controller('SettingsController1', SettingsController1);
23277 * function SettingsController1() {
23278 * this.name = "John Smith";
23279 * this.contacts = [
23280 * {type: 'phone', value: '408 555 1212'},
23281 * {type: 'email', value: 'john.smith@example.org'} ];
23284 * SettingsController1.prototype.greet = function() {
23285 * alert(this.name);
23288 * SettingsController1.prototype.addContact = function() {
23289 * this.contacts.push({type: 'email', value: 'yourname@example.org'});
23292 * SettingsController1.prototype.removeContact = function(contactToRemove) {
23293 * var index = this.contacts.indexOf(contactToRemove);
23294 * this.contacts.splice(index, 1);
23297 * SettingsController1.prototype.clearContact = function(contact) {
23298 * contact.type = 'phone';
23299 * contact.value = '';
23302 * <file name="protractor.js" type="protractor">
23303 * it('should check controller as', function() {
23304 * var container = element(by.id('ctrl-as-exmpl'));
23305 * expect(container.element(by.model('settings.name'))
23306 * .getAttribute('value')).toBe('John Smith');
23308 * var firstRepeat =
23309 * container.element(by.repeater('contact in settings.contacts').row(0));
23310 * var secondRepeat =
23311 * container.element(by.repeater('contact in settings.contacts').row(1));
23313 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23314 * .toBe('408 555 1212');
23316 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
23317 * .toBe('john.smith@example.org');
23319 * firstRepeat.element(by.buttonText('clear')).click();
23321 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23324 * container.element(by.buttonText('add')).click();
23326 * expect(container.element(by.repeater('contact in settings.contacts').row(2))
23327 * .element(by.model('contact.value'))
23328 * .getAttribute('value'))
23329 * .toBe('yourname@example.org');
23334 * This example demonstrates the "attach to `$scope`" style of controller.
23336 * <example name="ngController" module="controllerExample">
23337 * <file name="index.html">
23338 * <div id="ctrl-exmpl" ng-controller="SettingsController2">
23339 * <label>Name: <input type="text" ng-model="name"/></label>
23340 * <button ng-click="greet()">greet</button><br/>
23343 * <li ng-repeat="contact in contacts">
23344 * <select ng-model="contact.type" id="select_{{$index}}">
23345 * <option>phone</option>
23346 * <option>email</option>
23348 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
23349 * <button ng-click="clearContact(contact)">clear</button>
23350 * <button ng-click="removeContact(contact)">X</button>
23352 * <li>[ <button ng-click="addContact()">add</button> ]</li>
23356 * <file name="app.js">
23357 * angular.module('controllerExample', [])
23358 * .controller('SettingsController2', ['$scope', SettingsController2]);
23360 * function SettingsController2($scope) {
23361 * $scope.name = "John Smith";
23362 * $scope.contacts = [
23363 * {type:'phone', value:'408 555 1212'},
23364 * {type:'email', value:'john.smith@example.org'} ];
23366 * $scope.greet = function() {
23367 * alert($scope.name);
23370 * $scope.addContact = function() {
23371 * $scope.contacts.push({type:'email', value:'yourname@example.org'});
23374 * $scope.removeContact = function(contactToRemove) {
23375 * var index = $scope.contacts.indexOf(contactToRemove);
23376 * $scope.contacts.splice(index, 1);
23379 * $scope.clearContact = function(contact) {
23380 * contact.type = 'phone';
23381 * contact.value = '';
23385 * <file name="protractor.js" type="protractor">
23386 * it('should check controller', function() {
23387 * var container = element(by.id('ctrl-exmpl'));
23389 * expect(container.element(by.model('name'))
23390 * .getAttribute('value')).toBe('John Smith');
23392 * var firstRepeat =
23393 * container.element(by.repeater('contact in contacts').row(0));
23394 * var secondRepeat =
23395 * container.element(by.repeater('contact in contacts').row(1));
23397 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23398 * .toBe('408 555 1212');
23399 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
23400 * .toBe('john.smith@example.org');
23402 * firstRepeat.element(by.buttonText('clear')).click();
23404 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23407 * container.element(by.buttonText('add')).click();
23409 * expect(container.element(by.repeater('contact in contacts').row(2))
23410 * .element(by.model('contact.value'))
23411 * .getAttribute('value'))
23412 * .toBe('yourname@example.org');
23418 var ngControllerDirective = [function() {
23434 * Angular has some features that can break certain
23435 * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules.
23437 * If you intend to implement these rules then you must tell Angular not to use these features.
23439 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps.
23442 * The following rules affect Angular:
23444 * * `unsafe-eval`: this rule forbids apps to use `eval` or `Function(string)` generated functions
23445 * (among other things). Angular makes use of this in the {@link $parse} service to provide a 30%
23446 * increase in the speed of evaluating Angular expressions.
23448 * * `unsafe-inline`: this rule forbids apps from inject custom styles into the document. Angular
23449 * makes use of this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}).
23450 * To make these directives work when a CSP rule is blocking inline styles, you must link to the
23451 * `angular-csp.css` in your HTML manually.
23453 * If you do not provide `ngCsp` then Angular tries to autodetect if CSP is blocking unsafe-eval
23454 * and automatically deactivates this feature in the {@link $parse} service. This autodetection,
23455 * however, triggers a CSP error to be logged in the console:
23458 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of
23459 * script in the following Content Security Policy directive: "default-src 'self'". Note that
23460 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
23463 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp`
23464 * directive on an element of the HTML document that appears before the `<script>` tag that loads
23465 * the `angular.js` file.
23467 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.*
23469 * You can specify which of the CSP related Angular features should be deactivated by providing
23470 * a value for the `ng-csp` attribute. The options are as follows:
23472 * * no-inline-style: this stops Angular from injecting CSS styles into the DOM
23474 * * no-unsafe-eval: this stops Angular from optimising $parse with unsafe eval of strings
23476 * You can use these values in the following combinations:
23479 * * No declaration means that Angular will assume that you can do inline styles, but it will do
23480 * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous versions
23483 * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell Angular to deactivate both inline
23484 * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous versions
23487 * * Specifying only `no-unsafe-eval` tells Angular that we must not use eval, but that we can inject
23488 * inline styles. E.g. `<body ng-csp="no-unsafe-eval">`.
23490 * * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can
23491 * run eval - no automcatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">`
23493 * * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject
23494 * styles nor use eval, which is the same as an empty: ng-csp.
23495 * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">`
23498 * This example shows how to apply the `ngCsp` directive to the `html` tag.
23501 <html ng-app ng-csp>
23507 // Note: the suffix `.csp` in the example name triggers
23508 // csp mode in our http server!
23509 <example name="example.csp" module="cspExample" ng-csp="true">
23510 <file name="index.html">
23511 <div ng-controller="MainController as ctrl">
23513 <button ng-click="ctrl.inc()" id="inc">Increment</button>
23514 <span id="counter">
23520 <button ng-click="ctrl.evil()" id="evil">Evil</button>
23521 <span id="evilError">
23527 <file name="script.js">
23528 angular.module('cspExample', [])
23529 .controller('MainController', function() {
23531 this.inc = function() {
23534 this.evil = function() {
23535 // jshint evil:true
23539 this.evilError = e.message;
23544 <file name="protractor.js" type="protractor">
23545 var util, webdriver;
23547 var incBtn = element(by.id('inc'));
23548 var counter = element(by.id('counter'));
23549 var evilBtn = element(by.id('evil'));
23550 var evilError = element(by.id('evilError'));
23552 function getAndClearSevereErrors() {
23553 return browser.manage().logs().get('browser').then(function(browserLog) {
23554 return browserLog.filter(function(logEntry) {
23555 return logEntry.level.value > webdriver.logging.Level.WARNING.value;
23560 function clearErrors() {
23561 getAndClearSevereErrors();
23564 function expectNoErrors() {
23565 getAndClearSevereErrors().then(function(filteredLog) {
23566 expect(filteredLog.length).toEqual(0);
23567 if (filteredLog.length) {
23568 console.log('browser console errors: ' + util.inspect(filteredLog));
23573 function expectError(regex) {
23574 getAndClearSevereErrors().then(function(filteredLog) {
23576 filteredLog.forEach(function(log) {
23577 if (log.message.match(regex)) {
23582 throw new Error('expected an error that matches ' + regex);
23587 beforeEach(function() {
23588 util = require('util');
23589 webdriver = require('protractor/node_modules/selenium-webdriver');
23592 // For now, we only test on Chrome,
23593 // as Safari does not load the page with Protractor's injected scripts,
23594 // and Firefox webdriver always disables content security policy (#6358)
23595 if (browser.params.browser !== 'chrome') {
23599 it('should not report errors when the page is loaded', function() {
23600 // clear errors so we are not dependent on previous tests
23602 // Need to reload the page as the page is already loaded when
23604 browser.driver.getCurrentUrl().then(function(url) {
23610 it('should evaluate expressions', function() {
23611 expect(counter.getText()).toEqual('0');
23613 expect(counter.getText()).toEqual('1');
23617 it('should throw and report an error when using "eval"', function() {
23619 expect(evilError.getText()).toMatch(/Content Security Policy/);
23620 expectError(/Content Security Policy/);
23626 // ngCsp is not implemented as a proper directive any more, because we need it be processed while we
23627 // bootstrap the system (before $parse is instantiated), for this reason we just have
23628 // the csp() fn that looks for the `ng-csp` attribute anywhere in the current doc
23635 * The ngClick directive allows you to specify custom behavior when
23636 * an element is clicked.
23640 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
23641 * click. ({@link guide/expression#-event- Event object is available as `$event`})
23645 <file name="index.html">
23646 <button ng-click="count = count + 1" ng-init="count=0">
23653 <file name="protractor.js" type="protractor">
23654 it('should check ng-click', function() {
23655 expect(element(by.binding('count')).getText()).toMatch('0');
23656 element(by.css('button')).click();
23657 expect(element(by.binding('count')).getText()).toMatch('1');
23663 * A collection of directives that allows creation of custom event handlers that are defined as
23664 * angular expressions and are compiled and executed within the current scope.
23666 var ngEventDirectives = {};
23668 // For events that might fire synchronously during DOM manipulation
23669 // we need to execute their event handlers asynchronously using $evalAsync,
23670 // so that they are not executed in an inconsistent state.
23671 var forceAsyncEvents = {
23676 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
23677 function(eventName) {
23678 var directiveName = directiveNormalize('ng-' + eventName);
23679 ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) {
23682 compile: function($element, attr) {
23683 // We expose the powerful $event object on the scope that provides access to the Window,
23684 // etc. that isn't protected by the fast paths in $parse. We explicitly request better
23685 // checks at the cost of speed since event handler expressions are not executed as
23686 // frequently as regular change detection.
23687 var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true);
23688 return function ngEventHandler(scope, element) {
23689 element.on(eventName, function(event) {
23690 var callback = function() {
23691 fn(scope, {$event:event});
23693 if (forceAsyncEvents[eventName] && $rootScope.$$phase) {
23694 scope.$evalAsync(callback);
23696 scope.$apply(callback);
23711 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event.
23715 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon
23716 * a dblclick. (The Event object is available as `$event`)
23720 <file name="index.html">
23721 <button ng-dblclick="count = count + 1" ng-init="count=0">
23722 Increment (on double click)
23732 * @name ngMousedown
23735 * The ngMousedown directive allows you to specify custom behavior on mousedown event.
23739 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon
23740 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`})
23744 <file name="index.html">
23745 <button ng-mousedown="count = count + 1" ng-init="count=0">
23746 Increment (on mouse down)
23759 * Specify custom behavior on mouseup event.
23763 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon
23764 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`})
23768 <file name="index.html">
23769 <button ng-mouseup="count = count + 1" ng-init="count=0">
23770 Increment (on mouse up)
23779 * @name ngMouseover
23782 * Specify custom behavior on mouseover event.
23786 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon
23787 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`})
23791 <file name="index.html">
23792 <button ng-mouseover="count = count + 1" ng-init="count=0">
23793 Increment (when mouse is over)
23803 * @name ngMouseenter
23806 * Specify custom behavior on mouseenter event.
23810 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon
23811 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`})
23815 <file name="index.html">
23816 <button ng-mouseenter="count = count + 1" ng-init="count=0">
23817 Increment (when mouse enters)
23827 * @name ngMouseleave
23830 * Specify custom behavior on mouseleave event.
23834 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon
23835 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`})
23839 <file name="index.html">
23840 <button ng-mouseleave="count = count + 1" ng-init="count=0">
23841 Increment (when mouse leaves)
23851 * @name ngMousemove
23854 * Specify custom behavior on mousemove event.
23858 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon
23859 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`})
23863 <file name="index.html">
23864 <button ng-mousemove="count = count + 1" ng-init="count=0">
23865 Increment (when mouse moves)
23878 * Specify custom behavior on keydown event.
23882 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon
23883 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
23887 <file name="index.html">
23888 <input ng-keydown="count = count + 1" ng-init="count=0">
23889 key down count: {{count}}
23900 * Specify custom behavior on keyup event.
23904 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon
23905 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
23909 <file name="index.html">
23910 <p>Typing in the input box below updates the key count</p>
23911 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}}
23913 <p>Typing in the input box below updates the keycode</p>
23914 <input ng-keyup="event=$event">
23915 <p>event keyCode: {{ event.keyCode }}</p>
23916 <p>event altKey: {{ event.altKey }}</p>
23927 * Specify custom behavior on keypress event.
23930 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon
23931 * keypress. ({@link guide/expression#-event- Event object is available as `$event`}
23932 * and can be interrogated for keyCode, altKey, etc.)
23936 <file name="index.html">
23937 <input ng-keypress="count = count + 1" ng-init="count=0">
23938 key press count: {{count}}
23949 * Enables binding angular expressions to onsubmit events.
23951 * Additionally it prevents the default action (which for form means sending the request to the
23952 * server and reloading the current page), but only if the form does not contain `action`,
23953 * `data-action`, or `x-action` attributes.
23955 * <div class="alert alert-warning">
23956 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and
23957 * `ngSubmit` handlers together. See the
23958 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation}
23959 * for a detailed discussion of when `ngSubmit` may be triggered.
23964 * @param {expression} ngSubmit {@link guide/expression Expression} to eval.
23965 * ({@link guide/expression#-event- Event object is available as `$event`})
23968 <example module="submitExample">
23969 <file name="index.html">
23971 angular.module('submitExample', [])
23972 .controller('ExampleController', ['$scope', function($scope) {
23974 $scope.text = 'hello';
23975 $scope.submit = function() {
23977 $scope.list.push(this.text);
23983 <form ng-submit="submit()" ng-controller="ExampleController">
23984 Enter text and hit enter:
23985 <input type="text" ng-model="text" name="text" />
23986 <input type="submit" id="submit" value="Submit" />
23987 <pre>list={{list}}</pre>
23990 <file name="protractor.js" type="protractor">
23991 it('should check ng-submit', function() {
23992 expect(element(by.binding('list')).getText()).toBe('list=[]');
23993 element(by.css('#submit')).click();
23994 expect(element(by.binding('list')).getText()).toContain('hello');
23995 expect(element(by.model('text')).getAttribute('value')).toBe('');
23997 it('should ignore empty strings', function() {
23998 expect(element(by.binding('list')).getText()).toBe('list=[]');
23999 element(by.css('#submit')).click();
24000 element(by.css('#submit')).click();
24001 expect(element(by.binding('list')).getText()).toContain('hello');
24012 * Specify custom behavior on focus event.
24014 * Note: As the `focus` event is executed synchronously when calling `input.focus()`
24015 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired
24016 * during an `$apply` to ensure a consistent state.
24018 * @element window, input, select, textarea, a
24020 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon
24021 * focus. ({@link guide/expression#-event- Event object is available as `$event`})
24024 * See {@link ng.directive:ngClick ngClick}
24032 * Specify custom behavior on blur event.
24034 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when
24035 * an element has lost focus.
24037 * Note: As the `blur` event is executed synchronously also during DOM manipulations
24038 * (e.g. removing a focussed input),
24039 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired
24040 * during an `$apply` to ensure a consistent state.
24042 * @element window, input, select, textarea, a
24044 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon
24045 * blur. ({@link guide/expression#-event- Event object is available as `$event`})
24048 * See {@link ng.directive:ngClick ngClick}
24056 * Specify custom behavior on copy event.
24058 * @element window, input, select, textarea, a
24060 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon
24061 * copy. ({@link guide/expression#-event- Event object is available as `$event`})
24065 <file name="index.html">
24066 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value">
24077 * Specify custom behavior on cut event.
24079 * @element window, input, select, textarea, a
24081 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon
24082 * cut. ({@link guide/expression#-event- Event object is available as `$event`})
24086 <file name="index.html">
24087 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value">
24098 * Specify custom behavior on paste event.
24100 * @element window, input, select, textarea, a
24102 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon
24103 * paste. ({@link guide/expression#-event- Event object is available as `$event`})
24107 <file name="index.html">
24108 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'>
24121 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an
24122 * {expression}. If the expression assigned to `ngIf` evaluates to a false
24123 * value then the element is removed from the DOM, otherwise a clone of the
24124 * element is reinserted into the DOM.
24126 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the
24127 * element in the DOM rather than changing its visibility via the `display` css property. A common
24128 * case when this difference is significant is when using css selectors that rely on an element's
24129 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classes.
24131 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope
24132 * is created when the element is restored. The scope created within `ngIf` inherits from
24133 * its parent scope using
24134 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance).
24135 * An important implication of this is if `ngModel` is used within `ngIf` to bind to
24136 * a javascript primitive defined in the parent scope. In this case any modifications made to the
24137 * variable within the child scope will override (hide) the value in the parent scope.
24139 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior
24140 * is if an element's class attribute is directly modified after it's compiled, using something like
24141 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element
24142 * the added class will be lost because the original compiled state is used to regenerate the element.
24144 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter`
24145 * and `leave` effects.
24148 * enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container
24149 * leave - happens just before the `ngIf` contents are removed from the DOM
24154 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then
24155 * the element is removed from the DOM tree. If it is truthy a copy of the compiled
24156 * element is added to the DOM tree.
24159 <example module="ngAnimate" deps="angular-animate.js" animations="true">
24160 <file name="index.html">
24161 <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/>
24163 <span ng-if="checked" class="animate-if">
24164 This is removed when the checkbox is unchecked.
24167 <file name="animations.css">
24170 border:1px solid black;
24174 .animate-if.ng-enter, .animate-if.ng-leave {
24175 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
24178 .animate-if.ng-enter,
24179 .animate-if.ng-leave.ng-leave-active {
24183 .animate-if.ng-leave,
24184 .animate-if.ng-enter.ng-enter-active {
24190 var ngIfDirective = ['$animate', function($animate) {
24192 multiElement: true,
24193 transclude: 'element',
24198 link: function($scope, $element, $attr, ctrl, $transclude) {
24199 var block, childScope, previousElements;
24200 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) {
24204 $transclude(function(clone, newScope) {
24205 childScope = newScope;
24206 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ');
24207 // Note: We only need the first/last node of the cloned nodes.
24208 // However, we need to keep the reference to the jqlite wrapper as it might be changed later
24209 // by a directive with templateUrl when its template arrives.
24213 $animate.enter(clone, $element.parent(), $element);
24217 if (previousElements) {
24218 previousElements.remove();
24219 previousElements = null;
24222 childScope.$destroy();
24226 previousElements = getBlockNodes(block.clone);
24227 $animate.leave(previousElements).then(function() {
24228 previousElements = null;
24244 * Fetches, compiles and includes an external HTML fragment.
24246 * By default, the template URL is restricted to the same domain and protocol as the
24247 * application document. This is done by calling {@link $sce#getTrustedResourceUrl
24248 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols
24249 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or
24250 * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link
24251 * ng.$sce Strict Contextual Escaping}.
24253 * In addition, the browser's
24254 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
24255 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
24256 * policy may further restrict whether the template is successfully loaded.
24257 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://`
24258 * access on some browsers.
24261 * enter - animation is used to bring new content into the browser.
24262 * leave - animation is used to animate existing content away.
24264 * The enter and leave animation occur concurrently.
24269 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
24270 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`.
24271 * @param {string=} onload Expression to evaluate when a new partial is loaded.
24272 * <div class="alert alert-warning">
24273 * **Note:** When using onload on SVG elements in IE11, the browser will try to call
24274 * a function with the name on the window element, which will usually throw a
24275 * "function is undefined" error. To fix this, you can instead use `data-onload` or a
24276 * different form that {@link guide/directive#normalization matches} `onload`.
24279 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll
24280 * $anchorScroll} to scroll the viewport after the content is loaded.
24282 * - If the attribute is not set, disable scrolling.
24283 * - If the attribute is set without value, enable scrolling.
24284 * - Otherwise enable scrolling only if the expression evaluates to truthy value.
24287 <example module="includeExample" deps="angular-animate.js" animations="true">
24288 <file name="index.html">
24289 <div ng-controller="ExampleController">
24290 <select ng-model="template" ng-options="t.name for t in templates">
24291 <option value="">(blank)</option>
24293 url of the template: <code>{{template.url}}</code>
24295 <div class="slide-animate-container">
24296 <div class="slide-animate" ng-include="template.url"></div>
24300 <file name="script.js">
24301 angular.module('includeExample', ['ngAnimate'])
24302 .controller('ExampleController', ['$scope', function($scope) {
24304 [ { name: 'template1.html', url: 'template1.html'},
24305 { name: 'template2.html', url: 'template2.html'} ];
24306 $scope.template = $scope.templates[0];
24309 <file name="template1.html">
24310 Content of template1.html
24312 <file name="template2.html">
24313 Content of template2.html
24315 <file name="animations.css">
24316 .slide-animate-container {
24319 border:1px solid black;
24328 .slide-animate.ng-enter, .slide-animate.ng-leave {
24329 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
24340 .slide-animate.ng-enter {
24343 .slide-animate.ng-enter.ng-enter-active {
24347 .slide-animate.ng-leave {
24350 .slide-animate.ng-leave.ng-leave-active {
24354 <file name="protractor.js" type="protractor">
24355 var templateSelect = element(by.model('template'));
24356 var includeElem = element(by.css('[ng-include]'));
24358 it('should load template1.html', function() {
24359 expect(includeElem.getText()).toMatch(/Content of template1.html/);
24362 it('should load template2.html', function() {
24363 if (browser.params.browser == 'firefox') {
24364 // Firefox can't handle using selects
24365 // See https://github.com/angular/protractor/issues/480
24368 templateSelect.click();
24369 templateSelect.all(by.css('option')).get(2).click();
24370 expect(includeElem.getText()).toMatch(/Content of template2.html/);
24373 it('should change to blank', function() {
24374 if (browser.params.browser == 'firefox') {
24375 // Firefox can't handle using selects
24378 templateSelect.click();
24379 templateSelect.all(by.css('option')).get(0).click();
24380 expect(includeElem.isPresent()).toBe(false);
24389 * @name ngInclude#$includeContentRequested
24390 * @eventType emit on the scope ngInclude was declared in
24392 * Emitted every time the ngInclude content is requested.
24394 * @param {Object} angularEvent Synthetic event object.
24395 * @param {String} src URL of content to load.
24401 * @name ngInclude#$includeContentLoaded
24402 * @eventType emit on the current ngInclude scope
24404 * Emitted every time the ngInclude content is reloaded.
24406 * @param {Object} angularEvent Synthetic event object.
24407 * @param {String} src URL of content to load.
24413 * @name ngInclude#$includeContentError
24414 * @eventType emit on the scope ngInclude was declared in
24416 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299)
24418 * @param {Object} angularEvent Synthetic event object.
24419 * @param {String} src URL of content to load.
24421 var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate',
24422 function($templateRequest, $anchorScroll, $animate) {
24427 transclude: 'element',
24428 controller: angular.noop,
24429 compile: function(element, attr) {
24430 var srcExp = attr.ngInclude || attr.src,
24431 onloadExp = attr.onload || '',
24432 autoScrollExp = attr.autoscroll;
24434 return function(scope, $element, $attr, ctrl, $transclude) {
24435 var changeCounter = 0,
24440 var cleanupLastIncludeContent = function() {
24441 if (previousElement) {
24442 previousElement.remove();
24443 previousElement = null;
24445 if (currentScope) {
24446 currentScope.$destroy();
24447 currentScope = null;
24449 if (currentElement) {
24450 $animate.leave(currentElement).then(function() {
24451 previousElement = null;
24453 previousElement = currentElement;
24454 currentElement = null;
24458 scope.$watch(srcExp, function ngIncludeWatchAction(src) {
24459 var afterAnimation = function() {
24460 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
24464 var thisChangeId = ++changeCounter;
24467 //set the 2nd param to true to ignore the template request error so that the inner
24468 //contents and scope can be cleaned up.
24469 $templateRequest(src, true).then(function(response) {
24470 if (thisChangeId !== changeCounter) return;
24471 var newScope = scope.$new();
24472 ctrl.template = response;
24474 // Note: This will also link all children of ng-include that were contained in the original
24475 // html. If that content contains controllers, ... they could pollute/change the scope.
24476 // However, using ng-include on an element with additional content does not make sense...
24477 // Note: We can't remove them in the cloneAttchFn of $transclude as that
24478 // function is called before linking the content, which would apply child
24479 // directives to non existing elements.
24480 var clone = $transclude(newScope, function(clone) {
24481 cleanupLastIncludeContent();
24482 $animate.enter(clone, null, $element).then(afterAnimation);
24485 currentScope = newScope;
24486 currentElement = clone;
24488 currentScope.$emit('$includeContentLoaded', src);
24489 scope.$eval(onloadExp);
24491 if (thisChangeId === changeCounter) {
24492 cleanupLastIncludeContent();
24493 scope.$emit('$includeContentError', src);
24496 scope.$emit('$includeContentRequested', src);
24498 cleanupLastIncludeContent();
24499 ctrl.template = null;
24507 // This directive is called during the $transclude call of the first `ngInclude` directive.
24508 // It will replace and compile the content of the element with the loaded template.
24509 // We need this directive so that the element content is already filled when
24510 // the link function of another directive on the same element as ngInclude
24512 var ngIncludeFillContentDirective = ['$compile',
24513 function($compile) {
24517 require: 'ngInclude',
24518 link: function(scope, $element, $attr, ctrl) {
24519 if (/SVG/.test($element[0].toString())) {
24520 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not
24521 // support innerHTML, so detect this here and try to generate the contents
24524 $compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope,
24525 function namespaceAdaptedClone(clone) {
24526 $element.append(clone);
24527 }, {futureParentElement: $element});
24531 $element.html(ctrl.template);
24532 $compile($element.contents())(scope);
24543 * The `ngInit` directive allows you to evaluate an expression in the
24546 * <div class="alert alert-danger">
24547 * This directive can be abused to add unnecessary amounts of logic into your templates.
24548 * There are only a few appropriate uses of `ngInit`, such as for aliasing special properties of
24549 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below; and for injecting data via
24550 * server side scripting. Besides these few cases, you should use {@link guide/controller controllers}
24551 * rather than `ngInit` to initialize values on a scope.
24554 * <div class="alert alert-warning">
24555 * **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make
24556 * sure you have parentheses to ensure correct operator precedence:
24557 * <pre class="prettyprint">
24558 * `<div ng-init="test1 = ($index | toString)"></div>`
24565 * @param {expression} ngInit {@link guide/expression Expression} to eval.
24568 <example module="initExample">
24569 <file name="index.html">
24571 angular.module('initExample', [])
24572 .controller('ExampleController', ['$scope', function($scope) {
24573 $scope.list = [['a', 'b'], ['c', 'd']];
24576 <div ng-controller="ExampleController">
24577 <div ng-repeat="innerList in list" ng-init="outerIndex = $index">
24578 <div ng-repeat="value in innerList" ng-init="innerIndex = $index">
24579 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span>
24584 <file name="protractor.js" type="protractor">
24585 it('should alias index positions', function() {
24586 var elements = element.all(by.css('.example-init'));
24587 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;');
24588 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;');
24589 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;');
24590 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;');
24595 var ngInitDirective = ngDirective({
24597 compile: function() {
24599 pre: function(scope, element, attrs) {
24600 scope.$eval(attrs.ngInit);
24611 * Text input that converts between a delimited string and an array of strings. The default
24612 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom
24613 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`.
24615 * The behaviour of the directive is affected by the use of the `ngTrim` attribute.
24616 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each
24617 * list item is respected. This implies that the user of the directive is responsible for
24618 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a
24619 * tab or newline character.
24620 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected
24621 * when joining the list items back together) and whitespace around each list item is stripped
24622 * before it is added to the model.
24624 * ### Example with Validation
24626 * <example name="ngList-directive" module="listExample">
24627 * <file name="app.js">
24628 * angular.module('listExample', [])
24629 * .controller('ExampleController', ['$scope', function($scope) {
24630 * $scope.names = ['morpheus', 'neo', 'trinity'];
24633 * <file name="index.html">
24634 * <form name="myForm" ng-controller="ExampleController">
24635 * <label>List: <input name="namesInput" ng-model="names" ng-list required></label>
24636 * <span role="alert">
24637 * <span class="error" ng-show="myForm.namesInput.$error.required">
24641 * <tt>names = {{names}}</tt><br/>
24642 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
24643 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
24644 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
24645 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
24648 * <file name="protractor.js" type="protractor">
24649 * var listInput = element(by.model('names'));
24650 * var names = element(by.exactBinding('names'));
24651 * var valid = element(by.binding('myForm.namesInput.$valid'));
24652 * var error = element(by.css('span.error'));
24654 * it('should initialize to model', function() {
24655 * expect(names.getText()).toContain('["morpheus","neo","trinity"]');
24656 * expect(valid.getText()).toContain('true');
24657 * expect(error.getCssValue('display')).toBe('none');
24660 * it('should be invalid if empty', function() {
24661 * listInput.clear();
24662 * listInput.sendKeys('');
24664 * expect(names.getText()).toContain('');
24665 * expect(valid.getText()).toContain('false');
24666 * expect(error.getCssValue('display')).not.toBe('none');
24671 * ### Example - splitting on newline
24672 * <example name="ngList-directive-newlines">
24673 * <file name="index.html">
24674 * <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea>
24675 * <pre>{{ list | json }}</pre>
24677 * <file name="protractor.js" type="protractor">
24678 * it("should split the text by newlines", function() {
24679 * var listInput = element(by.model('list'));
24680 * var output = element(by.binding('list | json'));
24681 * listInput.sendKeys('abc\ndef\nghi');
24682 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]');
24688 * @param {string=} ngList optional delimiter that should be used to split the value.
24690 var ngListDirective = function() {
24694 require: 'ngModel',
24695 link: function(scope, element, attr, ctrl) {
24696 // We want to control whitespace trimming so we use this convoluted approach
24697 // to access the ngList attribute, which doesn't pre-trim the attribute
24698 var ngList = element.attr(attr.$attr.ngList) || ', ';
24699 var trimValues = attr.ngTrim !== 'false';
24700 var separator = trimValues ? trim(ngList) : ngList;
24702 var parse = function(viewValue) {
24703 // If the viewValue is invalid (say required but empty) it will be `undefined`
24704 if (isUndefined(viewValue)) return;
24709 forEach(viewValue.split(separator), function(value) {
24710 if (value) list.push(trimValues ? trim(value) : value);
24717 ctrl.$parsers.push(parse);
24718 ctrl.$formatters.push(function(value) {
24719 if (isArray(value)) {
24720 return value.join(ngList);
24726 // Override the standard $isEmpty because an empty array means the input is empty.
24727 ctrl.$isEmpty = function(value) {
24728 return !value || !value.length;
24734 /* global VALID_CLASS: true,
24735 INVALID_CLASS: true,
24736 PRISTINE_CLASS: true,
24738 UNTOUCHED_CLASS: true,
24739 TOUCHED_CLASS: true,
24742 var VALID_CLASS = 'ng-valid',
24743 INVALID_CLASS = 'ng-invalid',
24744 PRISTINE_CLASS = 'ng-pristine',
24745 DIRTY_CLASS = 'ng-dirty',
24746 UNTOUCHED_CLASS = 'ng-untouched',
24747 TOUCHED_CLASS = 'ng-touched',
24748 PENDING_CLASS = 'ng-pending';
24750 var ngModelMinErr = minErr('ngModel');
24754 * @name ngModel.NgModelController
24756 * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a
24757 * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue
24759 * @property {*} $modelValue The value in the model that the control is bound to.
24760 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
24761 the control reads value from the DOM. The functions are called in array order, each passing
24762 its return value through to the next. The last return value is forwarded to the
24763 {@link ngModel.NgModelController#$validators `$validators`} collection.
24765 Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue
24768 Returning `undefined` from a parser means a parse error occurred. In that case,
24769 no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel`
24770 will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`}
24771 is set to `true`. The parse error is stored in `ngModel.$error.parse`.
24774 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
24775 the model value changes. The functions are called in reverse array order, each passing the value through to the
24776 next. The last return value is used as the actual DOM value.
24777 Used to format / convert values for display in the control.
24779 * function formatter(value) {
24781 * return value.toUpperCase();
24784 * ngModel.$formatters.push(formatter);
24787 * @property {Object.<string, function>} $validators A collection of validators that are applied
24788 * whenever the model value changes. The key value within the object refers to the name of the
24789 * validator while the function refers to the validation operation. The validation operation is
24790 * provided with the model value as an argument and must return a true or false value depending
24791 * on the response of that validation.
24794 * ngModel.$validators.validCharacters = function(modelValue, viewValue) {
24795 * var value = modelValue || viewValue;
24796 * return /[0-9]+/.test(value) &&
24797 * /[a-z]+/.test(value) &&
24798 * /[A-Z]+/.test(value) &&
24799 * /\W+/.test(value);
24803 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to
24804 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided
24805 * is expected to return a promise when it is run during the model validation process. Once the promise
24806 * is delivered then the validation status will be set to true when fulfilled and false when rejected.
24807 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model
24808 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator
24809 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators
24810 * will only run once all synchronous validators have passed.
24812 * Please note that if $http is used then it is important that the server returns a success HTTP response code
24813 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation.
24816 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
24817 * var value = modelValue || viewValue;
24819 * // Lookup user by username
24820 * return $http.get('/api/users/' + value).
24821 * then(function resolved() {
24822 * //username exists, this means validation fails
24823 * return $q.reject('exists');
24824 * }, function rejected() {
24825 * //username does not exist, therefore this validation passes
24831 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the
24832 * view value has changed. It is called with no arguments, and its return value is ignored.
24833 * This can be used in place of additional $watches against the model value.
24835 * @property {Object} $error An object hash with all failing validator ids as keys.
24836 * @property {Object} $pending An object hash with all pending validator ids as keys.
24838 * @property {boolean} $untouched True if control has not lost focus yet.
24839 * @property {boolean} $touched True if control has lost focus.
24840 * @property {boolean} $pristine True if user has not interacted with the control yet.
24841 * @property {boolean} $dirty True if user has already interacted with the control.
24842 * @property {boolean} $valid True if there is no error.
24843 * @property {boolean} $invalid True if at least one error on the control.
24844 * @property {string} $name The name attribute of the control.
24848 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive.
24849 * The controller contains services for data-binding, validation, CSS updates, and value formatting
24850 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or
24851 * listening to DOM events.
24852 * Such DOM related logic should be provided by other directives which make use of
24853 * `NgModelController` for data-binding to control elements.
24854 * Angular provides this DOM logic for most {@link input `input`} elements.
24855 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example
24856 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements.
24859 * ### Custom Control Example
24860 * This example shows how to use `NgModelController` with a custom control to achieve
24861 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
24862 * collaborate together to achieve the desired result.
24864 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element
24865 * contents be edited in place by the user.
24867 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize}
24868 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`).
24869 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks
24870 * that content using the `$sce` service.
24872 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js">
24873 <file name="style.css">
24874 [contenteditable] {
24875 border: 1px solid black;
24876 background-color: white;
24881 border: 1px solid red;
24885 <file name="script.js">
24886 angular.module('customControl', ['ngSanitize']).
24887 directive('contenteditable', ['$sce', function($sce) {
24889 restrict: 'A', // only activate on element attribute
24890 require: '?ngModel', // get a hold of NgModelController
24891 link: function(scope, element, attrs, ngModel) {
24892 if (!ngModel) return; // do nothing if no ng-model
24894 // Specify how UI should be updated
24895 ngModel.$render = function() {
24896 element.html($sce.getTrustedHtml(ngModel.$viewValue || ''));
24899 // Listen for change events to enable binding
24900 element.on('blur keyup change', function() {
24901 scope.$evalAsync(read);
24903 read(); // initialize
24905 // Write data to the model
24907 var html = element.html();
24908 // When we clear the content editable the browser leaves a <br> behind
24909 // If strip-br attribute is provided then we strip this out
24910 if ( attrs.stripBr && html == '<br>' ) {
24913 ngModel.$setViewValue(html);
24919 <file name="index.html">
24920 <form name="myForm">
24921 <div contenteditable
24922 name="myWidget" ng-model="userContent"
24924 required>Change me!</div>
24925 <span ng-show="myForm.myWidget.$error.required">Required!</span>
24927 <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea>
24930 <file name="protractor.js" type="protractor">
24931 it('should data-bind and become invalid', function() {
24932 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') {
24933 // SafariDriver can't handle contenteditable
24934 // and Firefox driver can't clear contenteditables very well
24937 var contentEditable = element(by.css('[contenteditable]'));
24938 var content = 'Change me!';
24940 expect(contentEditable.getText()).toEqual(content);
24942 contentEditable.clear();
24943 contentEditable.sendKeys(protractor.Key.BACK_SPACE);
24944 expect(contentEditable.getText()).toEqual('');
24945 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/);
24952 var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate',
24953 function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) {
24954 this.$viewValue = Number.NaN;
24955 this.$modelValue = Number.NaN;
24956 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity.
24957 this.$validators = {};
24958 this.$asyncValidators = {};
24959 this.$parsers = [];
24960 this.$formatters = [];
24961 this.$viewChangeListeners = [];
24962 this.$untouched = true;
24963 this.$touched = false;
24964 this.$pristine = true;
24965 this.$dirty = false;
24966 this.$valid = true;
24967 this.$invalid = false;
24968 this.$error = {}; // keep invalid keys here
24969 this.$$success = {}; // keep valid keys here
24970 this.$pending = undefined; // keep pending keys here
24971 this.$name = $interpolate($attr.name || '', false)($scope);
24972 this.$$parentForm = nullFormCtrl;
24974 var parsedNgModel = $parse($attr.ngModel),
24975 parsedNgModelAssign = parsedNgModel.assign,
24976 ngModelGet = parsedNgModel,
24977 ngModelSet = parsedNgModelAssign,
24978 pendingDebounce = null,
24982 this.$$setOptions = function(options) {
24983 ctrl.$options = options;
24984 if (options && options.getterSetter) {
24985 var invokeModelGetter = $parse($attr.ngModel + '()'),
24986 invokeModelSetter = $parse($attr.ngModel + '($$$p)');
24988 ngModelGet = function($scope) {
24989 var modelValue = parsedNgModel($scope);
24990 if (isFunction(modelValue)) {
24991 modelValue = invokeModelGetter($scope);
24995 ngModelSet = function($scope, newValue) {
24996 if (isFunction(parsedNgModel($scope))) {
24997 invokeModelSetter($scope, {$$$p: ctrl.$modelValue});
24999 parsedNgModelAssign($scope, ctrl.$modelValue);
25002 } else if (!parsedNgModel.assign) {
25003 throw ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}",
25004 $attr.ngModel, startingTag($element));
25010 * @name ngModel.NgModelController#$render
25013 * Called when the view needs to be updated. It is expected that the user of the ng-model
25014 * directive will implement this method.
25016 * The `$render()` method is invoked in the following situations:
25018 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last
25019 * committed value then `$render()` is called to update the input control.
25020 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and
25021 * the `$viewValue` are different from last time.
25023 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of
25024 * `$modelValue` and `$viewValue` are actually different from their previous value. If `$modelValue`
25025 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be
25026 * invoked if you only change a property on the objects.
25028 this.$render = noop;
25032 * @name ngModel.NgModelController#$isEmpty
25035 * This is called when we need to determine if the value of an input is empty.
25037 * For instance, the required directive does this to work out if the input has data or not.
25039 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`.
25041 * You can override this for input directives whose concept of being empty is different from the
25042 * default. The `checkboxInputType` directive does this because in its case a value of `false`
25045 * @param {*} value The value of the input to check for emptiness.
25046 * @returns {boolean} True if `value` is "empty".
25048 this.$isEmpty = function(value) {
25049 return isUndefined(value) || value === '' || value === null || value !== value;
25052 var currentValidationRunId = 0;
25056 * @name ngModel.NgModelController#$setValidity
25059 * Change the validity state, and notify the form.
25061 * This method can be called within $parsers/$formatters or a custom validation implementation.
25062 * However, in most cases it should be sufficient to use the `ngModel.$validators` and
25063 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically.
25065 * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned
25066 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]`
25067 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding.
25068 * The `validationErrorKey` should be in camelCase and will get converted into dash-case
25069 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
25070 * class and can be bound to as `{{someForm.someControl.$error.myError}}` .
25071 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined),
25072 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`.
25073 * Skipped is used by Angular when validators do not run because of parse errors and
25074 * when `$asyncValidators` do not run because any of the `$validators` failed.
25076 addSetValidityMethod({
25078 $element: $element,
25079 set: function(object, property) {
25080 object[property] = true;
25082 unset: function(object, property) {
25083 delete object[property];
25090 * @name ngModel.NgModelController#$setPristine
25093 * Sets the control to its pristine state.
25095 * This method can be called to remove the `ng-dirty` class and set the control to its pristine
25096 * state (`ng-pristine` class). A model is considered to be pristine when the control
25097 * has not been changed from when first compiled.
25099 this.$setPristine = function() {
25100 ctrl.$dirty = false;
25101 ctrl.$pristine = true;
25102 $animate.removeClass($element, DIRTY_CLASS);
25103 $animate.addClass($element, PRISTINE_CLASS);
25108 * @name ngModel.NgModelController#$setDirty
25111 * Sets the control to its dirty state.
25113 * This method can be called to remove the `ng-pristine` class and set the control to its dirty
25114 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed
25115 * from when first compiled.
25117 this.$setDirty = function() {
25118 ctrl.$dirty = true;
25119 ctrl.$pristine = false;
25120 $animate.removeClass($element, PRISTINE_CLASS);
25121 $animate.addClass($element, DIRTY_CLASS);
25122 ctrl.$$parentForm.$setDirty();
25127 * @name ngModel.NgModelController#$setUntouched
25130 * Sets the control to its untouched state.
25132 * This method can be called to remove the `ng-touched` class and set the control to its
25133 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched
25134 * by default, however this function can be used to restore that state if the model has
25135 * already been touched by the user.
25137 this.$setUntouched = function() {
25138 ctrl.$touched = false;
25139 ctrl.$untouched = true;
25140 $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS);
25145 * @name ngModel.NgModelController#$setTouched
25148 * Sets the control to its touched state.
25150 * This method can be called to remove the `ng-untouched` class and set the control to its
25151 * touched state (`ng-touched` class). A model is considered to be touched when the user has
25152 * first focused the control element and then shifted focus away from the control (blur event).
25154 this.$setTouched = function() {
25155 ctrl.$touched = true;
25156 ctrl.$untouched = false;
25157 $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS);
25162 * @name ngModel.NgModelController#$rollbackViewValue
25165 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`,
25166 * which may be caused by a pending debounced event or because the input is waiting for a some
25169 * If you have an input that uses `ng-model-options` to set up debounced events or events such
25170 * as blur you can have a situation where there is a period when the `$viewValue`
25171 * is out of synch with the ngModel's `$modelValue`.
25173 * In this case, you can run into difficulties if you try to update the ngModel's `$modelValue`
25174 * programmatically before these debounced/future events have resolved/occurred, because Angular's
25175 * dirty checking mechanism is not able to tell whether the model has actually changed or not.
25177 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an
25178 * input which may have such events pending. This is important in order to make sure that the
25179 * input field will be updated with the new model value and any pending operations are cancelled.
25181 * <example name="ng-model-cancel-update" module="cancel-update-example">
25182 * <file name="app.js">
25183 * angular.module('cancel-update-example', [])
25185 * .controller('CancelUpdateController', ['$scope', function($scope) {
25186 * $scope.resetWithCancel = function(e) {
25187 * if (e.keyCode == 27) {
25188 * $scope.myForm.myInput1.$rollbackViewValue();
25189 * $scope.myValue = '';
25192 * $scope.resetWithoutCancel = function(e) {
25193 * if (e.keyCode == 27) {
25194 * $scope.myValue = '';
25199 * <file name="index.html">
25200 * <div ng-controller="CancelUpdateController">
25201 * <p>Try typing something in each input. See that the model only updates when you
25202 * blur off the input.
25204 * <p>Now see what happens if you start typing then press the Escape key</p>
25206 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }">
25207 * <p id="inputDescription1">With $rollbackViewValue()</p>
25208 * <input name="myInput1" aria-describedby="inputDescription1" ng-model="myValue"
25209 * ng-keydown="resetWithCancel($event)"><br/>
25210 * myValue: "{{ myValue }}"
25212 * <p id="inputDescription2">Without $rollbackViewValue()</p>
25213 * <input name="myInput2" aria-describedby="inputDescription2" ng-model="myValue"
25214 * ng-keydown="resetWithoutCancel($event)"><br/>
25215 * myValue: "{{ myValue }}"
25221 this.$rollbackViewValue = function() {
25222 $timeout.cancel(pendingDebounce);
25223 ctrl.$viewValue = ctrl.$$lastCommittedViewValue;
25229 * @name ngModel.NgModelController#$validate
25232 * Runs each of the registered validators (first synchronous validators and then
25233 * asynchronous validators).
25234 * If the validity changes to invalid, the model will be set to `undefined`,
25235 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`.
25236 * If the validity changes to valid, it will set the model to the last available valid
25237 * `$modelValue`, i.e. either the last parsed value or the last value set from the scope.
25239 this.$validate = function() {
25240 // ignore $validate before model is initialized
25241 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
25245 var viewValue = ctrl.$$lastCommittedViewValue;
25246 // Note: we use the $$rawModelValue as $modelValue might have been
25247 // set to undefined during a view -> model update that found validation
25248 // errors. We can't parse the view here, since that could change
25249 // the model although neither viewValue nor the model on the scope changed
25250 var modelValue = ctrl.$$rawModelValue;
25252 var prevValid = ctrl.$valid;
25253 var prevModelValue = ctrl.$modelValue;
25255 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
25257 ctrl.$$runValidators(modelValue, viewValue, function(allValid) {
25258 // If there was no change in validity, don't update the model
25259 // This prevents changing an invalid modelValue to undefined
25260 if (!allowInvalid && prevValid !== allValid) {
25261 // Note: Don't check ctrl.$valid here, as we could have
25262 // external validators (e.g. calculated on the server),
25263 // that just call $setValidity and need the model value
25264 // to calculate their validity.
25265 ctrl.$modelValue = allValid ? modelValue : undefined;
25267 if (ctrl.$modelValue !== prevModelValue) {
25268 ctrl.$$writeModelToScope();
25275 this.$$runValidators = function(modelValue, viewValue, doneCallback) {
25276 currentValidationRunId++;
25277 var localValidationRunId = currentValidationRunId;
25279 // check parser error
25280 if (!processParseErrors()) {
25281 validationDone(false);
25284 if (!processSyncValidators()) {
25285 validationDone(false);
25288 processAsyncValidators();
25290 function processParseErrors() {
25291 var errorKey = ctrl.$$parserName || 'parse';
25292 if (isUndefined(parserValid)) {
25293 setValidity(errorKey, null);
25295 if (!parserValid) {
25296 forEach(ctrl.$validators, function(v, name) {
25297 setValidity(name, null);
25299 forEach(ctrl.$asyncValidators, function(v, name) {
25300 setValidity(name, null);
25303 // Set the parse error last, to prevent unsetting it, should a $validators key == parserName
25304 setValidity(errorKey, parserValid);
25305 return parserValid;
25310 function processSyncValidators() {
25311 var syncValidatorsValid = true;
25312 forEach(ctrl.$validators, function(validator, name) {
25313 var result = validator(modelValue, viewValue);
25314 syncValidatorsValid = syncValidatorsValid && result;
25315 setValidity(name, result);
25317 if (!syncValidatorsValid) {
25318 forEach(ctrl.$asyncValidators, function(v, name) {
25319 setValidity(name, null);
25326 function processAsyncValidators() {
25327 var validatorPromises = [];
25328 var allValid = true;
25329 forEach(ctrl.$asyncValidators, function(validator, name) {
25330 var promise = validator(modelValue, viewValue);
25331 if (!isPromiseLike(promise)) {
25332 throw ngModelMinErr("$asyncValidators",
25333 "Expected asynchronous validator to return a promise but got '{0}' instead.", promise);
25335 setValidity(name, undefined);
25336 validatorPromises.push(promise.then(function() {
25337 setValidity(name, true);
25338 }, function(error) {
25340 setValidity(name, false);
25343 if (!validatorPromises.length) {
25344 validationDone(true);
25346 $q.all(validatorPromises).then(function() {
25347 validationDone(allValid);
25352 function setValidity(name, isValid) {
25353 if (localValidationRunId === currentValidationRunId) {
25354 ctrl.$setValidity(name, isValid);
25358 function validationDone(allValid) {
25359 if (localValidationRunId === currentValidationRunId) {
25361 doneCallback(allValid);
25368 * @name ngModel.NgModelController#$commitViewValue
25371 * Commit a pending update to the `$modelValue`.
25373 * Updates may be pending by a debounced event or because the input is waiting for a some future
25374 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController`
25375 * usually handles calling this in response to input events.
25377 this.$commitViewValue = function() {
25378 var viewValue = ctrl.$viewValue;
25380 $timeout.cancel(pendingDebounce);
25382 // If the view value has not changed then we should just exit, except in the case where there is
25383 // a native validator on the element. In this case the validation state may have changed even though
25384 // the viewValue has stayed empty.
25385 if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) {
25388 ctrl.$$lastCommittedViewValue = viewValue;
25391 if (ctrl.$pristine) {
25394 this.$$parseAndValidate();
25397 this.$$parseAndValidate = function() {
25398 var viewValue = ctrl.$$lastCommittedViewValue;
25399 var modelValue = viewValue;
25400 parserValid = isUndefined(modelValue) ? undefined : true;
25403 for (var i = 0; i < ctrl.$parsers.length; i++) {
25404 modelValue = ctrl.$parsers[i](modelValue);
25405 if (isUndefined(modelValue)) {
25406 parserValid = false;
25411 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
25412 // ctrl.$modelValue has not been touched yet...
25413 ctrl.$modelValue = ngModelGet($scope);
25415 var prevModelValue = ctrl.$modelValue;
25416 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
25417 ctrl.$$rawModelValue = modelValue;
25419 if (allowInvalid) {
25420 ctrl.$modelValue = modelValue;
25421 writeToModelIfNeeded();
25424 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
25425 // This can happen if e.g. $setViewValue is called from inside a parser
25426 ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
25427 if (!allowInvalid) {
25428 // Note: Don't check ctrl.$valid here, as we could have
25429 // external validators (e.g. calculated on the server),
25430 // that just call $setValidity and need the model value
25431 // to calculate their validity.
25432 ctrl.$modelValue = allValid ? modelValue : undefined;
25433 writeToModelIfNeeded();
25437 function writeToModelIfNeeded() {
25438 if (ctrl.$modelValue !== prevModelValue) {
25439 ctrl.$$writeModelToScope();
25444 this.$$writeModelToScope = function() {
25445 ngModelSet($scope, ctrl.$modelValue);
25446 forEach(ctrl.$viewChangeListeners, function(listener) {
25450 $exceptionHandler(e);
25457 * @name ngModel.NgModelController#$setViewValue
25460 * Update the view value.
25462 * This method should be called when a control wants to change the view value; typically,
25463 * this is done from within a DOM event handler. For example, the {@link ng.directive:input input}
25464 * directive calls it when the value of the input changes and {@link ng.directive:select select}
25465 * calls it when an option is selected.
25467 * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers`
25468 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged
25469 * value sent directly for processing, finally to be applied to `$modelValue` and then the
25470 * **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners,
25471 * in the `$viewChangeListeners` list, are called.
25473 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn`
25474 * and the `default` trigger is not listed, all those actions will remain pending until one of the
25475 * `updateOn` events is triggered on the DOM element.
25476 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions}
25477 * directive is used with a custom debounce for this particular event.
25478 * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce`
25479 * is specified, once the timer runs out.
25481 * When used with standard inputs, the view value will always be a string (which is in some cases
25482 * parsed into another type, such as a `Date` object for `input[date]`.)
25483 * However, custom controls might also pass objects to this method. In this case, we should make
25484 * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not
25485 * perform a deep watch of objects, it only looks for a change of identity. If you only change
25486 * the property of the object then ngModel will not realise that the object has changed and
25487 * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should
25488 * not change properties of the copy once it has been passed to `$setViewValue`.
25489 * Otherwise you may cause the model value on the scope to change incorrectly.
25491 * <div class="alert alert-info">
25492 * In any case, the value passed to the method should always reflect the current value
25493 * of the control. For example, if you are calling `$setViewValue` for an input element,
25494 * you should pass the input DOM value. Otherwise, the control and the scope model become
25495 * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change
25496 * the control's DOM value in any way. If we want to change the control's DOM value
25497 * programmatically, we should update the `ngModel` scope expression. Its new value will be
25498 * picked up by the model controller, which will run it through the `$formatters`, `$render` it
25499 * to update the DOM, and finally call `$validate` on it.
25502 * @param {*} value value from the view.
25503 * @param {string} trigger Event that triggered the update.
25505 this.$setViewValue = function(value, trigger) {
25506 ctrl.$viewValue = value;
25507 if (!ctrl.$options || ctrl.$options.updateOnDefault) {
25508 ctrl.$$debounceViewValueCommit(trigger);
25512 this.$$debounceViewValueCommit = function(trigger) {
25513 var debounceDelay = 0,
25514 options = ctrl.$options,
25517 if (options && isDefined(options.debounce)) {
25518 debounce = options.debounce;
25519 if (isNumber(debounce)) {
25520 debounceDelay = debounce;
25521 } else if (isNumber(debounce[trigger])) {
25522 debounceDelay = debounce[trigger];
25523 } else if (isNumber(debounce['default'])) {
25524 debounceDelay = debounce['default'];
25528 $timeout.cancel(pendingDebounce);
25529 if (debounceDelay) {
25530 pendingDebounce = $timeout(function() {
25531 ctrl.$commitViewValue();
25533 } else if ($rootScope.$$phase) {
25534 ctrl.$commitViewValue();
25536 $scope.$apply(function() {
25537 ctrl.$commitViewValue();
25543 // Note: we cannot use a normal scope.$watch as we want to detect the following:
25544 // 1. scope value is 'a'
25545 // 2. user enters 'b'
25546 // 3. ng-change kicks in and reverts scope value to 'a'
25547 // -> scope value did not change since the last digest as
25548 // ng-change executes in apply phase
25549 // 4. view should be changed back to 'a'
25550 $scope.$watch(function ngModelWatch() {
25551 var modelValue = ngModelGet($scope);
25553 // if scope model value and ngModel value are out of sync
25554 // TODO(perf): why not move this to the action fn?
25555 if (modelValue !== ctrl.$modelValue &&
25556 // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator
25557 (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue)
25559 ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
25560 parserValid = undefined;
25562 var formatters = ctrl.$formatters,
25563 idx = formatters.length;
25565 var viewValue = modelValue;
25567 viewValue = formatters[idx](viewValue);
25569 if (ctrl.$viewValue !== viewValue) {
25570 ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
25573 ctrl.$$runValidators(modelValue, viewValue, noop);
25590 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a
25591 * property on the scope using {@link ngModel.NgModelController NgModelController},
25592 * which is created and exposed by this directive.
25594 * `ngModel` is responsible for:
25596 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select`
25598 * - Providing validation behavior (i.e. required, number, email, url).
25599 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors).
25600 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations.
25601 * - Registering the control with its parent {@link ng.directive:form form}.
25603 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the
25604 * current scope. If the property doesn't already exist on this scope, it will be created
25605 * implicitly and added to the scope.
25607 * For best practices on using `ngModel`, see:
25609 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes)
25611 * For basic examples, how to use `ngModel`, see:
25613 * - {@link ng.directive:input input}
25614 * - {@link input[text] text}
25615 * - {@link input[checkbox] checkbox}
25616 * - {@link input[radio] radio}
25617 * - {@link input[number] number}
25618 * - {@link input[email] email}
25619 * - {@link input[url] url}
25620 * - {@link input[date] date}
25621 * - {@link input[datetime-local] datetime-local}
25622 * - {@link input[time] time}
25623 * - {@link input[month] month}
25624 * - {@link input[week] week}
25625 * - {@link ng.directive:select select}
25626 * - {@link ng.directive:textarea textarea}
25629 * The following CSS classes are added and removed on the associated input/select/textarea element
25630 * depending on the validity of the model.
25632 * - `ng-valid`: the model is valid
25633 * - `ng-invalid`: the model is invalid
25634 * - `ng-valid-[key]`: for each valid key added by `$setValidity`
25635 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity`
25636 * - `ng-pristine`: the control hasn't been interacted with yet
25637 * - `ng-dirty`: the control has been interacted with
25638 * - `ng-touched`: the control has been blurred
25639 * - `ng-untouched`: the control hasn't been blurred
25640 * - `ng-pending`: any `$asyncValidators` are unfulfilled
25642 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
25644 * ## Animation Hooks
25646 * Animations within models are triggered when any of the associated CSS classes are added and removed
25647 * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`,
25648 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself.
25649 * The animations that are triggered within ngModel are similar to how they work in ngClass and
25650 * animations can be hooked into using CSS transitions, keyframes as well as JS animations.
25652 * The following example shows a simple way to utilize CSS transitions to style an input element
25653 * that has been rendered as invalid after it has been validated:
25656 * //be sure to include ngAnimate as a module to hook into more
25657 * //advanced animations
25659 * transition:0.5s linear all;
25660 * background: white;
25662 * .my-input.ng-invalid {
25669 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample">
25670 <file name="index.html">
25672 angular.module('inputExample', [])
25673 .controller('ExampleController', ['$scope', function($scope) {
25679 transition:all linear 0.5s;
25680 background: transparent;
25682 .my-input.ng-invalid {
25687 <p id="inputDescription">
25688 Update input to see transitions when valid/invalid.
25689 Integer is a valid value.
25691 <form name="testForm" ng-controller="ExampleController">
25692 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input"
25693 aria-describedby="inputDescription" />
25698 * ## Binding to a getter/setter
25700 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a
25701 * function that returns a representation of the model when called with zero arguments, and sets
25702 * the internal state of a model when called with an argument. It's sometimes useful to use this
25703 * for models that have an internal representation that's different from what the model exposes
25706 * <div class="alert alert-success">
25707 * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more
25708 * frequently than other parts of your code.
25711 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that
25712 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to
25713 * a `<form>`, which will enable this behavior for all `<input>`s within it. See
25714 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more.
25716 * The following example shows how to use `ngModel` with a getter/setter:
25719 * <example name="ngModel-getter-setter" module="getterSetterExample">
25720 <file name="index.html">
25721 <div ng-controller="ExampleController">
25722 <form name="userForm">
25724 <input type="text" name="userName"
25725 ng-model="user.name"
25726 ng-model-options="{ getterSetter: true }" />
25729 <pre>user.name = <span ng-bind="user.name()"></span></pre>
25732 <file name="app.js">
25733 angular.module('getterSetterExample', [])
25734 .controller('ExampleController', ['$scope', function($scope) {
25735 var _name = 'Brian';
25737 name: function(newName) {
25738 // Note that newName can be undefined for two reasons:
25739 // 1. Because it is called as a getter and thus called with no arguments
25740 // 2. Because the property should actually be set to undefined. This happens e.g. if the
25741 // input is invalid
25742 return arguments.length ? (_name = newName) : _name;
25749 var ngModelDirective = ['$rootScope', function($rootScope) {
25752 require: ['ngModel', '^?form', '^?ngModelOptions'],
25753 controller: NgModelController,
25754 // Prelink needs to run before any input directive
25755 // so that we can set the NgModelOptions in NgModelController
25756 // before anyone else uses it.
25758 compile: function ngModelCompile(element) {
25759 // Setup initial state of the control
25760 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);
25763 pre: function ngModelPreLink(scope, element, attr, ctrls) {
25764 var modelCtrl = ctrls[0],
25765 formCtrl = ctrls[1] || modelCtrl.$$parentForm;
25767 modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
25769 // notify others, especially parent forms
25770 formCtrl.$addControl(modelCtrl);
25772 attr.$observe('name', function(newValue) {
25773 if (modelCtrl.$name !== newValue) {
25774 modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue);
25778 scope.$on('$destroy', function() {
25779 modelCtrl.$$parentForm.$removeControl(modelCtrl);
25782 post: function ngModelPostLink(scope, element, attr, ctrls) {
25783 var modelCtrl = ctrls[0];
25784 if (modelCtrl.$options && modelCtrl.$options.updateOn) {
25785 element.on(modelCtrl.$options.updateOn, function(ev) {
25786 modelCtrl.$$debounceViewValueCommit(ev && ev.type);
25790 element.on('blur', function(ev) {
25791 if (modelCtrl.$touched) return;
25793 if ($rootScope.$$phase) {
25794 scope.$evalAsync(modelCtrl.$setTouched);
25796 scope.$apply(modelCtrl.$setTouched);
25805 var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
25809 * @name ngModelOptions
25812 * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of
25813 * events that will trigger a model update and/or a debouncing delay so that the actual update only
25814 * takes place when a timer expires; this timer will be reset after another change takes place.
25816 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might
25817 * be different from the value in the actual model. This means that if you update the model you
25818 * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in
25819 * order to make sure it is synchronized with the model and that any debounced action is canceled.
25821 * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`}
25822 * method is by making sure the input is placed inside a form that has a `name` attribute. This is
25823 * important because `form` controllers are published to the related scope under the name in their
25824 * `name` attribute.
25826 * Any pending changes will take place immediately when an enclosing form is submitted via the
25827 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
25828 * to have access to the updated model.
25830 * `ngModelOptions` has an effect on the element it's declared on and its descendants.
25832 * @param {Object} ngModelOptions options to apply to the current model. Valid keys are:
25833 * - `updateOn`: string specifying which event should the input be bound to. You can set several
25834 * events using an space delimited list. There is a special event called `default` that
25835 * matches the default events belonging of the control.
25836 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A
25837 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a
25838 * custom value for each event. For example:
25839 * `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }"`
25840 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did
25841 * not validate correctly instead of the default behavior of setting the model to undefined.
25842 * - `getterSetter`: boolean value which determines whether or not to treat functions bound to
25843 `ngModel` as getters/setters.
25844 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
25845 * `<input type="date">`, `<input type="time">`, ... . It understands UTC/GMT and the
25846 * continental US time zone abbreviations, but for general use, use a time zone offset, for
25847 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
25848 * If not specified, the timezone of the browser will be used.
25852 The following example shows how to override immediate updates. Changes on the inputs within the
25853 form will update the model only when the control loses focus (blur event). If `escape` key is
25854 pressed while the input field is focused, the value is reset to the value in the current model.
25856 <example name="ngModelOptions-directive-blur" module="optionsExample">
25857 <file name="index.html">
25858 <div ng-controller="ExampleController">
25859 <form name="userForm">
25861 <input type="text" name="userName"
25862 ng-model="user.name"
25863 ng-model-options="{ updateOn: 'blur' }"
25864 ng-keyup="cancel($event)" />
25867 <input type="text" ng-model="user.data" />
25870 <pre>user.name = <span ng-bind="user.name"></span></pre>
25871 <pre>user.data = <span ng-bind="user.data"></span></pre>
25874 <file name="app.js">
25875 angular.module('optionsExample', [])
25876 .controller('ExampleController', ['$scope', function($scope) {
25877 $scope.user = { name: 'John', data: '' };
25879 $scope.cancel = function(e) {
25880 if (e.keyCode == 27) {
25881 $scope.userForm.userName.$rollbackViewValue();
25886 <file name="protractor.js" type="protractor">
25887 var model = element(by.binding('user.name'));
25888 var input = element(by.model('user.name'));
25889 var other = element(by.model('user.data'));
25891 it('should allow custom events', function() {
25892 input.sendKeys(' Doe');
25894 expect(model.getText()).toEqual('John');
25896 expect(model.getText()).toEqual('John Doe');
25899 it('should $rollbackViewValue when model changes', function() {
25900 input.sendKeys(' Doe');
25901 expect(input.getAttribute('value')).toEqual('John Doe');
25902 input.sendKeys(protractor.Key.ESCAPE);
25903 expect(input.getAttribute('value')).toEqual('John');
25905 expect(model.getText()).toEqual('John');
25910 This one shows how to debounce model changes. Model will be updated only 1 sec after last change.
25911 If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.
25913 <example name="ngModelOptions-directive-debounce" module="optionsExample">
25914 <file name="index.html">
25915 <div ng-controller="ExampleController">
25916 <form name="userForm">
25918 <input type="text" name="userName"
25919 ng-model="user.name"
25920 ng-model-options="{ debounce: 1000 }" />
25922 <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button>
25925 <pre>user.name = <span ng-bind="user.name"></span></pre>
25928 <file name="app.js">
25929 angular.module('optionsExample', [])
25930 .controller('ExampleController', ['$scope', function($scope) {
25931 $scope.user = { name: 'Igor' };
25936 This one shows how to bind to getter/setters:
25938 <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample">
25939 <file name="index.html">
25940 <div ng-controller="ExampleController">
25941 <form name="userForm">
25943 <input type="text" name="userName"
25944 ng-model="user.name"
25945 ng-model-options="{ getterSetter: true }" />
25948 <pre>user.name = <span ng-bind="user.name()"></span></pre>
25951 <file name="app.js">
25952 angular.module('getterSetterExample', [])
25953 .controller('ExampleController', ['$scope', function($scope) {
25954 var _name = 'Brian';
25956 name: function(newName) {
25957 // Note that newName can be undefined for two reasons:
25958 // 1. Because it is called as a getter and thus called with no arguments
25959 // 2. Because the property should actually be set to undefined. This happens e.g. if the
25960 // input is invalid
25961 return arguments.length ? (_name = newName) : _name;
25968 var ngModelOptionsDirective = function() {
25971 controller: ['$scope', '$attrs', function($scope, $attrs) {
25973 this.$options = copy($scope.$eval($attrs.ngModelOptions));
25974 // Allow adding/overriding bound events
25975 if (isDefined(this.$options.updateOn)) {
25976 this.$options.updateOnDefault = false;
25977 // extract "default" pseudo-event from list of events that can trigger a model update
25978 this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() {
25979 that.$options.updateOnDefault = true;
25983 this.$options.updateOnDefault = true;
25992 function addSetValidityMethod(context) {
25993 var ctrl = context.ctrl,
25994 $element = context.$element,
25997 unset = context.unset,
25998 $animate = context.$animate;
26000 classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS));
26002 ctrl.$setValidity = setValidity;
26004 function setValidity(validationErrorKey, state, controller) {
26005 if (isUndefined(state)) {
26006 createAndSet('$pending', validationErrorKey, controller);
26008 unsetAndCleanup('$pending', validationErrorKey, controller);
26010 if (!isBoolean(state)) {
26011 unset(ctrl.$error, validationErrorKey, controller);
26012 unset(ctrl.$$success, validationErrorKey, controller);
26015 unset(ctrl.$error, validationErrorKey, controller);
26016 set(ctrl.$$success, validationErrorKey, controller);
26018 set(ctrl.$error, validationErrorKey, controller);
26019 unset(ctrl.$$success, validationErrorKey, controller);
26022 if (ctrl.$pending) {
26023 cachedToggleClass(PENDING_CLASS, true);
26024 ctrl.$valid = ctrl.$invalid = undefined;
26025 toggleValidationCss('', null);
26027 cachedToggleClass(PENDING_CLASS, false);
26028 ctrl.$valid = isObjectEmpty(ctrl.$error);
26029 ctrl.$invalid = !ctrl.$valid;
26030 toggleValidationCss('', ctrl.$valid);
26033 // re-read the state as the set/unset methods could have
26034 // combined state in ctrl.$error[validationError] (used for forms),
26035 // where setting/unsetting only increments/decrements the value,
26036 // and does not replace it.
26038 if (ctrl.$pending && ctrl.$pending[validationErrorKey]) {
26039 combinedState = undefined;
26040 } else if (ctrl.$error[validationErrorKey]) {
26041 combinedState = false;
26042 } else if (ctrl.$$success[validationErrorKey]) {
26043 combinedState = true;
26045 combinedState = null;
26048 toggleValidationCss(validationErrorKey, combinedState);
26049 ctrl.$$parentForm.$setValidity(validationErrorKey, combinedState, ctrl);
26052 function createAndSet(name, value, controller) {
26056 set(ctrl[name], value, controller);
26059 function unsetAndCleanup(name, value, controller) {
26061 unset(ctrl[name], value, controller);
26063 if (isObjectEmpty(ctrl[name])) {
26064 ctrl[name] = undefined;
26068 function cachedToggleClass(className, switchValue) {
26069 if (switchValue && !classCache[className]) {
26070 $animate.addClass($element, className);
26071 classCache[className] = true;
26072 } else if (!switchValue && classCache[className]) {
26073 $animate.removeClass($element, className);
26074 classCache[className] = false;
26078 function toggleValidationCss(validationErrorKey, isValid) {
26079 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
26081 cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true);
26082 cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false);
26086 function isObjectEmpty(obj) {
26088 for (var prop in obj) {
26089 if (obj.hasOwnProperty(prop)) {
26099 * @name ngNonBindable
26104 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current
26105 * DOM element. This is useful if the element contains what appears to be Angular directives and
26106 * bindings but which should be ignored by Angular. This could be the case if you have a site that
26107 * displays snippets of code, for instance.
26112 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present,
26113 * but the one wrapped in `ngNonBindable` is left alone.
26117 <file name="index.html">
26118 <div>Normal: {{1 + 2}}</div>
26119 <div ng-non-bindable>Ignored: {{1 + 2}}</div>
26121 <file name="protractor.js" type="protractor">
26122 it('should check ng-non-bindable', function() {
26123 expect(element(by.binding('1 + 2')).getText()).toContain('3');
26124 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/);
26129 var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
26131 /* global jqLiteRemove */
26133 var ngOptionsMinErr = minErr('ngOptions');
26142 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
26143 * elements for the `<select>` element using the array or object obtained by evaluating the
26144 * `ngOptions` comprehension expression.
26146 * In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
26147 * similar result. However, `ngOptions` provides some benefits such as reducing memory and
26148 * increasing speed by not creating a new scope for each repeated instance, as well as providing
26149 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
26150 * comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound
26151 * to a non-string value. This is because an option element can only be bound to string values at
26154 * When an item in the `<select>` menu is selected, the array element or object property
26155 * represented by the selected option will be bound to the model identified by the `ngModel`
26158 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
26159 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
26160 * option. See example below for demonstration.
26162 * ## Complex Models (objects or collections)
26164 * By default, `ngModel` watches the model by reference, not value. This is important to know when
26165 * binding the select to a model that is an object or a collection.
26167 * One issue occurs if you want to preselect an option. For example, if you set
26168 * the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection,
26169 * because the objects are not identical. So by default, you should always reference the item in your collection
26170 * for preselections, e.g.: `$scope.selected = $scope.collection[3]`.
26172 * Another solution is to use a `track by` clause, because then `ngOptions` will track the identity
26173 * of the item not by reference, but by the result of the `track by` expression. For example, if your
26174 * collection items have an id property, you would `track by item.id`.
26176 * A different issue with objects or collections is that ngModel won't detect if an object property or
26177 * a collection item changes. For that reason, `ngOptions` additionally watches the model using
26178 * `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute.
26179 * This allows ngOptions to trigger a re-rendering of the options even if the actual object/collection
26180 * has not changed identity, but only a property on the object or an item in the collection changes.
26182 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection
26183 * if the model is an array). This means that changing a property deeper than the first level inside the
26184 * object/collection will not trigger a re-rendering.
26186 * ## `select` **`as`**
26188 * Using `select` **`as`** will bind the result of the `select` expression to the model, but
26189 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
26190 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression
26191 * is used, the result of that expression will be set as the value of the `option` and `select` elements.
26194 * ### `select` **`as`** and **`track by`**
26196 * <div class="alert alert-warning">
26197 * Be careful when using `select` **`as`** and **`track by`** in the same expression.
26200 * Given this array of items on the $scope:
26203 * $scope.items = [{
26206 * subItem: { name: 'aSubItem' }
26210 * subItem: { name: 'bSubItem' }
26217 * <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select>
26220 * $scope.selected = $scope.items[0];
26223 * but this will not work:
26226 * <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select>
26229 * $scope.selected = $scope.items[0].subItem;
26232 * In both examples, the **`track by`** expression is applied successfully to each `item` in the
26233 * `items` array. Because the selected option has been set programmatically in the controller, the
26234 * **`track by`** expression is also applied to the `ngModel` value. In the first example, the
26235 * `ngModel` value is `items[0]` and the **`track by`** expression evaluates to `items[0].id` with
26236 * no issue. In the second example, the `ngModel` value is `items[0].subItem` and the **`track by`**
26237 * expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value
26238 * is not matched against any `<option>` and the `<select>` appears as having no selected value.
26241 * @param {string} ngModel Assignable angular expression to data-bind to.
26242 * @param {string=} name Property name of the form under which the control is published.
26243 * @param {string=} required The control is considered valid only if value is entered.
26244 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
26245 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
26246 * `required` when you want to data-bind to the `required` attribute.
26247 * @param {comprehension_expression=} ngOptions in one of the following forms:
26249 * * for array data sources:
26250 * * `label` **`for`** `value` **`in`** `array`
26251 * * `select` **`as`** `label` **`for`** `value` **`in`** `array`
26252 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
26253 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array`
26254 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
26255 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
26256 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
26257 * (for including a filter with `track by`)
26258 * * for object data sources:
26259 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
26260 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
26261 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
26262 * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object`
26263 * * `select` **`as`** `label` **`group by`** `group`
26264 * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
26265 * * `select` **`as`** `label` **`disable when`** `disable`
26266 * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
26270 * * `array` / `object`: an expression which evaluates to an array / object to iterate over.
26271 * * `value`: local variable which will refer to each item in the `array` or each property value
26272 * of `object` during iteration.
26273 * * `key`: local variable which will refer to a property name in `object` during iteration.
26274 * * `label`: The result of this expression will be the label for `<option>` element. The
26275 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
26276 * * `select`: The result of this expression will be bound to the model of the parent `<select>`
26277 * element. If not specified, `select` expression will default to `value`.
26278 * * `group`: The result of this expression will be used to group options using the `<optgroup>`
26280 * * `disable`: The result of this expression will be used to disable the rendered `<option>`
26281 * element. Return `true` to disable.
26282 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be
26283 * used to identify the objects in the array. The `trackexpr` will most likely refer to the
26284 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved
26285 * even when the options are recreated (e.g. reloaded from the server).
26288 <example module="selectExample">
26289 <file name="index.html">
26291 angular.module('selectExample', [])
26292 .controller('ExampleController', ['$scope', function($scope) {
26294 {name:'black', shade:'dark'},
26295 {name:'white', shade:'light', notAnOption: true},
26296 {name:'red', shade:'dark'},
26297 {name:'blue', shade:'dark', notAnOption: true},
26298 {name:'yellow', shade:'light', notAnOption: false}
26300 $scope.myColor = $scope.colors[2]; // red
26303 <div ng-controller="ExampleController">
26305 <li ng-repeat="color in colors">
26306 <label>Name: <input ng-model="color.name"></label>
26307 <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label>
26308 <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button>
26311 <button ng-click="colors.push({})">add</button>
26315 <label>Color (null not allowed):
26316 <select ng-model="myColor" ng-options="color.name for color in colors"></select>
26318 <label>Color (null allowed):
26319 <span class="nullable">
26320 <select ng-model="myColor" ng-options="color.name for color in colors">
26321 <option value="">-- choose color --</option>
26323 </span></label><br/>
26325 <label>Color grouped by shade:
26326 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors">
26330 <label>Color grouped by shade, with some disabled:
26331 <select ng-model="myColor"
26332 ng-options="color.name group by color.shade disable when color.notAnOption for color in colors">
26338 Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>.
26341 Currently selected: {{ {selected_color:myColor} }}
26342 <div style="border:solid 1px black; height:20px"
26343 ng-style="{'background-color':myColor.name}">
26347 <file name="protractor.js" type="protractor">
26348 it('should check ng-options', function() {
26349 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red');
26350 element.all(by.model('myColor')).first().click();
26351 element.all(by.css('select[ng-model="myColor"] option')).first().click();
26352 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black');
26353 element(by.css('.nullable select[ng-model="myColor"]')).click();
26354 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click();
26355 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null');
26361 // jshint maxlen: false
26362 // //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555550000000006666666666666660000000777777777777777000000000000000888888888800000000000000000009999999999
26363 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]+?))?$/;
26364 // 1: value expression (valueFn)
26365 // 2: label expression (displayFn)
26366 // 3: group by expression (groupByFn)
26367 // 4: disable when expression (disableWhenFn)
26368 // 5: array item variable name
26369 // 6: object item key variable name
26370 // 7: object item value variable name
26371 // 8: collection expression
26372 // 9: track by expression
26373 // jshint maxlen: 100
26376 var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
26378 function parseOptionsExpression(optionsExp, selectElement, scope) {
26380 var match = optionsExp.match(NG_OPTIONS_REGEXP);
26382 throw ngOptionsMinErr('iexp',
26383 "Expected expression in form of " +
26384 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
26385 " but got '{0}'. Element: {1}",
26386 optionsExp, startingTag(selectElement));
26389 // Extract the parts from the ngOptions expression
26391 // The variable name for the value of the item in the collection
26392 var valueName = match[5] || match[7];
26393 // The variable name for the key of the item in the collection
26394 var keyName = match[6];
26396 // An expression that generates the viewValue for an option if there is a label expression
26397 var selectAs = / as /.test(match[0]) && match[1];
26398 // An expression that is used to track the id of each object in the options collection
26399 var trackBy = match[9];
26400 // An expression that generates the viewValue for an option if there is no label expression
26401 var valueFn = $parse(match[2] ? match[1] : valueName);
26402 var selectAsFn = selectAs && $parse(selectAs);
26403 var viewValueFn = selectAsFn || valueFn;
26404 var trackByFn = trackBy && $parse(trackBy);
26406 // Get the value by which we are going to track the option
26407 // if we have a trackFn then use that (passing scope and locals)
26408 // otherwise just hash the given viewValue
26409 var getTrackByValueFn = trackBy ?
26410 function(value, locals) { return trackByFn(scope, locals); } :
26411 function getHashOfValue(value) { return hashKey(value); };
26412 var getTrackByValue = function(value, key) {
26413 return getTrackByValueFn(value, getLocals(value, key));
26416 var displayFn = $parse(match[2] || match[1]);
26417 var groupByFn = $parse(match[3] || '');
26418 var disableWhenFn = $parse(match[4] || '');
26419 var valuesFn = $parse(match[8]);
26422 var getLocals = keyName ? function(value, key) {
26423 locals[keyName] = key;
26424 locals[valueName] = value;
26426 } : function(value) {
26427 locals[valueName] = value;
26432 function Option(selectValue, viewValue, label, group, disabled) {
26433 this.selectValue = selectValue;
26434 this.viewValue = viewValue;
26435 this.label = label;
26436 this.group = group;
26437 this.disabled = disabled;
26440 function getOptionValuesKeys(optionValues) {
26441 var optionValuesKeys;
26443 if (!keyName && isArrayLike(optionValues)) {
26444 optionValuesKeys = optionValues;
26446 // if object, extract keys, in enumeration order, unsorted
26447 optionValuesKeys = [];
26448 for (var itemKey in optionValues) {
26449 if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') {
26450 optionValuesKeys.push(itemKey);
26454 return optionValuesKeys;
26459 getTrackByValue: getTrackByValue,
26460 getWatchables: $parse(valuesFn, function(optionValues) {
26461 // Create a collection of things that we would like to watch (watchedArray)
26462 // so that they can all be watched using a single $watchCollection
26463 // that only runs the handler once if anything changes
26464 var watchedArray = [];
26465 optionValues = optionValues || [];
26467 var optionValuesKeys = getOptionValuesKeys(optionValues);
26468 var optionValuesLength = optionValuesKeys.length;
26469 for (var index = 0; index < optionValuesLength; index++) {
26470 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
26471 var value = optionValues[key];
26473 var locals = getLocals(optionValues[key], key);
26474 var selectValue = getTrackByValueFn(optionValues[key], locals);
26475 watchedArray.push(selectValue);
26477 // Only need to watch the displayFn if there is a specific label expression
26478 if (match[2] || match[1]) {
26479 var label = displayFn(scope, locals);
26480 watchedArray.push(label);
26483 // Only need to watch the disableWhenFn if there is a specific disable expression
26485 var disableWhen = disableWhenFn(scope, locals);
26486 watchedArray.push(disableWhen);
26489 return watchedArray;
26492 getOptions: function() {
26494 var optionItems = [];
26495 var selectValueMap = {};
26497 // The option values were already computed in the `getWatchables` fn,
26498 // which must have been called to trigger `getOptions`
26499 var optionValues = valuesFn(scope) || [];
26500 var optionValuesKeys = getOptionValuesKeys(optionValues);
26501 var optionValuesLength = optionValuesKeys.length;
26503 for (var index = 0; index < optionValuesLength; index++) {
26504 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
26505 var value = optionValues[key];
26506 var locals = getLocals(value, key);
26507 var viewValue = viewValueFn(scope, locals);
26508 var selectValue = getTrackByValueFn(viewValue, locals);
26509 var label = displayFn(scope, locals);
26510 var group = groupByFn(scope, locals);
26511 var disabled = disableWhenFn(scope, locals);
26512 var optionItem = new Option(selectValue, viewValue, label, group, disabled);
26514 optionItems.push(optionItem);
26515 selectValueMap[selectValue] = optionItem;
26519 items: optionItems,
26520 selectValueMap: selectValueMap,
26521 getOptionFromViewValue: function(value) {
26522 return selectValueMap[getTrackByValue(value)];
26524 getViewValueFromOption: function(option) {
26525 // If the viewValue could be an object that may be mutated by the application,
26526 // we need to make a copy and not return the reference to the value on the option.
26527 return trackBy ? angular.copy(option.viewValue) : option.viewValue;
26535 // we can't just jqLite('<option>') since jqLite is not smart enough
26536 // to create it in <select> and IE barfs otherwise.
26537 var optionTemplate = document.createElement('option'),
26538 optGroupTemplate = document.createElement('optgroup');
26541 function ngOptionsPostLink(scope, selectElement, attr, ctrls) {
26543 // if ngModel is not defined, we don't need to do anything
26544 var ngModelCtrl = ctrls[1];
26545 if (!ngModelCtrl) return;
26547 var selectCtrl = ctrls[0];
26548 var multiple = attr.multiple;
26550 // The emptyOption allows the application developer to provide their own custom "empty"
26551 // option when the viewValue does not match any of the option values.
26553 for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) {
26554 if (children[i].value === '') {
26555 emptyOption = children.eq(i);
26560 var providedEmptyOption = !!emptyOption;
26562 var unknownOption = jqLite(optionTemplate.cloneNode(false));
26563 unknownOption.val('?');
26566 var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope);
26569 var renderEmptyOption = function() {
26570 if (!providedEmptyOption) {
26571 selectElement.prepend(emptyOption);
26573 selectElement.val('');
26574 emptyOption.prop('selected', true); // needed for IE
26575 emptyOption.attr('selected', true);
26578 var removeEmptyOption = function() {
26579 if (!providedEmptyOption) {
26580 emptyOption.remove();
26585 var renderUnknownOption = function() {
26586 selectElement.prepend(unknownOption);
26587 selectElement.val('?');
26588 unknownOption.prop('selected', true); // needed for IE
26589 unknownOption.attr('selected', true);
26592 var removeUnknownOption = function() {
26593 unknownOption.remove();
26596 // Update the controller methods for multiple selectable options
26599 selectCtrl.writeValue = function writeNgOptionsValue(value) {
26600 var option = options.getOptionFromViewValue(value);
26602 if (option && !option.disabled) {
26603 if (selectElement[0].value !== option.selectValue) {
26604 removeUnknownOption();
26605 removeEmptyOption();
26607 selectElement[0].value = option.selectValue;
26608 option.element.selected = true;
26609 option.element.setAttribute('selected', 'selected');
26612 if (value === null || providedEmptyOption) {
26613 removeUnknownOption();
26614 renderEmptyOption();
26616 removeEmptyOption();
26617 renderUnknownOption();
26622 selectCtrl.readValue = function readNgOptionsValue() {
26624 var selectedOption = options.selectValueMap[selectElement.val()];
26626 if (selectedOption && !selectedOption.disabled) {
26627 removeEmptyOption();
26628 removeUnknownOption();
26629 return options.getViewValueFromOption(selectedOption);
26634 // If we are using `track by` then we must watch the tracked value on the model
26635 // since ngModel only watches for object identity change
26636 if (ngOptions.trackBy) {
26638 function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); },
26639 function() { ngModelCtrl.$render(); }
26645 ngModelCtrl.$isEmpty = function(value) {
26646 return !value || value.length === 0;
26650 selectCtrl.writeValue = function writeNgOptionsMultiple(value) {
26651 options.items.forEach(function(option) {
26652 option.element.selected = false;
26656 value.forEach(function(item) {
26657 var option = options.getOptionFromViewValue(item);
26658 if (option && !option.disabled) option.element.selected = true;
26664 selectCtrl.readValue = function readNgOptionsMultiple() {
26665 var selectedValues = selectElement.val() || [],
26668 forEach(selectedValues, function(value) {
26669 var option = options.selectValueMap[value];
26670 if (option && !option.disabled) selections.push(options.getViewValueFromOption(option));
26676 // If we are using `track by` then we must watch these tracked values on the model
26677 // since ngModel only watches for object identity change
26678 if (ngOptions.trackBy) {
26680 scope.$watchCollection(function() {
26681 if (isArray(ngModelCtrl.$viewValue)) {
26682 return ngModelCtrl.$viewValue.map(function(value) {
26683 return ngOptions.getTrackByValue(value);
26687 ngModelCtrl.$render();
26694 if (providedEmptyOption) {
26696 // we need to remove it before calling selectElement.empty() because otherwise IE will
26697 // remove the label from the element. wtf?
26698 emptyOption.remove();
26700 // compile the element since there might be bindings in it
26701 $compile(emptyOption)(scope);
26703 // remove the class, which is added automatically because we recompile the element and it
26704 // becomes the compilation root
26705 emptyOption.removeClass('ng-scope');
26707 emptyOption = jqLite(optionTemplate.cloneNode(false));
26710 // We need to do this here to ensure that the options object is defined
26711 // when we first hit it in writeNgOptionsValue
26714 // We will re-render the option elements if the option values or labels change
26715 scope.$watchCollection(ngOptions.getWatchables, updateOptions);
26717 // ------------------------------------------------------------------ //
26720 function updateOptionElement(option, element) {
26721 option.element = element;
26722 element.disabled = option.disabled;
26723 // NOTE: The label must be set before the value, otherwise IE10/11/EDGE create unresponsive
26724 // selects in certain circumstances when multiple selects are next to each other and display
26725 // the option list in listbox style, i.e. the select is [multiple], or specifies a [size].
26726 // See https://github.com/angular/angular.js/issues/11314 for more info.
26727 // This is unfortunately untestable with unit / e2e tests
26728 if (option.label !== element.label) {
26729 element.label = option.label;
26730 element.textContent = option.label;
26732 if (option.value !== element.value) element.value = option.selectValue;
26735 function addOrReuseElement(parent, current, type, templateElement) {
26737 // Check whether we can reuse the next element
26738 if (current && lowercase(current.nodeName) === type) {
26739 // The next element is the right type so reuse it
26742 // The next element is not the right type so create a new one
26743 element = templateElement.cloneNode(false);
26745 // There are no more elements so just append it to the select
26746 parent.appendChild(element);
26748 // The next element is not a group so insert the new one
26749 parent.insertBefore(element, current);
26756 function removeExcessElements(current) {
26759 next = current.nextSibling;
26760 jqLiteRemove(current);
26766 function skipEmptyAndUnknownOptions(current) {
26767 var emptyOption_ = emptyOption && emptyOption[0];
26768 var unknownOption_ = unknownOption && unknownOption[0];
26770 // We cannot rely on the extracted empty option being the same as the compiled empty option,
26771 // because the compiled empty option might have been replaced by a comment because
26772 // it had an "element" transclusion directive on it (such as ngIf)
26773 if (emptyOption_ || unknownOption_) {
26775 (current === emptyOption_ ||
26776 current === unknownOption_ ||
26777 current.nodeType === NODE_TYPE_COMMENT ||
26778 current.value === '')) {
26779 current = current.nextSibling;
26786 function updateOptions() {
26788 var previousValue = options && selectCtrl.readValue();
26790 options = ngOptions.getOptions();
26793 var currentElement = selectElement[0].firstChild;
26795 // Ensure that the empty option is always there if it was explicitly provided
26796 if (providedEmptyOption) {
26797 selectElement.prepend(emptyOption);
26800 currentElement = skipEmptyAndUnknownOptions(currentElement);
26802 options.items.forEach(function updateOption(option) {
26807 if (option.group) {
26809 // This option is to live in a group
26810 // See if we have already created this group
26811 group = groupMap[option.group];
26815 // We have not already created this group
26816 groupElement = addOrReuseElement(selectElement[0],
26820 // Move to the next element
26821 currentElement = groupElement.nextSibling;
26823 // Update the label on the group element
26824 groupElement.label = option.group;
26826 // Store it for use later
26827 group = groupMap[option.group] = {
26828 groupElement: groupElement,
26829 currentOptionElement: groupElement.firstChild
26834 // So now we have a group for this option we add the option to the group
26835 optionElement = addOrReuseElement(group.groupElement,
26836 group.currentOptionElement,
26839 updateOptionElement(option, optionElement);
26840 // Move to the next element
26841 group.currentOptionElement = optionElement.nextSibling;
26845 // This option is not in a group
26846 optionElement = addOrReuseElement(selectElement[0],
26850 updateOptionElement(option, optionElement);
26851 // Move to the next element
26852 currentElement = optionElement.nextSibling;
26857 // Now remove all excess options and group
26858 Object.keys(groupMap).forEach(function(key) {
26859 removeExcessElements(groupMap[key].currentOptionElement);
26861 removeExcessElements(currentElement);
26863 ngModelCtrl.$render();
26865 // Check to see if the value has changed due to the update to the options
26866 if (!ngModelCtrl.$isEmpty(previousValue)) {
26867 var nextValue = selectCtrl.readValue();
26868 if (ngOptions.trackBy ? !equals(previousValue, nextValue) : previousValue !== nextValue) {
26869 ngModelCtrl.$setViewValue(nextValue);
26870 ngModelCtrl.$render();
26880 require: ['select', '?ngModel'],
26882 pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) {
26883 // Deactivate the SelectController.register method to prevent
26884 // option directives from accidentally registering themselves
26885 // (and unwanted $destroy handlers etc.)
26886 ctrls[0].registerOption = noop;
26888 post: ngOptionsPostLink
26895 * @name ngPluralize
26899 * `ngPluralize` is a directive that displays messages according to en-US localization rules.
26900 * These rules are bundled with angular.js, but can be overridden
26901 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive
26902 * by specifying the mappings between
26903 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
26904 * and the strings to be displayed.
26906 * # Plural categories and explicit number rules
26908 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
26909 * in Angular's default en-US locale: "one" and "other".
26911 * While a plural category may match many numbers (for example, in en-US locale, "other" can match
26912 * any number that is not 1), an explicit number rule can only match one number. For example, the
26913 * explicit number rule for "3" matches the number 3. There are examples of plural categories
26914 * and explicit number rules throughout the rest of this documentation.
26916 * # Configuring ngPluralize
26917 * You configure ngPluralize by providing 2 attributes: `count` and `when`.
26918 * You can also provide an optional attribute, `offset`.
26920 * The value of the `count` attribute can be either a string or an {@link guide/expression
26921 * Angular expression}; these are evaluated on the current scope for its bound value.
26923 * The `when` attribute specifies the mappings between plural categories and the actual
26924 * string to be displayed. The value of the attribute should be a JSON object.
26926 * The following example shows how to configure ngPluralize:
26929 * <ng-pluralize count="personCount"
26930 when="{'0': 'Nobody is viewing.',
26931 * 'one': '1 person is viewing.',
26932 * 'other': '{} people are viewing.'}">
26936 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not
26937 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing"
26938 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for
26939 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can
26940 * show "a dozen people are viewing".
26942 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted
26943 * into pluralized strings. In the previous example, Angular will replace `{}` with
26944 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
26945 * for <span ng-non-bindable>{{numberExpression}}</span>.
26947 * If no rule is defined for a category, then an empty string is displayed and a warning is generated.
26948 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`.
26950 * # Configuring ngPluralize with offset
26951 * The `offset` attribute allows further customization of pluralized text, which can result in
26952 * a better user experience. For example, instead of the message "4 people are viewing this document",
26953 * you might display "John, Kate and 2 others are viewing this document".
26954 * The offset attribute allows you to offset a number by any desired value.
26955 * Let's take a look at an example:
26958 * <ng-pluralize count="personCount" offset=2
26959 * when="{'0': 'Nobody is viewing.',
26960 * '1': '{{person1}} is viewing.',
26961 * '2': '{{person1}} and {{person2}} are viewing.',
26962 * 'one': '{{person1}}, {{person2}} and one other person are viewing.',
26963 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
26967 * Notice that we are still using two plural categories(one, other), but we added
26968 * three explicit number rules 0, 1 and 2.
26969 * When one person, perhaps John, views the document, "John is viewing" will be shown.
26970 * When three people view the document, no explicit number rule is found, so
26971 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category.
26972 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing"
26975 * Note that when you specify offsets, you must provide explicit number rules for
26976 * numbers from 0 up to and including the offset. If you use an offset of 3, for example,
26977 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for
26978 * plural categories "one" and "other".
26980 * @param {string|expression} count The variable to be bound to.
26981 * @param {string} when The mapping between plural category to its corresponding strings.
26982 * @param {number=} offset Offset to deduct from the total number.
26985 <example module="pluralizeExample">
26986 <file name="index.html">
26988 angular.module('pluralizeExample', [])
26989 .controller('ExampleController', ['$scope', function($scope) {
26990 $scope.person1 = 'Igor';
26991 $scope.person2 = 'Misko';
26992 $scope.personCount = 1;
26995 <div ng-controller="ExampleController">
26996 <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/>
26997 <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/>
26998 <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/>
27000 <!--- Example with simple pluralization rules for en locale --->
27002 <ng-pluralize count="personCount"
27003 when="{'0': 'Nobody is viewing.',
27004 'one': '1 person is viewing.',
27005 'other': '{} people are viewing.'}">
27006 </ng-pluralize><br>
27008 <!--- Example with offset --->
27010 <ng-pluralize count="personCount" offset=2
27011 when="{'0': 'Nobody is viewing.',
27012 '1': '{{person1}} is viewing.',
27013 '2': '{{person1}} and {{person2}} are viewing.',
27014 'one': '{{person1}}, {{person2}} and one other person are viewing.',
27015 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
27019 <file name="protractor.js" type="protractor">
27020 it('should show correct pluralized string', function() {
27021 var withoutOffset = element.all(by.css('ng-pluralize')).get(0);
27022 var withOffset = element.all(by.css('ng-pluralize')).get(1);
27023 var countInput = element(by.model('personCount'));
27025 expect(withoutOffset.getText()).toEqual('1 person is viewing.');
27026 expect(withOffset.getText()).toEqual('Igor is viewing.');
27028 countInput.clear();
27029 countInput.sendKeys('0');
27031 expect(withoutOffset.getText()).toEqual('Nobody is viewing.');
27032 expect(withOffset.getText()).toEqual('Nobody is viewing.');
27034 countInput.clear();
27035 countInput.sendKeys('2');
27037 expect(withoutOffset.getText()).toEqual('2 people are viewing.');
27038 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.');
27040 countInput.clear();
27041 countInput.sendKeys('3');
27043 expect(withoutOffset.getText()).toEqual('3 people are viewing.');
27044 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.');
27046 countInput.clear();
27047 countInput.sendKeys('4');
27049 expect(withoutOffset.getText()).toEqual('4 people are viewing.');
27050 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.');
27052 it('should show data-bound names', function() {
27053 var withOffset = element.all(by.css('ng-pluralize')).get(1);
27054 var personCount = element(by.model('personCount'));
27055 var person1 = element(by.model('person1'));
27056 var person2 = element(by.model('person2'));
27057 personCount.clear();
27058 personCount.sendKeys('4');
27060 person1.sendKeys('Di');
27062 person2.sendKeys('Vojta');
27063 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.');
27068 var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) {
27070 IS_WHEN = /^when(Minus)?(.+)$/;
27073 link: function(scope, element, attr) {
27074 var numberExp = attr.count,
27075 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs
27076 offset = attr.offset || 0,
27077 whens = scope.$eval(whenExp) || {},
27079 startSymbol = $interpolate.startSymbol(),
27080 endSymbol = $interpolate.endSymbol(),
27081 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol,
27082 watchRemover = angular.noop,
27085 forEach(attr, function(expression, attributeName) {
27086 var tmpMatch = IS_WHEN.exec(attributeName);
27088 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]);
27089 whens[whenKey] = element.attr(attr.$attr[attributeName]);
27092 forEach(whens, function(expression, key) {
27093 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement));
27097 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) {
27098 var count = parseFloat(newVal);
27099 var countIsNaN = isNaN(count);
27101 if (!countIsNaN && !(count in whens)) {
27102 // If an explicit number rule such as 1, 2, 3... is defined, just use it.
27103 // Otherwise, check it against pluralization rules in $locale service.
27104 count = $locale.pluralCat(count - offset);
27107 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch.
27108 // In JS `NaN !== NaN`, so we have to exlicitly check.
27109 if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) {
27111 var whenExpFn = whensExpFns[count];
27112 if (isUndefined(whenExpFn)) {
27113 if (newVal != null) {
27114 $log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp);
27116 watchRemover = noop;
27117 updateElementText();
27119 watchRemover = scope.$watch(whenExpFn, updateElementText);
27125 function updateElementText(newText) {
27126 element.text(newText || '');
27138 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template
27139 * instance gets its own scope, where the given loop variable is set to the current collection item,
27140 * and `$index` is set to the item index or key.
27142 * Special properties are exposed on the local scope of each template instance, including:
27144 * | Variable | Type | Details |
27145 * |-----------|-----------------|-----------------------------------------------------------------------------|
27146 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) |
27147 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. |
27148 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. |
27149 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. |
27150 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). |
27151 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). |
27153 * <div class="alert alert-info">
27154 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
27155 * This may be useful when, for instance, nesting ngRepeats.
27159 * # Iterating over object properties
27161 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following
27165 * <div ng-repeat="(key, value) in myObj"> ... </div>
27168 * You need to be aware that the JavaScript specification does not define the order of keys
27169 * returned for an object. (To mitigate this in Angular 1.3 the `ngRepeat` directive
27170 * used to sort the keys alphabetically.)
27172 * Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser
27173 * when running `for key in myObj`. It seems that browsers generally follow the strategy of providing
27174 * keys in the order in which they were defined, although there are exceptions when keys are deleted
27175 * 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).
27177 * If this is not desired, the recommended workaround is to convert your object into an array
27178 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
27179 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter)
27180 * or implement a `$watch` on the object yourself.
27183 * # Tracking and Duplicates
27185 * `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in
27186 * the collection. When a change happens, ngRepeat then makes the corresponding changes to the DOM:
27188 * * When an item is added, a new instance of the template is added to the DOM.
27189 * * When an item is removed, its template instance is removed from the DOM.
27190 * * When items are reordered, their respective templates are reordered in the DOM.
27192 * To minimize creation of DOM elements, `ngRepeat` uses a function
27193 * to "keep track" of all items in the collection and their corresponding DOM elements.
27194 * For example, if an item is added to the collection, ngRepeat will know that all other items
27195 * already have DOM elements, and will not re-render them.
27197 * The default tracking function (which tracks items by their identity) does not allow
27198 * duplicate items in arrays. This is because when there are duplicates, it is not possible
27199 * to maintain a one-to-one mapping between collection items and DOM elements.
27201 * If you do need to repeat duplicate items, you can substitute the default tracking behavior
27202 * with your own using the `track by` expression.
27204 * For example, you may track items by the index of each item in the collection, using the
27205 * special scope property `$index`:
27207 * <div ng-repeat="n in [42, 42, 43, 43] track by $index">
27212 * You may also use arbitrary expressions in `track by`, including references to custom functions
27215 * <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)">
27220 * <div class="alert alert-success">
27221 * If you are working with objects that have an identifier property, you should track
27222 * by the identifier instead of the whole object. Should you reload your data later, `ngRepeat`
27223 * will not have to rebuild the DOM elements for items it has already rendered, even if the
27224 * JavaScript objects in the collection have been substituted for new ones. For large collections,
27225 * this signifincantly improves rendering performance. If you don't have a unique identifier,
27226 * `track by $index` can also provide a performance boost.
27229 * <div ng-repeat="model in collection track by model.id">
27234 * When no `track by` expression is provided, it is equivalent to tracking by the built-in
27235 * `$id` function, which tracks items by their identity:
27237 * <div ng-repeat="obj in collection track by $id(obj)">
27242 * <div class="alert alert-warning">
27243 * **Note:** `track by` must always be the last expression:
27246 * <div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id">
27251 * # Special repeat start and end points
27252 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
27253 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively.
27254 * 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)
27255 * up to and including the ending HTML tag where **ng-repeat-end** is placed.
27257 * The example below makes use of this feature:
27259 * <header ng-repeat-start="item in items">
27260 * Header {{ item }}
27262 * <div class="body">
27265 * <footer ng-repeat-end>
27266 * Footer {{ item }}
27270 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to:
27275 * <div class="body">
27284 * <div class="body">
27292 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such
27293 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**).
27296 * **.enter** - when a new item is added to the list or when an item is revealed after a filter
27298 * **.leave** - when an item is removed from the list or when an item is filtered out
27300 * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered
27305 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These
27306 * formats are currently supported:
27308 * * `variable in expression` – where variable is the user defined loop variable and `expression`
27309 * is a scope expression giving the collection to enumerate.
27311 * For example: `album in artist.albums`.
27313 * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers,
27314 * and `expression` is the scope expression giving the collection to enumerate.
27316 * For example: `(name, age) in {'adam':10, 'amalie':12}`.
27318 * * `variable in expression track by tracking_expression` – You can also provide an optional tracking expression
27319 * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression
27320 * is specified, ng-repeat associates elements by identity. It is an error to have
27321 * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are
27322 * mapped to the same DOM element, which is not possible.)
27324 * Note that the tracking expression must come last, after any filters, and the alias expression.
27326 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements
27327 * will be associated by item identity in the array.
27329 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique
27330 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements
27331 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM
27332 * element in the same way in the DOM.
27334 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this
27335 * case the object identity does not matter. Two objects are considered equivalent as long as their `id`
27336 * property is same.
27338 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter
27339 * to items in conjunction with a tracking expression.
27341 * * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the
27342 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message
27343 * when a filter is active on the repeater, but the filtered result set is empty.
27345 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after
27346 * the items have been processed through the filter.
27348 * 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
27349 * (and not as operator, inside an expression).
27351 * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` .
27354 * This example initializes the scope to a list of names and
27355 * then uses `ngRepeat` to display every person:
27356 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27357 <file name="index.html">
27358 <div ng-init="friends = [
27359 {name:'John', age:25, gender:'boy'},
27360 {name:'Jessie', age:30, gender:'girl'},
27361 {name:'Johanna', age:28, gender:'girl'},
27362 {name:'Joy', age:15, gender:'girl'},
27363 {name:'Mary', age:28, gender:'girl'},
27364 {name:'Peter', age:95, gender:'boy'},
27365 {name:'Sebastian', age:50, gender:'boy'},
27366 {name:'Erika', age:27, gender:'girl'},
27367 {name:'Patrick', age:40, gender:'boy'},
27368 {name:'Samantha', age:60, gender:'girl'}
27370 I have {{friends.length}} friends. They are:
27371 <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" />
27372 <ul class="example-animate-container">
27373 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results">
27374 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
27376 <li class="animate-repeat" ng-if="results.length == 0">
27377 <strong>No results found...</strong>
27382 <file name="animations.css">
27383 .example-animate-container {
27385 border:1px solid black;
27394 box-sizing:border-box;
27397 .animate-repeat.ng-move,
27398 .animate-repeat.ng-enter,
27399 .animate-repeat.ng-leave {
27400 transition:all linear 0.5s;
27403 .animate-repeat.ng-leave.ng-leave-active,
27404 .animate-repeat.ng-move,
27405 .animate-repeat.ng-enter {
27410 .animate-repeat.ng-leave,
27411 .animate-repeat.ng-move.ng-move-active,
27412 .animate-repeat.ng-enter.ng-enter-active {
27417 <file name="protractor.js" type="protractor">
27418 var friends = element.all(by.repeater('friend in friends'));
27420 it('should render initial data set', function() {
27421 expect(friends.count()).toBe(10);
27422 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.');
27423 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.');
27424 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.');
27425 expect(element(by.binding('friends.length')).getText())
27426 .toMatch("I have 10 friends. They are:");
27429 it('should update repeater when filter predicate changes', function() {
27430 expect(friends.count()).toBe(10);
27432 element(by.model('q')).sendKeys('ma');
27434 expect(friends.count()).toBe(2);
27435 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.');
27436 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.');
27441 var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
27442 var NG_REMOVED = '$$NG_REMOVED';
27443 var ngRepeatMinErr = minErr('ngRepeat');
27445 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) {
27446 // TODO(perf): generate setters to shave off ~40ms or 1-1.5%
27447 scope[valueIdentifier] = value;
27448 if (keyIdentifier) scope[keyIdentifier] = key;
27449 scope.$index = index;
27450 scope.$first = (index === 0);
27451 scope.$last = (index === (arrayLength - 1));
27452 scope.$middle = !(scope.$first || scope.$last);
27453 // jshint bitwise: false
27454 scope.$odd = !(scope.$even = (index&1) === 0);
27455 // jshint bitwise: true
27458 var getBlockStart = function(block) {
27459 return block.clone[0];
27462 var getBlockEnd = function(block) {
27463 return block.clone[block.clone.length - 1];
27469 multiElement: true,
27470 transclude: 'element',
27474 compile: function ngRepeatCompile($element, $attr) {
27475 var expression = $attr.ngRepeat;
27476 var ngRepeatEndComment = document.createComment(' end ngRepeat: ' + expression + ' ');
27478 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*$/);
27481 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.",
27485 var lhs = match[1];
27486 var rhs = match[2];
27487 var aliasAs = match[3];
27488 var trackByExp = match[4];
27490 match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);
27493 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.",
27496 var valueIdentifier = match[3] || match[1];
27497 var keyIdentifier = match[2];
27499 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) ||
27500 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) {
27501 throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.",
27505 var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn;
27506 var hashFnLocals = {$id: hashKey};
27509 trackByExpGetter = $parse(trackByExp);
27511 trackByIdArrayFn = function(key, value) {
27512 return hashKey(value);
27514 trackByIdObjFn = function(key) {
27519 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) {
27521 if (trackByExpGetter) {
27522 trackByIdExpFn = function(key, value, index) {
27523 // assign key, value, and $index to the locals so that they can be used in hash functions
27524 if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
27525 hashFnLocals[valueIdentifier] = value;
27526 hashFnLocals.$index = index;
27527 return trackByExpGetter($scope, hashFnLocals);
27531 // Store a list of elements from previous run. This is a hash where key is the item from the
27532 // iterator, and the value is objects with following properties.
27533 // - scope: bound scope
27534 // - element: previous element.
27535 // - index: position
27537 // We are using no-proto object so that we don't need to guard against inherited props via
27539 var lastBlockMap = createMap();
27542 $scope.$watchCollection(rhs, function ngRepeatAction(collection) {
27544 previousNode = $element[0], // node that cloned nodes should be inserted after
27545 // initialized to the comment node anchor
27547 // Same as lastBlockMap but it has the current state. It will become the
27548 // lastBlockMap on the next iteration.
27549 nextBlockMap = createMap(),
27551 key, value, // key/value of iteration
27555 block, // last object information {scope, element, id}
27560 $scope[aliasAs] = collection;
27563 if (isArrayLike(collection)) {
27564 collectionKeys = collection;
27565 trackByIdFn = trackByIdExpFn || trackByIdArrayFn;
27567 trackByIdFn = trackByIdExpFn || trackByIdObjFn;
27568 // if object, extract keys, in enumeration order, unsorted
27569 collectionKeys = [];
27570 for (var itemKey in collection) {
27571 if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') {
27572 collectionKeys.push(itemKey);
27577 collectionLength = collectionKeys.length;
27578 nextBlockOrder = new Array(collectionLength);
27580 // locate existing items
27581 for (index = 0; index < collectionLength; index++) {
27582 key = (collection === collectionKeys) ? index : collectionKeys[index];
27583 value = collection[key];
27584 trackById = trackByIdFn(key, value, index);
27585 if (lastBlockMap[trackById]) {
27586 // found previously seen block
27587 block = lastBlockMap[trackById];
27588 delete lastBlockMap[trackById];
27589 nextBlockMap[trackById] = block;
27590 nextBlockOrder[index] = block;
27591 } else if (nextBlockMap[trackById]) {
27592 // if collision detected. restore lastBlockMap and throw an error
27593 forEach(nextBlockOrder, function(block) {
27594 if (block && block.scope) lastBlockMap[block.id] = block;
27596 throw ngRepeatMinErr('dupes',
27597 "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}",
27598 expression, trackById, value);
27600 // new never before seen block
27601 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined};
27602 nextBlockMap[trackById] = true;
27606 // remove leftover items
27607 for (var blockKey in lastBlockMap) {
27608 block = lastBlockMap[blockKey];
27609 elementsToRemove = getBlockNodes(block.clone);
27610 $animate.leave(elementsToRemove);
27611 if (elementsToRemove[0].parentNode) {
27612 // if the element was not removed yet because of pending animation, mark it as deleted
27613 // so that we can ignore it later
27614 for (index = 0, length = elementsToRemove.length; index < length; index++) {
27615 elementsToRemove[index][NG_REMOVED] = true;
27618 block.scope.$destroy();
27621 // we are not using forEach for perf reasons (trying to avoid #call)
27622 for (index = 0; index < collectionLength; index++) {
27623 key = (collection === collectionKeys) ? index : collectionKeys[index];
27624 value = collection[key];
27625 block = nextBlockOrder[index];
27628 // if we have already seen this object, then we need to reuse the
27629 // associated scope/element
27631 nextNode = previousNode;
27633 // skip nodes that are already pending removal via leave animation
27635 nextNode = nextNode.nextSibling;
27636 } while (nextNode && nextNode[NG_REMOVED]);
27638 if (getBlockStart(block) != nextNode) {
27639 // existing item which got moved
27640 $animate.move(getBlockNodes(block.clone), null, jqLite(previousNode));
27642 previousNode = getBlockEnd(block);
27643 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
27645 // new item which we don't know about
27646 $transclude(function ngRepeatTransclude(clone, scope) {
27647 block.scope = scope;
27648 // http://jsperf.com/clone-vs-createcomment
27649 var endNode = ngRepeatEndComment.cloneNode(false);
27650 clone[clone.length++] = endNode;
27652 // TODO(perf): support naked previousNode in `enter` to avoid creation of jqLite wrapper?
27653 $animate.enter(clone, null, jqLite(previousNode));
27654 previousNode = endNode;
27655 // Note: We only need the first/last node of the cloned nodes.
27656 // However, we need to keep the reference to the jqlite wrapper as it might be changed later
27657 // by a directive with templateUrl when its template arrives.
27658 block.clone = clone;
27659 nextBlockMap[block.id] = block;
27660 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
27664 lastBlockMap = nextBlockMap;
27671 var NG_HIDE_CLASS = 'ng-hide';
27672 var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
27679 * The `ngShow` directive shows or hides the given HTML element based on the expression
27680 * provided to the `ngShow` attribute. The element is shown or hidden by removing or adding
27681 * the `.ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined
27682 * in AngularJS and sets the display style to none (using an !important flag).
27683 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
27686 * <!-- when $scope.myValue is truthy (element is visible) -->
27687 * <div ng-show="myValue"></div>
27689 * <!-- when $scope.myValue is falsy (element is hidden) -->
27690 * <div ng-show="myValue" class="ng-hide"></div>
27693 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added to the class
27694 * attribute on the element causing it to become hidden. When truthy, the `.ng-hide` CSS class is removed
27695 * from the element causing the element not to appear hidden.
27697 * ## Why is !important used?
27699 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector
27700 * can be easily overridden by heavier selectors. For example, something as simple
27701 * as changing the display style on a HTML list item would make hidden elements appear visible.
27702 * This also becomes a bigger issue when dealing with CSS frameworks.
27704 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector
27705 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the
27706 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code.
27708 * ### Overriding `.ng-hide`
27710 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
27711 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
27712 * class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope
27713 * with extra animation classes that can be added.
27716 * .ng-hide:not(.ng-hide-animate) {
27717 * /* this is just another form of hiding an element */
27718 * display: block!important;
27719 * position: absolute;
27725 * By default you don't need to override in CSS anything and the animations will work around the display style.
27727 * ## A note about animations with `ngShow`
27729 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
27730 * is true and false. This system works like the animation system present with ngClass except that
27731 * you must also include the !important flag to override the display property
27732 * so that you can perform an animation when the element is hidden during the time of the animation.
27736 * //a working example can be found at the bottom of this page
27738 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
27739 * /* this is required as of 1.3x to properly
27740 * apply all styling in a show/hide animation */
27741 * transition: 0s linear all;
27744 * .my-element.ng-hide-add-active,
27745 * .my-element.ng-hide-remove-active {
27746 * /* the transition is defined in the active class */
27747 * transition: 1s linear all;
27750 * .my-element.ng-hide-add { ... }
27751 * .my-element.ng-hide-add.ng-hide-add-active { ... }
27752 * .my-element.ng-hide-remove { ... }
27753 * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
27756 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display
27757 * property to block during animation states--ngAnimate will handle the style toggling automatically for you.
27760 * addClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a truthy value and the just before contents are set to visible
27761 * removeClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden
27764 * @param {expression} ngShow If the {@link guide/expression expression} is truthy
27765 * then the element is shown or hidden respectively.
27768 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27769 <file name="index.html">
27770 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br/>
27773 <div class="check-element animate-show" ng-show="checked">
27774 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked.
27779 <div class="check-element animate-show" ng-hide="checked">
27780 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked.
27784 <file name="glyphicons.css">
27785 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
27787 <file name="animations.css">
27792 border: 1px solid black;
27796 .animate-show.ng-hide-add, .animate-show.ng-hide-remove {
27797 transition: all linear 0.5s;
27800 .animate-show.ng-hide {
27808 border: 1px solid black;
27812 <file name="protractor.js" type="protractor">
27813 var thumbsUp = element(by.css('span.glyphicon-thumbs-up'));
27814 var thumbsDown = element(by.css('span.glyphicon-thumbs-down'));
27816 it('should check ng-show / ng-hide', function() {
27817 expect(thumbsUp.isDisplayed()).toBeFalsy();
27818 expect(thumbsDown.isDisplayed()).toBeTruthy();
27820 element(by.model('checked')).click();
27822 expect(thumbsUp.isDisplayed()).toBeTruthy();
27823 expect(thumbsDown.isDisplayed()).toBeFalsy();
27828 var ngShowDirective = ['$animate', function($animate) {
27831 multiElement: true,
27832 link: function(scope, element, attr) {
27833 scope.$watch(attr.ngShow, function ngShowWatchAction(value) {
27834 // we're adding a temporary, animation-specific class for ng-hide since this way
27835 // we can control when the element is actually displayed on screen without having
27836 // to have a global/greedy CSS selector that breaks when other animations are run.
27837 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845
27838 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, {
27839 tempClasses: NG_HIDE_IN_PROGRESS_CLASS
27853 * The `ngHide` directive shows or hides the given HTML element based on the expression
27854 * provided to the `ngHide` attribute. The element is shown or hidden by removing or adding
27855 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined
27856 * in AngularJS and sets the display style to none (using an !important flag).
27857 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
27860 * <!-- when $scope.myValue is truthy (element is hidden) -->
27861 * <div ng-hide="myValue" class="ng-hide"></div>
27863 * <!-- when $scope.myValue is falsy (element is visible) -->
27864 * <div ng-hide="myValue"></div>
27867 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added to the class
27868 * attribute on the element causing it to become hidden. When falsy, the `.ng-hide` CSS class is removed
27869 * from the element causing the element not to appear hidden.
27871 * ## Why is !important used?
27873 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector
27874 * can be easily overridden by heavier selectors. For example, something as simple
27875 * as changing the display style on a HTML list item would make hidden elements appear visible.
27876 * This also becomes a bigger issue when dealing with CSS frameworks.
27878 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector
27879 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the
27880 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code.
27882 * ### Overriding `.ng-hide`
27884 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
27885 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
27890 * /* this is just another form of hiding an element */
27891 * display: block!important;
27892 * position: absolute;
27898 * By default you don't need to override in CSS anything and the animations will work around the display style.
27900 * ## A note about animations with `ngHide`
27902 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
27903 * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide`
27904 * CSS class is added and removed for you instead of your own CSS class.
27908 * //a working example can be found at the bottom of this page
27910 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
27911 * transition: 0.5s linear all;
27914 * .my-element.ng-hide-add { ... }
27915 * .my-element.ng-hide-add.ng-hide-add-active { ... }
27916 * .my-element.ng-hide-remove { ... }
27917 * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
27920 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display
27921 * property to block during animation states--ngAnimate will handle the style toggling automatically for you.
27924 * removeClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden
27925 * addClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a non truthy value and just before the contents are set to visible
27928 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then
27929 * the element is shown or hidden respectively.
27932 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27933 <file name="index.html">
27934 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br/>
27937 <div class="check-element animate-hide" ng-show="checked">
27938 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked.
27943 <div class="check-element animate-hide" ng-hide="checked">
27944 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked.
27948 <file name="glyphicons.css">
27949 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
27951 <file name="animations.css">
27953 transition: all linear 0.5s;
27957 border: 1px solid black;
27961 .animate-hide.ng-hide {
27969 border: 1px solid black;
27973 <file name="protractor.js" type="protractor">
27974 var thumbsUp = element(by.css('span.glyphicon-thumbs-up'));
27975 var thumbsDown = element(by.css('span.glyphicon-thumbs-down'));
27977 it('should check ng-show / ng-hide', function() {
27978 expect(thumbsUp.isDisplayed()).toBeFalsy();
27979 expect(thumbsDown.isDisplayed()).toBeTruthy();
27981 element(by.model('checked')).click();
27983 expect(thumbsUp.isDisplayed()).toBeTruthy();
27984 expect(thumbsDown.isDisplayed()).toBeFalsy();
27989 var ngHideDirective = ['$animate', function($animate) {
27992 multiElement: true,
27993 link: function(scope, element, attr) {
27994 scope.$watch(attr.ngHide, function ngHideWatchAction(value) {
27995 // The comment inside of the ngShowDirective explains why we add and
27996 // remove a temporary class for the show/hide animation
27997 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, {
27998 tempClasses: NG_HIDE_IN_PROGRESS_CLASS
28011 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally.
28014 * @param {expression} ngStyle
28016 * {@link guide/expression Expression} which evals to an
28017 * object whose keys are CSS style names and values are corresponding values for those CSS
28020 * Since some CSS style names are not valid keys for an object, they must be quoted.
28021 * See the 'background-color' style in the example below.
28025 <file name="index.html">
28026 <input type="button" value="set color" ng-click="myStyle={color:'red'}">
28027 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}">
28028 <input type="button" value="clear" ng-click="myStyle={}">
28030 <span ng-style="myStyle">Sample Text</span>
28031 <pre>myStyle={{myStyle}}</pre>
28033 <file name="style.css">
28038 <file name="protractor.js" type="protractor">
28039 var colorSpan = element(by.css('span'));
28041 it('should check ng-style', function() {
28042 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
28043 element(by.css('input[value=\'set color\']')).click();
28044 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)');
28045 element(by.css('input[value=clear]')).click();
28046 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
28051 var ngStyleDirective = ngDirective(function(scope, element, attr) {
28052 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
28053 if (oldStyles && (newStyles !== oldStyles)) {
28054 forEach(oldStyles, function(val, style) { element.css(style, '');});
28056 if (newStyles) element.css(newStyles);
28066 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression.
28067 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location
28068 * as specified in the template.
28070 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it
28071 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element
28072 * matches the value obtained from the evaluated expression. In other words, you define a container element
28073 * (where you place the directive), place an expression on the **`on="..."` attribute**
28074 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place
28075 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on
28076 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default
28077 * attribute is displayed.
28079 * <div class="alert alert-info">
28080 * Be aware that the attribute values to match against cannot be expressions. They are interpreted
28081 * as literal string values to match against.
28082 * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the
28083 * value of the expression `$scope.someVal`.
28087 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container
28088 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM
28093 * <ANY ng-switch="expression">
28094 * <ANY ng-switch-when="matchValue1">...</ANY>
28095 * <ANY ng-switch-when="matchValue2">...</ANY>
28096 * <ANY ng-switch-default>...</ANY>
28103 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>.
28104 * On child elements add:
28106 * * `ngSwitchWhen`: the case statement to match against. If match then this
28107 * case will be displayed. If the same match appears multiple times, all the
28108 * elements will be displayed.
28109 * * `ngSwitchDefault`: the default case when no other case match. If there
28110 * are multiple default cases, all of them will be displayed when no other
28115 <example module="switchExample" deps="angular-animate.js" animations="true">
28116 <file name="index.html">
28117 <div ng-controller="ExampleController">
28118 <select ng-model="selection" ng-options="item for item in items">
28120 <code>selection={{selection}}</code>
28122 <div class="animate-switch-container"
28123 ng-switch on="selection">
28124 <div class="animate-switch" ng-switch-when="settings">Settings Div</div>
28125 <div class="animate-switch" ng-switch-when="home">Home Span</div>
28126 <div class="animate-switch" ng-switch-default>default</div>
28130 <file name="script.js">
28131 angular.module('switchExample', ['ngAnimate'])
28132 .controller('ExampleController', ['$scope', function($scope) {
28133 $scope.items = ['settings', 'home', 'other'];
28134 $scope.selection = $scope.items[0];
28137 <file name="animations.css">
28138 .animate-switch-container {
28141 border:1px solid black;
28150 .animate-switch.ng-animate {
28151 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
28160 .animate-switch.ng-leave.ng-leave-active,
28161 .animate-switch.ng-enter {
28164 .animate-switch.ng-leave,
28165 .animate-switch.ng-enter.ng-enter-active {
28169 <file name="protractor.js" type="protractor">
28170 var switchElem = element(by.css('[ng-switch]'));
28171 var select = element(by.model('selection'));
28173 it('should start in settings', function() {
28174 expect(switchElem.getText()).toMatch(/Settings Div/);
28176 it('should change to home', function() {
28177 select.all(by.css('option')).get(1).click();
28178 expect(switchElem.getText()).toMatch(/Home Span/);
28180 it('should select default', function() {
28181 select.all(by.css('option')).get(2).click();
28182 expect(switchElem.getText()).toMatch(/default/);
28187 var ngSwitchDirective = ['$animate', function($animate) {
28189 require: 'ngSwitch',
28191 // asks for $scope to fool the BC controller module
28192 controller: ['$scope', function ngSwitchController() {
28195 link: function(scope, element, attr, ngSwitchController) {
28196 var watchExpr = attr.ngSwitch || attr.on,
28197 selectedTranscludes = [],
28198 selectedElements = [],
28199 previousLeaveAnimations = [],
28200 selectedScopes = [];
28202 var spliceFactory = function(array, index) {
28203 return function() { array.splice(index, 1); };
28206 scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
28208 for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) {
28209 $animate.cancel(previousLeaveAnimations[i]);
28211 previousLeaveAnimations.length = 0;
28213 for (i = 0, ii = selectedScopes.length; i < ii; ++i) {
28214 var selected = getBlockNodes(selectedElements[i].clone);
28215 selectedScopes[i].$destroy();
28216 var promise = previousLeaveAnimations[i] = $animate.leave(selected);
28217 promise.then(spliceFactory(previousLeaveAnimations, i));
28220 selectedElements.length = 0;
28221 selectedScopes.length = 0;
28223 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) {
28224 forEach(selectedTranscludes, function(selectedTransclude) {
28225 selectedTransclude.transclude(function(caseElement, selectedScope) {
28226 selectedScopes.push(selectedScope);
28227 var anchor = selectedTransclude.element;
28228 caseElement[caseElement.length++] = document.createComment(' end ngSwitchWhen: ');
28229 var block = { clone: caseElement };
28231 selectedElements.push(block);
28232 $animate.enter(caseElement, anchor.parent(), anchor);
28241 var ngSwitchWhenDirective = ngDirective({
28242 transclude: 'element',
28244 require: '^ngSwitch',
28245 multiElement: true,
28246 link: function(scope, element, attrs, ctrl, $transclude) {
28247 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
28248 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element });
28252 var ngSwitchDefaultDirective = ngDirective({
28253 transclude: 'element',
28255 require: '^ngSwitch',
28256 multiElement: true,
28257 link: function(scope, element, attr, ctrl, $transclude) {
28258 ctrl.cases['?'] = (ctrl.cases['?'] || []);
28259 ctrl.cases['?'].push({ transclude: $transclude, element: element });
28265 * @name ngTransclude
28269 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.
28271 * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted.
28276 <example module="transcludeExample">
28277 <file name="index.html">
28279 angular.module('transcludeExample', [])
28280 .directive('pane', function(){
28284 scope: { title:'@' },
28285 template: '<div style="border: 1px solid black;">' +
28286 '<div style="background-color: gray">{{title}}</div>' +
28287 '<ng-transclude></ng-transclude>' +
28291 .controller('ExampleController', ['$scope', function($scope) {
28292 $scope.title = 'Lorem Ipsum';
28293 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
28296 <div ng-controller="ExampleController">
28297 <input ng-model="title" aria-label="title"> <br/>
28298 <textarea ng-model="text" aria-label="text"></textarea> <br/>
28299 <pane title="{{title}}">{{text}}</pane>
28302 <file name="protractor.js" type="protractor">
28303 it('should have transcluded', function() {
28304 var titleElement = element(by.model('title'));
28305 titleElement.clear();
28306 titleElement.sendKeys('TITLE');
28307 var textElement = element(by.model('text'));
28308 textElement.clear();
28309 textElement.sendKeys('TEXT');
28310 expect(element(by.binding('title')).getText()).toEqual('TITLE');
28311 expect(element(by.binding('text')).getText()).toEqual('TEXT');
28317 var ngTranscludeDirective = ngDirective({
28319 link: function($scope, $element, $attrs, controller, $transclude) {
28320 if (!$transclude) {
28321 throw minErr('ngTransclude')('orphan',
28322 'Illegal use of ngTransclude directive in the template! ' +
28323 'No parent directive that requires a transclusion found. ' +
28325 startingTag($element));
28328 $transclude(function(clone) {
28330 $element.append(clone);
28341 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the
28342 * template can be used by {@link ng.directive:ngInclude `ngInclude`},
28343 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the
28344 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be
28345 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`.
28347 * @param {string} type Must be set to `'text/ng-template'`.
28348 * @param {string} id Cache name of the template.
28352 <file name="index.html">
28353 <script type="text/ng-template" id="/tpl.html">
28354 Content of the template.
28357 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a>
28358 <div id="tpl-content" ng-include src="currentTpl"></div>
28360 <file name="protractor.js" type="protractor">
28361 it('should load template defined inside script tag', function() {
28362 element(by.css('#tpl-link')).click();
28363 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/);
28368 var scriptDirective = ['$templateCache', function($templateCache) {
28372 compile: function(element, attr) {
28373 if (attr.type == 'text/ng-template') {
28374 var templateUrl = attr.id,
28375 text = element[0].text;
28377 $templateCache.put(templateUrl, text);
28383 var noopNgModelController = { $setViewValue: noop, $render: noop };
28385 function chromeHack(optionElement) {
28386 // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459
28387 // Adding an <option selected="selected"> element to a <select required="required"> should
28388 // automatically select the new element
28389 if (optionElement[0].hasAttribute('selected')) {
28390 optionElement[0].selected = true;
28396 * @name select.SelectController
28398 * The controller for the `<select>` directive. This provides support for reading
28399 * and writing the selected value(s) of the control and also coordinates dynamically
28400 * added `<option>` elements, perhaps by an `ngRepeat` directive.
28402 var SelectController =
28403 ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
28406 optionsMap = new HashMap();
28408 // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors
28409 self.ngModelCtrl = noopNgModelController;
28411 // The "unknown" option is one that is prepended to the list if the viewValue
28412 // does not match any of the options. When it is rendered the value of the unknown
28413 // option is '? XXX ?' where XXX is the hashKey of the value that is not known.
28415 // We can't just jqLite('<option>') since jqLite is not smart enough
28416 // to create it in <select> and IE barfs otherwise.
28417 self.unknownOption = jqLite(document.createElement('option'));
28418 self.renderUnknownOption = function(val) {
28419 var unknownVal = '? ' + hashKey(val) + ' ?';
28420 self.unknownOption.val(unknownVal);
28421 $element.prepend(self.unknownOption);
28422 $element.val(unknownVal);
28425 $scope.$on('$destroy', function() {
28426 // disable unknown option so that we don't do work when the whole select is being destroyed
28427 self.renderUnknownOption = noop;
28430 self.removeUnknownOption = function() {
28431 if (self.unknownOption.parent()) self.unknownOption.remove();
28435 // Read the value of the select control, the implementation of this changes depending
28436 // upon whether the select can have multiple values and whether ngOptions is at work.
28437 self.readValue = function readSingleValue() {
28438 self.removeUnknownOption();
28439 return $element.val();
28443 // Write the value to the select control, the implementation of this changes depending
28444 // upon whether the select can have multiple values and whether ngOptions is at work.
28445 self.writeValue = function writeSingleValue(value) {
28446 if (self.hasOption(value)) {
28447 self.removeUnknownOption();
28448 $element.val(value);
28449 if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy
28451 if (value == null && self.emptyOption) {
28452 self.removeUnknownOption();
28455 self.renderUnknownOption(value);
28461 // Tell the select control that an option, with the given value, has been added
28462 self.addOption = function(value, element) {
28463 assertNotHasOwnProperty(value, '"option value"');
28464 if (value === '') {
28465 self.emptyOption = element;
28467 var count = optionsMap.get(value) || 0;
28468 optionsMap.put(value, count + 1);
28469 self.ngModelCtrl.$render();
28470 chromeHack(element);
28473 // Tell the select control that an option, with the given value, has been removed
28474 self.removeOption = function(value) {
28475 var count = optionsMap.get(value);
28478 optionsMap.remove(value);
28479 if (value === '') {
28480 self.emptyOption = undefined;
28483 optionsMap.put(value, count - 1);
28488 // Check whether the select control has an option matching the given value
28489 self.hasOption = function(value) {
28490 return !!optionsMap.get(value);
28494 self.registerOption = function(optionScope, optionElement, optionAttrs, interpolateValueFn, interpolateTextFn) {
28496 if (interpolateValueFn) {
28497 // The value attribute is interpolated
28499 optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {
28500 if (isDefined(oldVal)) {
28501 self.removeOption(oldVal);
28504 self.addOption(newVal, optionElement);
28506 } else if (interpolateTextFn) {
28507 // The text content is interpolated
28508 optionScope.$watch(interpolateTextFn, function interpolateWatchAction(newVal, oldVal) {
28509 optionAttrs.$set('value', newVal);
28510 if (oldVal !== newVal) {
28511 self.removeOption(oldVal);
28513 self.addOption(newVal, optionElement);
28516 // The value attribute is static
28517 self.addOption(optionAttrs.value, optionElement);
28520 optionElement.on('$destroy', function() {
28521 self.removeOption(optionAttrs.value);
28522 self.ngModelCtrl.$render();
28533 * HTML `SELECT` element with angular data-binding.
28535 * The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding
28536 * between the scope and the `<select>` control (including setting default values).
28537 * Ìt also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or
28538 * {@link ngOptions `ngOptions`} directives.
28540 * When an item in the `<select>` menu is selected, the value of the selected option will be bound
28541 * to the model identified by the `ngModel` directive. With static or repeated options, this is
28542 * the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing.
28543 * If you want dynamic value attributes, you can use interpolation inside the value attribute.
28545 * <div class="alert alert-warning">
28546 * Note that the value of a `select` directive used without `ngOptions` is always a string.
28547 * When the model needs to be bound to a non-string value, you must either explictly convert it
28548 * using a directive (see example below) or use `ngOptions` to specify the set of options.
28549 * This is because an option element can only be bound to string values at present.
28552 * If the viewValue of `ngModel` does not match any of the options, then the control
28553 * will automatically add an "unknown" option, which it then removes when the mismatch is resolved.
28555 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
28556 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
28557 * option. See example below for demonstration.
28559 * <div class="alert alert-info">
28560 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions
28561 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits, such as
28562 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
28563 * comprehension expression, and additionally in reducing memory and increasing speed by not creating
28564 * a new scope for each repeated instance.
28568 * @param {string} ngModel Assignable angular expression to data-bind to.
28569 * @param {string=} name Property name of the form under which the control is published.
28570 * @param {string=} multiple Allows multiple options to be selected. The selected values will be
28571 * bound to the model as an array.
28572 * @param {string=} required Sets `required` validation error key if the value is not entered.
28573 * @param {string=} ngRequired Adds required attribute and required validation constraint to
28574 * the element when the ngRequired expression evaluates to true. Use ngRequired instead of required
28575 * when you want to data-bind to the required attribute.
28576 * @param {string=} ngChange Angular expression to be executed when selected option(s) changes due to user
28577 * interaction with the select element.
28578 * @param {string=} ngOptions sets the options that the select is populated with and defines what is
28579 * set on the model on selection. See {@link ngOptions `ngOptions`}.
28582 * ### Simple `select` elements with static options
28584 * <example name="static-select" module="staticSelect">
28585 * <file name="index.html">
28586 * <div ng-controller="ExampleController">
28587 * <form name="myForm">
28588 * <label for="singleSelect"> Single select: </label><br>
28589 * <select name="singleSelect" ng-model="data.singleSelect">
28590 * <option value="option-1">Option 1</option>
28591 * <option value="option-2">Option 2</option>
28594 * <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br>
28595 * <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect">
28596 * <option value="">---Please select---</option> <!-- not selected / blank option -->
28597 * <option value="{{data.option1}}">Option 1</option> <!-- interpolation -->
28598 * <option value="option-2">Option 2</option>
28600 * <button ng-click="forceUnknownOption()">Force unknown option</button><br>
28601 * <tt>singleSelect = {{data.singleSelect}}</tt>
28604 * <label for="multipleSelect"> Multiple select: </label><br>
28605 * <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple>
28606 * <option value="option-1">Option 1</option>
28607 * <option value="option-2">Option 2</option>
28608 * <option value="option-3">Option 3</option>
28610 * <tt>multipleSelect = {{data.multipleSelect}}</tt><br/>
28614 * <file name="app.js">
28615 * angular.module('staticSelect', [])
28616 * .controller('ExampleController', ['$scope', function($scope) {
28618 * singleSelect: null,
28619 * multipleSelect: [],
28620 * option1: 'option-1',
28623 * $scope.forceUnknownOption = function() {
28624 * $scope.data.singleSelect = 'nonsense';
28630 * ### Using `ngRepeat` to generate `select` options
28631 * <example name="ngrepeat-select" module="ngrepeatSelect">
28632 * <file name="index.html">
28633 * <div ng-controller="ExampleController">
28634 * <form name="myForm">
28635 * <label for="repeatSelect"> Repeat select: </label>
28636 * <select name="repeatSelect" id="repeatSelect" ng-model="data.repeatSelect">
28637 * <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option>
28641 * <tt>repeatSelect = {{data.repeatSelect}}</tt><br/>
28644 * <file name="app.js">
28645 * angular.module('ngrepeatSelect', [])
28646 * .controller('ExampleController', ['$scope', function($scope) {
28648 * repeatSelect: null,
28649 * availableOptions: [
28650 * {id: '1', name: 'Option A'},
28651 * {id: '2', name: 'Option B'},
28652 * {id: '3', name: 'Option C'}
28660 * ### Using `select` with `ngOptions` and setting a default value
28661 * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples.
28663 * <example name="select-with-default-values" module="defaultValueSelect">
28664 * <file name="index.html">
28665 * <div ng-controller="ExampleController">
28666 * <form name="myForm">
28667 * <label for="mySelect">Make a choice:</label>
28668 * <select name="mySelect" id="mySelect"
28669 * ng-options="option.name for option in data.availableOptions track by option.id"
28670 * ng-model="data.selectedOption"></select>
28673 * <tt>option = {{data.selectedOption}}</tt><br/>
28676 * <file name="app.js">
28677 * angular.module('defaultValueSelect', [])
28678 * .controller('ExampleController', ['$scope', function($scope) {
28680 * availableOptions: [
28681 * {id: '1', name: 'Option A'},
28682 * {id: '2', name: 'Option B'},
28683 * {id: '3', name: 'Option C'}
28685 * selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui
28692 * ### Binding `select` to a non-string value via `ngModel` parsing / formatting
28694 * <example name="select-with-non-string-options" module="nonStringSelect">
28695 * <file name="index.html">
28696 * <select ng-model="model.id" convert-to-number>
28697 * <option value="0">Zero</option>
28698 * <option value="1">One</option>
28699 * <option value="2">Two</option>
28703 * <file name="app.js">
28704 * angular.module('nonStringSelect', [])
28705 * .run(function($rootScope) {
28706 * $rootScope.model = { id: 2 };
28708 * .directive('convertToNumber', function() {
28710 * require: 'ngModel',
28711 * link: function(scope, element, attrs, ngModel) {
28712 * ngModel.$parsers.push(function(val) {
28713 * return parseInt(val, 10);
28715 * ngModel.$formatters.push(function(val) {
28722 * <file name="protractor.js" type="protractor">
28723 * it('should initialize to model', function() {
28724 * var select = element(by.css('select'));
28725 * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two');
28731 var selectDirective = function() {
28735 require: ['select', '?ngModel'],
28736 controller: SelectController,
28743 function selectPreLink(scope, element, attr, ctrls) {
28745 // if ngModel is not defined, we don't need to do anything
28746 var ngModelCtrl = ctrls[1];
28747 if (!ngModelCtrl) return;
28749 var selectCtrl = ctrls[0];
28751 selectCtrl.ngModelCtrl = ngModelCtrl;
28753 // We delegate rendering to the `writeValue` method, which can be changed
28754 // if the select can have multiple selected values or if the options are being
28755 // generated by `ngOptions`
28756 ngModelCtrl.$render = function() {
28757 selectCtrl.writeValue(ngModelCtrl.$viewValue);
28760 // When the selected item(s) changes we delegate getting the value of the select control
28761 // to the `readValue` method, which can be changed if the select can have multiple
28762 // selected values or if the options are being generated by `ngOptions`
28763 element.on('change', function() {
28764 scope.$apply(function() {
28765 ngModelCtrl.$setViewValue(selectCtrl.readValue());
28769 // If the select allows multiple values then we need to modify how we read and write
28770 // values from and to the control; also what it means for the value to be empty and
28771 // we have to add an extra watch since ngModel doesn't work well with arrays - it
28772 // doesn't trigger rendering if only an item in the array changes.
28773 if (attr.multiple) {
28775 // Read value now needs to check each option to see if it is selected
28776 selectCtrl.readValue = function readMultipleValue() {
28778 forEach(element.find('option'), function(option) {
28779 if (option.selected) {
28780 array.push(option.value);
28786 // Write value now needs to set the selected property of each matching option
28787 selectCtrl.writeValue = function writeMultipleValue(value) {
28788 var items = new HashMap(value);
28789 forEach(element.find('option'), function(option) {
28790 option.selected = isDefined(items.get(option.value));
28794 // we have to do it on each watch since ngModel watches reference, but
28795 // we need to work of an array, so we need to see if anything was inserted/removed
28796 var lastView, lastViewRef = NaN;
28797 scope.$watch(function selectMultipleWatch() {
28798 if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) {
28799 lastView = shallowCopy(ngModelCtrl.$viewValue);
28800 ngModelCtrl.$render();
28802 lastViewRef = ngModelCtrl.$viewValue;
28805 // If we are a multiple select then value is now a collection
28806 // so the meaning of $isEmpty changes
28807 ngModelCtrl.$isEmpty = function(value) {
28808 return !value || value.length === 0;
28816 // The option directive is purely designed to communicate the existence (or lack of)
28817 // of dynamically created (and destroyed) option elements to their containing select
28818 // directive via its controller.
28819 var optionDirective = ['$interpolate', function($interpolate) {
28823 compile: function(element, attr) {
28825 if (isDefined(attr.value)) {
28826 // If the value attribute is defined, check if it contains an interpolation
28827 var interpolateValueFn = $interpolate(attr.value, true);
28829 // If the value attribute is not defined then we fall back to the
28830 // text content of the option element, which may be interpolated
28831 var interpolateTextFn = $interpolate(element.text(), true);
28832 if (!interpolateTextFn) {
28833 attr.$set('value', element.text());
28837 return function(scope, element, attr) {
28839 // This is an optimization over using ^^ since we don't want to have to search
28840 // all the way to the root of the DOM for every single option element
28841 var selectCtrlName = '$selectController',
28842 parent = element.parent(),
28843 selectCtrl = parent.data(selectCtrlName) ||
28844 parent.parent().data(selectCtrlName); // in case we are in optgroup
28847 selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn);
28854 var styleDirective = valueFn({
28859 var requiredDirective = function() {
28862 require: '?ngModel',
28863 link: function(scope, elm, attr, ctrl) {
28865 attr.required = true; // force truthy in case we are on non input element
28867 ctrl.$validators.required = function(modelValue, viewValue) {
28868 return !attr.required || !ctrl.$isEmpty(viewValue);
28871 attr.$observe('required', function() {
28879 var patternDirective = function() {
28882 require: '?ngModel',
28883 link: function(scope, elm, attr, ctrl) {
28886 var regexp, patternExp = attr.ngPattern || attr.pattern;
28887 attr.$observe('pattern', function(regex) {
28888 if (isString(regex) && regex.length > 0) {
28889 regex = new RegExp('^' + regex + '$');
28892 if (regex && !regex.test) {
28893 throw minErr('ngPattern')('noregexp',
28894 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
28895 regex, startingTag(elm));
28898 regexp = regex || undefined;
28902 ctrl.$validators.pattern = function(modelValue, viewValue) {
28903 // HTML5 pattern constraint validates the input value, so we validate the viewValue
28904 return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue);
28911 var maxlengthDirective = function() {
28914 require: '?ngModel',
28915 link: function(scope, elm, attr, ctrl) {
28918 var maxlength = -1;
28919 attr.$observe('maxlength', function(value) {
28920 var intVal = toInt(value);
28921 maxlength = isNaN(intVal) ? -1 : intVal;
28924 ctrl.$validators.maxlength = function(modelValue, viewValue) {
28925 return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength);
28931 var minlengthDirective = function() {
28934 require: '?ngModel',
28935 link: function(scope, elm, attr, ctrl) {
28939 attr.$observe('minlength', function(value) {
28940 minlength = toInt(value) || 0;
28943 ctrl.$validators.minlength = function(modelValue, viewValue) {
28944 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
28950 if (window.angular.bootstrap) {
28951 //AngularJS is already loaded, so we can return here...
28952 console.log('WARNING: Tried to load angular more than once.');
28956 //try to bind to jquery now so that one can write jqLite(document).ready()
28957 //but we will rebind on bootstrap again.
28960 publishExternalAPI(angular);
28962 angular.module("ngLocale", [], ["$provide", function($provide) {
28963 var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"};
28964 function getDecimals(n) {
28966 var i = n.indexOf('.');
28967 return (i == -1) ? 0 : n.length - i - 1;
28970 function getVF(n, opt_precision) {
28971 var v = opt_precision;
28973 if (undefined === v) {
28974 v = Math.min(getDecimals(n), 3);
28977 var base = Math.pow(10, v);
28978 var f = ((n * base) | 0) % base;
28979 return {v: v, f: f};
28982 $provide.value("$locale", {
28983 "DATETIME_FORMATS": {
29005 "FIRSTDAYOFWEEK": 6,
29047 "fullDate": "EEEE, MMMM d, y",
29048 "longDate": "MMMM d, y",
29049 "medium": "MMM d, y h:mm:ss a",
29050 "mediumDate": "MMM d, y",
29051 "mediumTime": "h:mm:ss a",
29052 "short": "M/d/yy h:mm a",
29053 "shortDate": "M/d/yy",
29054 "shortTime": "h:mm a"
29056 "NUMBER_FORMATS": {
29057 "CURRENCY_SYM": "$",
29058 "DECIMAL_SEP": ".",
29078 "negPre": "-\u00a4",
29080 "posPre": "\u00a4",
29086 "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;}
29090 jqLite(document).ready(function() {
29091 angularInit(document, bootstrap);
29094 })(window, document);
29096 !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>');
29100 /***/ function(module, exports) {
29103 * State-based routing for AngularJS
29105 * @link http://angular-ui.github.com/
29106 * @license MIT License, http://www.opensource.org/licenses/MIT
29109 /* commonjs package manager support (eg componentjs) */
29110 if (typeof module !== "undefined" && typeof exports !== "undefined" && module.exports === exports){
29111 module.exports = 'ui.router';
29114 (function (window, angular, undefined) {
29115 /*jshint globalstrict:true*/
29116 /*global angular:false*/
29119 var isDefined = angular.isDefined,
29120 isFunction = angular.isFunction,
29121 isString = angular.isString,
29122 isObject = angular.isObject,
29123 isArray = angular.isArray,
29124 forEach = angular.forEach,
29125 extend = angular.extend,
29126 copy = angular.copy;
29128 function inherit(parent, extra) {
29129 return extend(new (extend(function() {}, { prototype: parent }))(), extra);
29132 function merge(dst) {
29133 forEach(arguments, function(obj) {
29135 forEach(obj, function(value, key) {
29136 if (!dst.hasOwnProperty(key)) dst[key] = value;
29144 * Finds the common ancestor path between two states.
29146 * @param {Object} first The first state.
29147 * @param {Object} second The second state.
29148 * @return {Array} Returns an array of state names in descending order, not including the root.
29150 function ancestors(first, second) {
29153 for (var n in first.path) {
29154 if (first.path[n] !== second.path[n]) break;
29155 path.push(first.path[n]);
29161 * IE8-safe wrapper for `Object.keys()`.
29163 * @param {Object} object A JavaScript object.
29164 * @return {Array} Returns the keys of the object as an array.
29166 function objectKeys(object) {
29168 return Object.keys(object);
29172 forEach(object, function(val, key) {
29179 * IE8-safe wrapper for `Array.prototype.indexOf()`.
29181 * @param {Array} array A JavaScript array.
29182 * @param {*} value A value to search the array for.
29183 * @return {Number} Returns the array index value of `value`, or `-1` if not present.
29185 function indexOf(array, value) {
29186 if (Array.prototype.indexOf) {
29187 return array.indexOf(value, Number(arguments[2]) || 0);
29189 var len = array.length >>> 0, from = Number(arguments[2]) || 0;
29190 from = (from < 0) ? Math.ceil(from) : Math.floor(from);
29192 if (from < 0) from += len;
29194 for (; from < len; from++) {
29195 if (from in array && array[from] === value) return from;
29201 * Merges a set of parameters with all parameters inherited between the common parents of the
29202 * current state and a given destination state.
29204 * @param {Object} currentParams The value of the current state parameters ($stateParams).
29205 * @param {Object} newParams The set of parameters which will be composited with inherited params.
29206 * @param {Object} $current Internal definition of object representing the current state.
29207 * @param {Object} $to Internal definition of object representing state to transition to.
29209 function inheritParams(currentParams, newParams, $current, $to) {
29210 var parents = ancestors($current, $to), parentParams, inherited = {}, inheritList = [];
29212 for (var i in parents) {
29213 if (!parents[i].params) continue;
29214 parentParams = objectKeys(parents[i].params);
29215 if (!parentParams.length) continue;
29217 for (var j in parentParams) {
29218 if (indexOf(inheritList, parentParams[j]) >= 0) continue;
29219 inheritList.push(parentParams[j]);
29220 inherited[parentParams[j]] = currentParams[parentParams[j]];
29223 return extend({}, inherited, newParams);
29227 * Performs a non-strict comparison of the subset of two objects, defined by a list of keys.
29229 * @param {Object} a The first object.
29230 * @param {Object} b The second object.
29231 * @param {Array} keys The list of keys within each object to compare. If the list is empty or not specified,
29232 * it defaults to the list of keys in `a`.
29233 * @return {Boolean} Returns `true` if the keys match, otherwise `false`.
29235 function equalForKeys(a, b, keys) {
29238 for (var n in a) keys.push(n); // Used instead of Object.keys() for IE8 compatibility
29241 for (var i=0; i<keys.length; i++) {
29243 if (a[k] != b[k]) return false; // Not '===', values aren't necessarily normalized
29249 * Returns the subset of an object, based on a list of keys.
29251 * @param {Array} keys
29252 * @param {Object} values
29253 * @return {Boolean} Returns a subset of `values`.
29255 function filterByKeys(keys, values) {
29258 forEach(keys, function (name) {
29259 filtered[name] = values[name];
29265 // when you know that your index values will be unique, or you want last-one-in to win
29266 function indexBy(array, propName) {
29268 forEach(array, function(item) {
29269 result[item[propName]] = item;
29274 // extracted from underscore.js
29275 // Return a copy of the object only containing the whitelisted properties.
29276 function pick(obj) {
29278 var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1));
29279 forEach(keys, function(key) {
29280 if (key in obj) copy[key] = obj[key];
29285 // extracted from underscore.js
29286 // Return a copy of the object omitting the blacklisted properties.
29287 function omit(obj) {
29289 var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1));
29290 for (var key in obj) {
29291 if (indexOf(keys, key) == -1) copy[key] = obj[key];
29296 function pluck(collection, key) {
29297 var result = isArray(collection) ? [] : {};
29299 forEach(collection, function(val, i) {
29300 result[i] = isFunction(key) ? key(val) : val[key];
29305 function filter(collection, callback) {
29306 var array = isArray(collection);
29307 var result = array ? [] : {};
29308 forEach(collection, function(val, i) {
29309 if (callback(val, i)) {
29310 result[array ? result.length : i] = val;
29316 function map(collection, callback) {
29317 var result = isArray(collection) ? [] : {};
29319 forEach(collection, function(val, i) {
29320 result[i] = callback(val, i);
29327 * @name ui.router.util
29330 * # ui.router.util sub-module
29332 * This module is a dependency of other sub-modules. Do not include this module as a dependency
29333 * in your angular app (use {@link ui.router} module instead).
29336 angular.module('ui.router.util', ['ng']);
29340 * @name ui.router.router
29342 * @requires ui.router.util
29345 * # ui.router.router sub-module
29347 * This module is a dependency of other sub-modules. Do not include this module as a dependency
29348 * in your angular app (use {@link ui.router} module instead).
29350 angular.module('ui.router.router', ['ui.router.util']);
29354 * @name ui.router.state
29356 * @requires ui.router.router
29357 * @requires ui.router.util
29360 * # ui.router.state sub-module
29362 * This module is a dependency of the main ui.router module. Do not include this module as a dependency
29363 * in your angular app (use {@link ui.router} module instead).
29366 angular.module('ui.router.state', ['ui.router.router', 'ui.router.util']);
29372 * @requires ui.router.state
29377 * ## The main module for ui.router
29378 * There are several sub-modules included with the ui.router module, however only this module is needed
29379 * as a dependency within your angular app. The other modules are for organization purposes.
29382 * * ui.router - the main "umbrella" module
29383 * * ui.router.router -
29385 * *You'll need to include **only** this module as the dependency within your angular app.*
29389 * <html ng-app="myApp">
29391 * <script src="js/angular.js"></script>
29392 * <!-- Include the ui-router script -->
29393 * <script src="js/angular-ui-router.min.js"></script>
29395 * // ...and add 'ui.router' as a dependency
29396 * var myApp = angular.module('myApp', ['ui.router']);
29404 angular.module('ui.router', ['ui.router.state']);
29406 angular.module('ui.router.compat', ['ui.router']);
29410 * @name ui.router.util.$resolve
29413 * @requires $injector
29416 * Manages resolution of (acyclic) graphs of promises.
29418 $Resolve.$inject = ['$q', '$injector'];
29419 function $Resolve( $q, $injector) {
29421 var VISIT_IN_PROGRESS = 1,
29424 NO_DEPENDENCIES = [],
29425 NO_LOCALS = NOTHING,
29426 NO_PARENT = extend($q.when(NOTHING), { $$promises: NOTHING, $$values: NOTHING });
29431 * @name ui.router.util.$resolve#study
29432 * @methodOf ui.router.util.$resolve
29435 * Studies a set of invocables that are likely to be used multiple times.
29437 * $resolve.study(invocables)(locals, parent, self)
29441 * $resolve.resolve(invocables, locals, parent, self)
29443 * but the former is more efficient (in fact `resolve` just calls `study`
29446 * @param {object} invocables Invocable objects
29447 * @return {function} a function to pass in locals, parent and self
29449 this.study = function (invocables) {
29450 if (!isObject(invocables)) throw new Error("'invocables' must be an object");
29451 var invocableKeys = objectKeys(invocables || {});
29453 // Perform a topological sort of invocables to build an ordered plan
29454 var plan = [], cycle = [], visited = {};
29455 function visit(value, key) {
29456 if (visited[key] === VISIT_DONE) return;
29459 if (visited[key] === VISIT_IN_PROGRESS) {
29460 cycle.splice(0, indexOf(cycle, key));
29461 throw new Error("Cyclic dependency: " + cycle.join(" -> "));
29463 visited[key] = VISIT_IN_PROGRESS;
29465 if (isString(value)) {
29466 plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES);
29468 var params = $injector.annotate(value);
29469 forEach(params, function (param) {
29470 if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param);
29472 plan.push(key, value, params);
29476 visited[key] = VISIT_DONE;
29478 forEach(invocables, visit);
29479 invocables = cycle = visited = null; // plan is all that's required
29481 function isResolve(value) {
29482 return isObject(value) && value.then && value.$$promises;
29485 return function (locals, parent, self) {
29486 if (isResolve(locals) && self === undefined) {
29487 self = parent; parent = locals; locals = null;
29489 if (!locals) locals = NO_LOCALS;
29490 else if (!isObject(locals)) {
29491 throw new Error("'locals' must be an object");
29493 if (!parent) parent = NO_PARENT;
29494 else if (!isResolve(parent)) {
29495 throw new Error("'parent' must be a promise returned by $resolve.resolve()");
29498 // To complete the overall resolution, we have to wait for the parent
29499 // promise and for the promise for each invokable in our plan.
29500 var resolution = $q.defer(),
29501 result = resolution.promise,
29502 promises = result.$$promises = {},
29503 values = extend({}, locals),
29504 wait = 1 + plan.length/3,
29508 // Merge parent values we haven't got yet and publish our own $$values
29510 if (!merged) merge(values, parent.$$values);
29511 result.$$values = values;
29512 result.$$promises = result.$$promises || true; // keep for isResolve()
29513 delete result.$$inheritedValues;
29514 resolution.resolve(values);
29518 function fail(reason) {
29519 result.$$failure = reason;
29520 resolution.reject(reason);
29523 // Short-circuit if parent has already failed
29524 if (isDefined(parent.$$failure)) {
29525 fail(parent.$$failure);
29529 if (parent.$$inheritedValues) {
29530 merge(values, omit(parent.$$inheritedValues, invocableKeys));
29533 // Merge parent values if the parent has already resolved, or merge
29534 // parent promises and wait if the parent resolve is still in progress.
29535 extend(promises, parent.$$promises);
29536 if (parent.$$values) {
29537 merged = merge(values, omit(parent.$$values, invocableKeys));
29538 result.$$inheritedValues = omit(parent.$$values, invocableKeys);
29541 if (parent.$$inheritedValues) {
29542 result.$$inheritedValues = omit(parent.$$inheritedValues, invocableKeys);
29544 parent.then(done, fail);
29547 // Process each invocable in the plan, but ignore any where a local of the same name exists.
29548 for (var i=0, ii=plan.length; i<ii; i+=3) {
29549 if (locals.hasOwnProperty(plan[i])) done();
29550 else invoke(plan[i], plan[i+1], plan[i+2]);
29553 function invoke(key, invocable, params) {
29554 // Create a deferred for this invocation. Failures will propagate to the resolution as well.
29555 var invocation = $q.defer(), waitParams = 0;
29556 function onfailure(reason) {
29557 invocation.reject(reason);
29560 // Wait for any parameter that we have a promise for (either from parent or from this
29561 // resolve; in that case study() will have made sure it's ordered before us in the plan).
29562 forEach(params, function (dep) {
29563 if (promises.hasOwnProperty(dep) && !locals.hasOwnProperty(dep)) {
29565 promises[dep].then(function (result) {
29566 values[dep] = result;
29567 if (!(--waitParams)) proceed();
29571 if (!waitParams) proceed();
29572 function proceed() {
29573 if (isDefined(result.$$failure)) return;
29575 invocation.resolve($injector.invoke(invocable, self, values));
29576 invocation.promise.then(function (result) {
29577 values[key] = result;
29584 // Publish promise synchronously; invocations further down in the plan may depend on it.
29585 promises[key] = invocation.promise;
29594 * @name ui.router.util.$resolve#resolve
29595 * @methodOf ui.router.util.$resolve
29598 * Resolves a set of invocables. An invocable is a function to be invoked via
29599 * `$injector.invoke()`, and can have an arbitrary number of dependencies.
29600 * An invocable can either return a value directly,
29601 * or a `$q` promise. If a promise is returned it will be resolved and the
29602 * resulting value will be used instead. Dependencies of invocables are resolved
29603 * (in this order of precedence)
29605 * - from the specified `locals`
29606 * - from another invocable that is part of this `$resolve` call
29607 * - from an invocable that is inherited from a `parent` call to `$resolve`
29609 * - from any ancestor `$resolve` of that parent).
29611 * The return value of `$resolve` is a promise for an object that contains
29612 * (in this order of precedence)
29614 * - any `locals` (if specified)
29615 * - the resolved return values of all injectables
29616 * - any values inherited from a `parent` call to `$resolve` (if specified)
29618 * The promise will resolve after the `parent` promise (if any) and all promises
29619 * returned by injectables have been resolved. If any invocable
29620 * (or `$injector.invoke`) throws an exception, or if a promise returned by an
29621 * invocable is rejected, the `$resolve` promise is immediately rejected with the
29622 * same error. A rejection of a `parent` promise (if specified) will likewise be
29623 * propagated immediately. Once the `$resolve` promise has been rejected, no
29624 * further invocables will be called.
29626 * Cyclic dependencies between invocables are not permitted and will caues `$resolve`
29627 * to throw an error. As a special case, an injectable can depend on a parameter
29628 * with the same name as the injectable, which will be fulfilled from the `parent`
29629 * injectable of the same name. This allows inherited values to be decorated.
29630 * Note that in this case any other injectable in the same `$resolve` with the same
29631 * dependency would see the decorated value, not the inherited value.
29633 * Note that missing dependencies -- unlike cyclic dependencies -- will cause an
29634 * (asynchronous) rejection of the `$resolve` promise rather than a (synchronous)
29637 * Invocables are invoked eagerly as soon as all dependencies are available.
29638 * This is true even for dependencies inherited from a `parent` call to `$resolve`.
29640 * As a special case, an invocable can be a string, in which case it is taken to
29641 * be a service name to be passed to `$injector.get()`. This is supported primarily
29642 * for backwards-compatibility with the `resolve` property of `$routeProvider`
29645 * @param {object} invocables functions to invoke or
29646 * `$injector` services to fetch.
29647 * @param {object} locals values to make available to the injectables
29648 * @param {object} parent a promise returned by another call to `$resolve`.
29649 * @param {object} self the `this` for the invoked methods
29650 * @return {object} Promise for an object that contains the resolved return value
29651 * of all invocables, as well as any inherited and local values.
29653 this.resolve = function (invocables, locals, parent, self) {
29654 return this.study(invocables)(locals, parent, self);
29658 angular.module('ui.router.util').service('$resolve', $Resolve);
29663 * @name ui.router.util.$templateFactory
29666 * @requires $templateCache
29667 * @requires $injector
29670 * Service. Manages loading of templates.
29672 $TemplateFactory.$inject = ['$http', '$templateCache', '$injector'];
29673 function $TemplateFactory( $http, $templateCache, $injector) {
29677 * @name ui.router.util.$templateFactory#fromConfig
29678 * @methodOf ui.router.util.$templateFactory
29681 * Creates a template from a configuration object.
29683 * @param {object} config Configuration object for which to load a template.
29684 * The following properties are search in the specified order, and the first one
29685 * that is defined is used to create the template:
29687 * @param {string|object} config.template html string template or function to
29688 * load via {@link ui.router.util.$templateFactory#fromString fromString}.
29689 * @param {string|object} config.templateUrl url to load or a function returning
29690 * the url to load via {@link ui.router.util.$templateFactory#fromUrl fromUrl}.
29691 * @param {Function} config.templateProvider function to invoke via
29692 * {@link ui.router.util.$templateFactory#fromProvider fromProvider}.
29693 * @param {object} params Parameters to pass to the template function.
29694 * @param {object} locals Locals to pass to `invoke` if the template is loaded
29695 * via a `templateProvider`. Defaults to `{ params: params }`.
29697 * @return {string|object} The template html as a string, or a promise for
29698 * that string,or `null` if no template is configured.
29700 this.fromConfig = function (config, params, locals) {
29702 isDefined(config.template) ? this.fromString(config.template, params) :
29703 isDefined(config.templateUrl) ? this.fromUrl(config.templateUrl, params) :
29704 isDefined(config.templateProvider) ? this.fromProvider(config.templateProvider, params, locals) :
29711 * @name ui.router.util.$templateFactory#fromString
29712 * @methodOf ui.router.util.$templateFactory
29715 * Creates a template from a string or a function returning a string.
29717 * @param {string|object} template html template as a string or function that
29718 * returns an html template as a string.
29719 * @param {object} params Parameters to pass to the template function.
29721 * @return {string|object} The template html as a string, or a promise for that
29724 this.fromString = function (template, params) {
29725 return isFunction(template) ? template(params) : template;
29730 * @name ui.router.util.$templateFactory#fromUrl
29731 * @methodOf ui.router.util.$templateFactory
29734 * Loads a template from the a URL via `$http` and `$templateCache`.
29736 * @param {string|Function} url url of the template to load, or a function
29737 * that returns a url.
29738 * @param {Object} params Parameters to pass to the url function.
29739 * @return {string|Promise.<string>} The template html as a string, or a promise
29742 this.fromUrl = function (url, params) {
29743 if (isFunction(url)) url = url(params);
29744 if (url == null) return null;
29746 .get(url, { cache: $templateCache, headers: { Accept: 'text/html' }})
29747 .then(function(response) { return response.data; });
29752 * @name ui.router.util.$templateFactory#fromProvider
29753 * @methodOf ui.router.util.$templateFactory
29756 * Creates a template by invoking an injectable provider function.
29758 * @param {Function} provider Function to invoke via `$injector.invoke`
29759 * @param {Object} params Parameters for the template.
29760 * @param {Object} locals Locals to pass to `invoke`. Defaults to
29761 * `{ params: params }`.
29762 * @return {string|Promise.<string>} The template html as a string, or a promise
29765 this.fromProvider = function (provider, params, locals) {
29766 return $injector.invoke(provider, null, locals || { params: params });
29770 angular.module('ui.router.util').service('$templateFactory', $TemplateFactory);
29772 var $$UMFP; // reference to $UrlMatcherFactoryProvider
29776 * @name ui.router.util.type:UrlMatcher
29779 * Matches URLs against patterns and extracts named parameters from the path or the search
29780 * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list
29781 * of search parameters. Multiple search parameter names are separated by '&'. Search parameters
29782 * do not influence whether or not a URL is matched, but their values are passed through into
29783 * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}.
29785 * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace
29786 * syntax, which optionally allows a regular expression for the parameter to be specified:
29788 * * `':'` name - colon placeholder
29789 * * `'*'` name - catch-all placeholder
29790 * * `'{' name '}'` - curly placeholder
29791 * * `'{' name ':' regexp|type '}'` - curly placeholder with regexp or type name. Should the
29792 * regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash.
29794 * Parameter names may contain only word characters (latin letters, digits, and underscore) and
29795 * must be unique within the pattern (across both path and search parameters). For colon
29796 * placeholders or curly placeholders without an explicit regexp, a path parameter matches any
29797 * number of characters other than '/'. For catch-all placeholders the path parameter matches
29798 * any number of characters.
29802 * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for
29803 * trailing slashes, and patterns have to match the entire path, not just a prefix.
29804 * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or
29805 * '/user/bob/details'. The second path segment will be captured as the parameter 'id'.
29806 * * `'/user/{id}'` - Same as the previous example, but using curly brace syntax.
29807 * * `'/user/{id:[^/]*}'` - Same as the previous example.
29808 * * `'/user/{id:[0-9a-fA-F]{1,8}}'` - Similar to the previous example, but only matches if the id
29809 * parameter consists of 1 to 8 hex digits.
29810 * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the
29811 * path into the parameter 'path'.
29812 * * `'/files/*path'` - ditto.
29813 * * `'/calendar/{start:date}'` - Matches "/calendar/2014-11-12" (because the pattern defined
29814 * in the built-in `date` Type matches `2014-11-12`) and provides a Date object in $stateParams.start
29816 * @param {string} pattern The pattern to compile into a matcher.
29817 * @param {Object} config A configuration object hash:
29818 * @param {Object=} parentMatcher Used to concatenate the pattern/config onto
29819 * an existing UrlMatcher
29821 * * `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`.
29822 * * `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`.
29824 * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any
29825 * URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns
29826 * non-null) will start with this prefix.
29828 * @property {string} source The pattern that was passed into the constructor
29830 * @property {string} sourcePath The path portion of the source property
29832 * @property {string} sourceSearch The search portion of the source property
29834 * @property {string} regex The constructed regex that will be used to match against the url when
29835 * it is time to determine which url will match.
29837 * @returns {Object} New `UrlMatcher` object
29839 function UrlMatcher(pattern, config, parentMatcher) {
29840 config = extend({ params: {} }, isObject(config) ? config : {});
29842 // Find all placeholders and create a compiled pattern, using either classic or curly syntax:
29846 // '{' name ':' regexp '}'
29847 // The regular expression is somewhat complicated due to the need to allow curly braces
29848 // inside the regular expression. The placeholder regexp breaks down as follows:
29849 // ([:*])([\w\[\]]+) - classic placeholder ($1 / $2) (search version has - for snake-case)
29850 // \{([\w\[\]]+)(?:\:( ... ))?\} - curly brace placeholder ($3) with optional regexp/type ... ($4) (search version has - for snake-case
29851 // (?: ... | ... | ... )+ - the regexp consists of any number of atoms, an atom being either
29852 // [^{}\\]+ - anything other than curly braces or backslash
29853 // \\. - a backslash escape
29854 // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms
29855 var placeholder = /([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
29856 searchPlaceholder = /([:]?)([\w\[\]-]+)|\{([\w\[\]-]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
29857 compiled = '^', last = 0, m,
29858 segments = this.segments = [],
29859 parentParams = parentMatcher ? parentMatcher.params : {},
29860 params = this.params = parentMatcher ? parentMatcher.params.$$new() : new $$UMFP.ParamSet(),
29863 function addParameter(id, type, config, location) {
29864 paramNames.push(id);
29865 if (parentParams[id]) return parentParams[id];
29866 if (!/^\w+(-+\w+)*(?:\[\])?$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'");
29867 if (params[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'");
29868 params[id] = new $$UMFP.Param(id, type, config, location);
29872 function quoteRegExp(string, pattern, squash, optional) {
29873 var surroundPattern = ['',''], result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&");
29874 if (!pattern) return result;
29876 case false: surroundPattern = ['(', ')' + (optional ? "?" : "")]; break;
29877 case true: surroundPattern = ['?(', ')?']; break;
29878 default: surroundPattern = ['(' + squash + "|", ')?']; break;
29880 return result + surroundPattern[0] + pattern + surroundPattern[1];
29883 this.source = pattern;
29885 // Split into static segments separated by path parameter placeholders.
29886 // The number of segments is always 1 more than the number of parameters.
29887 function matchDetails(m, isSearch) {
29888 var id, regexp, segment, type, cfg, arrayMode;
29889 id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null
29890 cfg = config.params[id];
29891 segment = pattern.substring(last, m.index);
29892 regexp = isSearch ? m[4] : m[4] || (m[1] == '*' ? '.*' : null);
29893 type = $$UMFP.type(regexp || "string") || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp, config.caseInsensitive ? 'i' : undefined) });
29895 id: id, regexp: regexp, segment: segment, type: type, cfg: cfg
29899 var p, param, segment;
29900 while ((m = placeholder.exec(pattern))) {
29901 p = matchDetails(m, false);
29902 if (p.segment.indexOf('?') >= 0) break; // we're into the search part
29904 param = addParameter(p.id, p.type, p.cfg, "path");
29905 compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash, param.isOptional);
29906 segments.push(p.segment);
29907 last = placeholder.lastIndex;
29909 segment = pattern.substring(last);
29911 // Find any search parameter names and remove them from the last segment
29912 var i = segment.indexOf('?');
29915 var search = this.sourceSearch = segment.substring(i);
29916 segment = segment.substring(0, i);
29917 this.sourcePath = pattern.substring(0, last + i);
29919 if (search.length > 0) {
29921 while ((m = searchPlaceholder.exec(search))) {
29922 p = matchDetails(m, true);
29923 param = addParameter(p.id, p.type, p.cfg, "search");
29924 last = placeholder.lastIndex;
29929 this.sourcePath = pattern;
29930 this.sourceSearch = '';
29933 compiled += quoteRegExp(segment) + (config.strict === false ? '\/?' : '') + '$';
29934 segments.push(segment);
29936 this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined);
29937 this.prefix = segments[0];
29938 this.$$paramNames = paramNames;
29943 * @name ui.router.util.type:UrlMatcher#concat
29944 * @methodOf ui.router.util.type:UrlMatcher
29947 * Returns a new matcher for a pattern constructed by appending the path part and adding the
29948 * search parameters of the specified pattern to this pattern. The current pattern is not
29949 * modified. This can be understood as creating a pattern for URLs that are relative to (or
29950 * suffixes of) the current pattern.
29953 * The following two matchers are equivalent:
29955 * new UrlMatcher('/user/{id}?q').concat('/details?date');
29956 * new UrlMatcher('/user/{id}/details?q&date');
29959 * @param {string} pattern The pattern to append.
29960 * @param {Object} config An object hash of the configuration for the matcher.
29961 * @returns {UrlMatcher} A matcher for the concatenated pattern.
29963 UrlMatcher.prototype.concat = function (pattern, config) {
29964 // Because order of search parameters is irrelevant, we can add our own search
29965 // parameters to the end of the new pattern. Parse the new pattern by itself
29966 // and then join the bits together, but it's much easier to do this on a string level.
29967 var defaultConfig = {
29968 caseInsensitive: $$UMFP.caseInsensitive(),
29969 strict: $$UMFP.strictMode(),
29970 squash: $$UMFP.defaultSquashPolicy()
29972 return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch, extend(defaultConfig, config), this);
29975 UrlMatcher.prototype.toString = function () {
29976 return this.source;
29981 * @name ui.router.util.type:UrlMatcher#exec
29982 * @methodOf ui.router.util.type:UrlMatcher
29985 * Tests the specified path against this matcher, and returns an object containing the captured
29986 * parameter values, or null if the path does not match. The returned object contains the values
29987 * of any search parameters that are mentioned in the pattern, but their value may be null if
29988 * they are not present in `searchParams`. This means that search parameters are always treated
29993 * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', {
29994 * x: '1', q: 'hello'
29996 * // returns { id: 'bob', q: 'hello', r: null }
29999 * @param {string} path The URL path to match, e.g. `$location.path()`.
30000 * @param {Object} searchParams URL search parameters, e.g. `$location.search()`.
30001 * @returns {Object} The captured parameter values.
30003 UrlMatcher.prototype.exec = function (path, searchParams) {
30004 var m = this.regexp.exec(path);
30005 if (!m) return null;
30006 searchParams = searchParams || {};
30008 var paramNames = this.parameters(), nTotal = paramNames.length,
30009 nPath = this.segments.length - 1,
30010 values = {}, i, j, cfg, paramName;
30012 if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'");
30014 function decodePathArray(string) {
30015 function reverseString(str) { return str.split("").reverse().join(""); }
30016 function unquoteDashes(str) { return str.replace(/\\-/g, "-"); }
30018 var split = reverseString(string).split(/-(?!\\)/);
30019 var allReversed = map(split, reverseString);
30020 return map(allReversed, unquoteDashes).reverse();
30023 for (i = 0; i < nPath; i++) {
30024 paramName = paramNames[i];
30025 var param = this.params[paramName];
30026 var paramVal = m[i+1];
30027 // if the param value matches a pre-replace pair, replace the value before decoding.
30028 for (j = 0; j < param.replace; j++) {
30029 if (param.replace[j].from === paramVal) paramVal = param.replace[j].to;
30031 if (paramVal && param.array === true) paramVal = decodePathArray(paramVal);
30032 values[paramName] = param.value(paramVal);
30034 for (/**/; i < nTotal; i++) {
30035 paramName = paramNames[i];
30036 values[paramName] = this.params[paramName].value(searchParams[paramName]);
30044 * @name ui.router.util.type:UrlMatcher#parameters
30045 * @methodOf ui.router.util.type:UrlMatcher
30048 * Returns the names of all path and search parameters of this pattern in an unspecified order.
30050 * @returns {Array.<string>} An array of parameter names. Must be treated as read-only. If the
30051 * pattern has no parameters, an empty array is returned.
30053 UrlMatcher.prototype.parameters = function (param) {
30054 if (!isDefined(param)) return this.$$paramNames;
30055 return this.params[param] || null;
30060 * @name ui.router.util.type:UrlMatcher#validate
30061 * @methodOf ui.router.util.type:UrlMatcher
30064 * Checks an object hash of parameters to validate their correctness according to the parameter
30065 * types of this `UrlMatcher`.
30067 * @param {Object} params The object hash of parameters to validate.
30068 * @returns {boolean} Returns `true` if `params` validates, otherwise `false`.
30070 UrlMatcher.prototype.validates = function (params) {
30071 return this.params.$$validates(params);
30076 * @name ui.router.util.type:UrlMatcher#format
30077 * @methodOf ui.router.util.type:UrlMatcher
30080 * Creates a URL that matches this pattern by substituting the specified values
30081 * for the path and search parameters. Null values for path parameters are
30082 * treated as empty strings.
30086 * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' });
30087 * // returns '/user/bob?q=yes'
30090 * @param {Object} values the values to substitute for the parameters in this pattern.
30091 * @returns {string} the formatted URL (path and optionally search part).
30093 UrlMatcher.prototype.format = function (values) {
30094 values = values || {};
30095 var segments = this.segments, params = this.parameters(), paramset = this.params;
30096 if (!this.validates(values)) return null;
30098 var i, search = false, nPath = segments.length - 1, nTotal = params.length, result = segments[0];
30100 function encodeDashes(str) { // Replace dashes with encoded "\-"
30101 return encodeURIComponent(str).replace(/-/g, function(c) { return '%5C%' + c.charCodeAt(0).toString(16).toUpperCase(); });
30104 for (i = 0; i < nTotal; i++) {
30105 var isPathParam = i < nPath;
30106 var name = params[i], param = paramset[name], value = param.value(values[name]);
30107 var isDefaultValue = param.isOptional && param.type.equals(param.value(), value);
30108 var squash = isDefaultValue ? param.squash : false;
30109 var encoded = param.type.encode(value);
30112 var nextSegment = segments[i + 1];
30113 if (squash === false) {
30114 if (encoded != null) {
30115 if (isArray(encoded)) {
30116 result += map(encoded, encodeDashes).join("-");
30118 result += encodeURIComponent(encoded);
30121 result += nextSegment;
30122 } else if (squash === true) {
30123 var capture = result.match(/\/$/) ? /\/?(.*)/ : /(.*)/;
30124 result += nextSegment.match(capture)[1];
30125 } else if (isString(squash)) {
30126 result += squash + nextSegment;
30129 if (encoded == null || (isDefaultValue && squash !== false)) continue;
30130 if (!isArray(encoded)) encoded = [ encoded ];
30131 encoded = map(encoded, encodeURIComponent).join('&' + name + '=');
30132 result += (search ? '&' : '?') + (name + '=' + encoded);
30142 * @name ui.router.util.type:Type
30145 * Implements an interface to define custom parameter types that can be decoded from and encoded to
30146 * string parameters matched in a URL. Used by {@link ui.router.util.type:UrlMatcher `UrlMatcher`}
30147 * objects when matching or formatting URLs, or comparing or validating parameter values.
30149 * See {@link ui.router.util.$urlMatcherFactory#methods_type `$urlMatcherFactory#type()`} for more
30150 * information on registering custom types.
30152 * @param {Object} config A configuration object which contains the custom type definition. The object's
30153 * properties will override the default methods and/or pattern in `Type`'s public interface.
30157 * decode: function(val) { return parseInt(val, 10); },
30158 * encode: function(val) { return val && val.toString(); },
30159 * equals: function(a, b) { return this.is(a) && a === b; },
30160 * is: function(val) { return angular.isNumber(val) isFinite(val) && val % 1 === 0; },
30165 * @property {RegExp} pattern The regular expression pattern used to match values of this type when
30166 * coming from a substring of a URL.
30168 * @returns {Object} Returns a new `Type` object.
30170 function Type(config) {
30171 extend(this, config);
30176 * @name ui.router.util.type:Type#is
30177 * @methodOf ui.router.util.type:Type
30180 * Detects whether a value is of a particular type. Accepts a native (decoded) value
30181 * and determines whether it matches the current `Type` object.
30183 * @param {*} val The value to check.
30184 * @param {string} key Optional. If the type check is happening in the context of a specific
30185 * {@link ui.router.util.type:UrlMatcher `UrlMatcher`} object, this is the name of the
30186 * parameter in which `val` is stored. Can be used for meta-programming of `Type` objects.
30187 * @returns {Boolean} Returns `true` if the value matches the type, otherwise `false`.
30189 Type.prototype.is = function(val, key) {
30195 * @name ui.router.util.type:Type#encode
30196 * @methodOf ui.router.util.type:Type
30199 * Encodes a custom/native type value to a string that can be embedded in a URL. Note that the
30200 * return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it
30201 * only needs to be a representation of `val` that has been coerced to a string.
30203 * @param {*} val The value to encode.
30204 * @param {string} key The name of the parameter in which `val` is stored. Can be used for
30205 * meta-programming of `Type` objects.
30206 * @returns {string} Returns a string representation of `val` that can be encoded in a URL.
30208 Type.prototype.encode = function(val, key) {
30214 * @name ui.router.util.type:Type#decode
30215 * @methodOf ui.router.util.type:Type
30218 * Converts a parameter value (from URL string or transition param) to a custom/native value.
30220 * @param {string} val The URL parameter value to decode.
30221 * @param {string} key The name of the parameter in which `val` is stored. Can be used for
30222 * meta-programming of `Type` objects.
30223 * @returns {*} Returns a custom representation of the URL parameter value.
30225 Type.prototype.decode = function(val, key) {
30231 * @name ui.router.util.type:Type#equals
30232 * @methodOf ui.router.util.type:Type
30235 * Determines whether two decoded values are equivalent.
30237 * @param {*} a A value to compare against.
30238 * @param {*} b A value to compare against.
30239 * @returns {Boolean} Returns `true` if the values are equivalent/equal, otherwise `false`.
30241 Type.prototype.equals = function(a, b) {
30245 Type.prototype.$subPattern = function() {
30246 var sub = this.pattern.toString();
30247 return sub.substr(1, sub.length - 2);
30250 Type.prototype.pattern = /.*/;
30252 Type.prototype.toString = function() { return "{Type:" + this.name + "}"; };
30254 /** Given an encoded string, or a decoded object, returns a decoded object */
30255 Type.prototype.$normalize = function(val) {
30256 return this.is(val) ? val : this.decode(val);
30260 * Wraps an existing custom Type as an array of Type, depending on 'mode'.
30262 * - urlmatcher pattern "/path?{queryParam[]:int}"
30263 * - url: "/path?queryParam=1&queryParam=2
30264 * - $stateParams.queryParam will be [1, 2]
30265 * if `mode` is "auto", then
30266 * - url: "/path?queryParam=1 will create $stateParams.queryParam: 1
30267 * - url: "/path?queryParam=1&queryParam=2 will create $stateParams.queryParam: [1, 2]
30269 Type.prototype.$asArray = function(mode, isSearch) {
30270 if (!mode) return this;
30271 if (mode === "auto" && !isSearch) throw new Error("'auto' array mode is for query parameters only");
30273 function ArrayType(type, mode) {
30274 function bindTo(type, callbackName) {
30275 return function() {
30276 return type[callbackName].apply(type, arguments);
30280 // Wrap non-array value as array
30281 function arrayWrap(val) { return isArray(val) ? val : (isDefined(val) ? [ val ] : []); }
30282 // Unwrap array value for "auto" mode. Return undefined for empty array.
30283 function arrayUnwrap(val) {
30284 switch(val.length) {
30285 case 0: return undefined;
30286 case 1: return mode === "auto" ? val[0] : val;
30287 default: return val;
30290 function falsey(val) { return !val; }
30292 // Wraps type (.is/.encode/.decode) functions to operate on each value of an array
30293 function arrayHandler(callback, allTruthyMode) {
30294 return function handleArray(val) {
30295 val = arrayWrap(val);
30296 var result = map(val, callback);
30297 if (allTruthyMode === true)
30298 return filter(result, falsey).length === 0;
30299 return arrayUnwrap(result);
30303 // Wraps type (.equals) functions to operate on each value of an array
30304 function arrayEqualsHandler(callback) {
30305 return function handleArray(val1, val2) {
30306 var left = arrayWrap(val1), right = arrayWrap(val2);
30307 if (left.length !== right.length) return false;
30308 for (var i = 0; i < left.length; i++) {
30309 if (!callback(left[i], right[i])) return false;
30315 this.encode = arrayHandler(bindTo(type, 'encode'));
30316 this.decode = arrayHandler(bindTo(type, 'decode'));
30317 this.is = arrayHandler(bindTo(type, 'is'), true);
30318 this.equals = arrayEqualsHandler(bindTo(type, 'equals'));
30319 this.pattern = type.pattern;
30320 this.$normalize = arrayHandler(bindTo(type, '$normalize'));
30321 this.name = type.name;
30322 this.$arrayMode = mode;
30325 return new ArrayType(this, mode);
30332 * @name ui.router.util.$urlMatcherFactory
30335 * Factory for {@link ui.router.util.type:UrlMatcher `UrlMatcher`} instances. The factory
30336 * is also available to providers under the name `$urlMatcherFactoryProvider`.
30338 function $UrlMatcherFactory() {
30341 var isCaseInsensitive = false, isStrictMode = true, defaultSquashPolicy = false;
30343 function valToString(val) { return val != null ? val.toString().replace(/\//g, "%2F") : val; }
30344 function valFromString(val) { return val != null ? val.toString().replace(/%2F/g, "/") : val; }
30346 var $types = {}, enqueue = true, typeQueue = [], injector, defaultTypes = {
30348 encode: valToString,
30349 decode: valFromString,
30350 // TODO: in 1.0, make string .is() return false if value is undefined/null by default.
30351 // In 0.2.x, string params are optional by default for backwards compat
30352 is: function(val) { return val == null || !isDefined(val) || typeof val === "string"; },
30356 encode: valToString,
30357 decode: function(val) { return parseInt(val, 10); },
30358 is: function(val) { return isDefined(val) && this.decode(val.toString()) === val; },
30362 encode: function(val) { return val ? 1 : 0; },
30363 decode: function(val) { return parseInt(val, 10) !== 0; },
30364 is: function(val) { return val === true || val === false; },
30368 encode: function (val) {
30371 return [ val.getFullYear(),
30372 ('0' + (val.getMonth() + 1)).slice(-2),
30373 ('0' + val.getDate()).slice(-2)
30376 decode: function (val) {
30377 if (this.is(val)) return val;
30378 var match = this.capture.exec(val);
30379 return match ? new Date(match[1], match[2] - 1, match[3]) : undefined;
30381 is: function(val) { return val instanceof Date && !isNaN(val.valueOf()); },
30382 equals: function (a, b) { return this.is(a) && this.is(b) && a.toISOString() === b.toISOString(); },
30383 pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/,
30384 capture: /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/
30387 encode: angular.toJson,
30388 decode: angular.fromJson,
30389 is: angular.isObject,
30390 equals: angular.equals,
30393 any: { // does not encode/decode
30394 encode: angular.identity,
30395 decode: angular.identity,
30396 equals: angular.equals,
30401 function getDefaultConfig() {
30403 strict: isStrictMode,
30404 caseInsensitive: isCaseInsensitive
30408 function isInjectable(value) {
30409 return (isFunction(value) || (isArray(value) && isFunction(value[value.length - 1])));
30413 * [Internal] Get the default value of a parameter, which may be an injectable function.
30415 $UrlMatcherFactory.$$getDefaultValue = function(config) {
30416 if (!isInjectable(config.value)) return config.value;
30417 if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
30418 return injector.invoke(config.value);
30423 * @name ui.router.util.$urlMatcherFactory#caseInsensitive
30424 * @methodOf ui.router.util.$urlMatcherFactory
30427 * Defines whether URL matching should be case sensitive (the default behavior), or not.
30429 * @param {boolean} value `false` to match URL in a case sensitive manner; otherwise `true`;
30430 * @returns {boolean} the current value of caseInsensitive
30432 this.caseInsensitive = function(value) {
30433 if (isDefined(value))
30434 isCaseInsensitive = value;
30435 return isCaseInsensitive;
30440 * @name ui.router.util.$urlMatcherFactory#strictMode
30441 * @methodOf ui.router.util.$urlMatcherFactory
30444 * Defines whether URLs should match trailing slashes, or not (the default behavior).
30446 * @param {boolean=} value `false` to match trailing slashes in URLs, otherwise `true`.
30447 * @returns {boolean} the current value of strictMode
30449 this.strictMode = function(value) {
30450 if (isDefined(value))
30451 isStrictMode = value;
30452 return isStrictMode;
30457 * @name ui.router.util.$urlMatcherFactory#defaultSquashPolicy
30458 * @methodOf ui.router.util.$urlMatcherFactory
30461 * Sets the default behavior when generating or matching URLs with default parameter values.
30463 * @param {string} value A string that defines the default parameter URL squashing behavior.
30464 * `nosquash`: When generating an href with a default parameter value, do not squash the parameter value from the URL
30465 * `slash`: When generating an href with a default parameter value, squash (remove) the parameter value, and, if the
30466 * parameter is surrounded by slashes, squash (remove) one slash from the URL
30467 * any other string, e.g. "~": When generating an href with a default parameter value, squash (remove)
30468 * the parameter value from the URL and replace it with this string.
30470 this.defaultSquashPolicy = function(value) {
30471 if (!isDefined(value)) return defaultSquashPolicy;
30472 if (value !== true && value !== false && !isString(value))
30473 throw new Error("Invalid squash policy: " + value + ". Valid policies: false, true, arbitrary-string");
30474 defaultSquashPolicy = value;
30480 * @name ui.router.util.$urlMatcherFactory#compile
30481 * @methodOf ui.router.util.$urlMatcherFactory
30484 * Creates a {@link ui.router.util.type:UrlMatcher `UrlMatcher`} for the specified pattern.
30486 * @param {string} pattern The URL pattern.
30487 * @param {Object} config The config object hash.
30488 * @returns {UrlMatcher} The UrlMatcher.
30490 this.compile = function (pattern, config) {
30491 return new UrlMatcher(pattern, extend(getDefaultConfig(), config));
30496 * @name ui.router.util.$urlMatcherFactory#isMatcher
30497 * @methodOf ui.router.util.$urlMatcherFactory
30500 * Returns true if the specified object is a `UrlMatcher`, or false otherwise.
30502 * @param {Object} object The object to perform the type check against.
30503 * @returns {Boolean} Returns `true` if the object matches the `UrlMatcher` interface, by
30504 * implementing all the same methods.
30506 this.isMatcher = function (o) {
30507 if (!isObject(o)) return false;
30510 forEach(UrlMatcher.prototype, function(val, name) {
30511 if (isFunction(val)) {
30512 result = result && (isDefined(o[name]) && isFunction(o[name]));
30520 * @name ui.router.util.$urlMatcherFactory#type
30521 * @methodOf ui.router.util.$urlMatcherFactory
30524 * Registers a custom {@link ui.router.util.type:Type `Type`} object that can be used to
30525 * generate URLs with typed parameters.
30527 * @param {string} name The type name.
30528 * @param {Object|Function} definition The type definition. See
30529 * {@link ui.router.util.type:Type `Type`} for information on the values accepted.
30530 * @param {Object|Function} definitionFn (optional) A function that is injected before the app
30531 * runtime starts. The result of this function is merged into the existing `definition`.
30532 * See {@link ui.router.util.type:Type `Type`} for information on the values accepted.
30534 * @returns {Object} Returns `$urlMatcherFactoryProvider`.
30537 * This is a simple example of a custom type that encodes and decodes items from an
30538 * array, using the array index as the URL-encoded value:
30541 * var list = ['John', 'Paul', 'George', 'Ringo'];
30543 * $urlMatcherFactoryProvider.type('listItem', {
30544 * encode: function(item) {
30545 * // Represent the list item in the URL using its corresponding index
30546 * return list.indexOf(item);
30548 * decode: function(item) {
30549 * // Look up the list item by index
30550 * return list[parseInt(item, 10)];
30552 * is: function(item) {
30553 * // Ensure the item is valid by checking to see that it appears
30555 * return list.indexOf(item) > -1;
30559 * $stateProvider.state('list', {
30560 * url: "/list/{item:listItem}",
30561 * controller: function($scope, $stateParams) {
30562 * console.log($stateParams.item);
30568 * // Changes URL to '/list/3', logs "Ringo" to the console
30569 * $state.go('list', { item: "Ringo" });
30572 * This is a more complex example of a type that relies on dependency injection to
30573 * interact with services, and uses the parameter name from the URL to infer how to
30574 * handle encoding and decoding parameter values:
30577 * // Defines a custom type that gets a value from a service,
30578 * // where each service gets different types of values from
30579 * // a backend API:
30580 * $urlMatcherFactoryProvider.type('dbObject', {}, function(Users, Posts) {
30582 * // Matches up services to URL parameter names
30589 * encode: function(object) {
30590 * // Represent the object in the URL using its unique ID
30591 * return object.id;
30593 * decode: function(value, key) {
30594 * // Look up the object by ID, using the parameter
30595 * // name (key) to call the correct service
30596 * return services[key].findById(value);
30598 * is: function(object, key) {
30599 * // Check that object is a valid dbObject
30600 * return angular.isObject(object) && object.id && services[key];
30602 * equals: function(a, b) {
30603 * // Check the equality of decoded objects by comparing
30604 * // their unique IDs
30605 * return a.id === b.id;
30610 * // In a config() block, you can then attach URLs with
30611 * // type-annotated parameters:
30612 * $stateProvider.state('users', {
30615 * }).state('users.item', {
30616 * url: "/{user:dbObject}",
30617 * controller: function($scope, $stateParams) {
30618 * // $stateParams.user will now be an object returned from
30619 * // the Users service
30625 this.type = function (name, definition, definitionFn) {
30626 if (!isDefined(definition)) return $types[name];
30627 if ($types.hasOwnProperty(name)) throw new Error("A type named '" + name + "' has already been defined.");
30629 $types[name] = new Type(extend({ name: name }, definition));
30630 if (definitionFn) {
30631 typeQueue.push({ name: name, def: definitionFn });
30632 if (!enqueue) flushTypeQueue();
30637 // `flushTypeQueue()` waits until `$urlMatcherFactory` is injected before invoking the queued `definitionFn`s
30638 function flushTypeQueue() {
30639 while(typeQueue.length) {
30640 var type = typeQueue.shift();
30641 if (type.pattern) throw new Error("You cannot override a type's .pattern at runtime.");
30642 angular.extend($types[type.name], injector.invoke(type.def));
30646 // Register default types. Store them in the prototype of $types.
30647 forEach(defaultTypes, function(type, name) { $types[name] = new Type(extend({name: name}, type)); });
30648 $types = inherit($types, {});
30650 /* No need to document $get, since it returns this */
30651 this.$get = ['$injector', function ($injector) {
30652 injector = $injector;
30656 forEach(defaultTypes, function(type, name) {
30657 if (!$types[name]) $types[name] = new Type(type);
30662 this.Param = function Param(id, type, config, location) {
30664 config = unwrapShorthand(config);
30665 type = getType(config, type, location);
30666 var arrayMode = getArrayMode();
30667 type = arrayMode ? type.$asArray(arrayMode, location === "search") : type;
30668 if (type.name === "string" && !arrayMode && location === "path" && config.value === undefined)
30669 config.value = ""; // for 0.2.x; in 0.3.0+ do not automatically default to ""
30670 var isOptional = config.value !== undefined;
30671 var squash = getSquashPolicy(config, isOptional);
30672 var replace = getReplace(config, arrayMode, isOptional, squash);
30674 function unwrapShorthand(config) {
30675 var keys = isObject(config) ? objectKeys(config) : [];
30676 var isShorthand = indexOf(keys, "value") === -1 && indexOf(keys, "type") === -1 &&
30677 indexOf(keys, "squash") === -1 && indexOf(keys, "array") === -1;
30678 if (isShorthand) config = { value: config };
30679 config.$$fn = isInjectable(config.value) ? config.value : function () { return config.value; };
30683 function getType(config, urlType, location) {
30684 if (config.type && urlType) throw new Error("Param '"+id+"' has two type configurations.");
30685 if (urlType) return urlType;
30686 if (!config.type) return (location === "config" ? $types.any : $types.string);
30687 return config.type instanceof Type ? config.type : new Type(config.type);
30690 // array config: param name (param[]) overrides default settings. explicit config overrides param name.
30691 function getArrayMode() {
30692 var arrayDefaults = { array: (location === "search" ? "auto" : false) };
30693 var arrayParamNomenclature = id.match(/\[\]$/) ? { array: true } : {};
30694 return extend(arrayDefaults, arrayParamNomenclature, config).array;
30698 * returns false, true, or the squash value to indicate the "default parameter url squash policy".
30700 function getSquashPolicy(config, isOptional) {
30701 var squash = config.squash;
30702 if (!isOptional || squash === false) return false;
30703 if (!isDefined(squash) || squash == null) return defaultSquashPolicy;
30704 if (squash === true || isString(squash)) return squash;
30705 throw new Error("Invalid squash policy: '" + squash + "'. Valid policies: false, true, or arbitrary string");
30708 function getReplace(config, arrayMode, isOptional, squash) {
30709 var replace, configuredKeys, defaultPolicy = [
30710 { from: "", to: (isOptional || arrayMode ? undefined : "") },
30711 { from: null, to: (isOptional || arrayMode ? undefined : "") }
30713 replace = isArray(config.replace) ? config.replace : [];
30714 if (isString(squash))
30715 replace.push({ from: squash, to: undefined });
30716 configuredKeys = map(replace, function(item) { return item.from; } );
30717 return filter(defaultPolicy, function(item) { return indexOf(configuredKeys, item.from) === -1; }).concat(replace);
30721 * [Internal] Get the default value of a parameter, which may be an injectable function.
30723 function $$getDefaultValue() {
30724 if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
30725 var defaultValue = injector.invoke(config.$$fn);
30726 if (defaultValue !== null && defaultValue !== undefined && !self.type.is(defaultValue))
30727 throw new Error("Default value (" + defaultValue + ") for parameter '" + self.id + "' is not an instance of Type (" + self.type.name + ")");
30728 return defaultValue;
30732 * [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the
30733 * default value, which may be the result of an injectable function.
30735 function $value(value) {
30736 function hasReplaceVal(val) { return function(obj) { return obj.from === val; }; }
30737 function $replace(value) {
30738 var replacement = map(filter(self.replace, hasReplaceVal(value)), function(obj) { return obj.to; });
30739 return replacement.length ? replacement[0] : value;
30741 value = $replace(value);
30742 return !isDefined(value) ? $$getDefaultValue() : self.type.$normalize(value);
30745 function toString() { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}"; }
30750 location: location,
30754 isOptional: isOptional,
30756 dynamic: undefined,
30762 function ParamSet(params) {
30763 extend(this, params || {});
30766 ParamSet.prototype = {
30767 $$new: function() {
30768 return inherit(this, extend(new ParamSet(), { $$parent: this}));
30770 $$keys: function () {
30771 var keys = [], chain = [], parent = this,
30772 ignore = objectKeys(ParamSet.prototype);
30773 while (parent) { chain.push(parent); parent = parent.$$parent; }
30775 forEach(chain, function(paramset) {
30776 forEach(objectKeys(paramset), function(key) {
30777 if (indexOf(keys, key) === -1 && indexOf(ignore, key) === -1) keys.push(key);
30782 $$values: function(paramValues) {
30783 var values = {}, self = this;
30784 forEach(self.$$keys(), function(key) {
30785 values[key] = self[key].value(paramValues && paramValues[key]);
30789 $$equals: function(paramValues1, paramValues2) {
30790 var equal = true, self = this;
30791 forEach(self.$$keys(), function(key) {
30792 var left = paramValues1 && paramValues1[key], right = paramValues2 && paramValues2[key];
30793 if (!self[key].type.equals(left, right)) equal = false;
30797 $$validates: function $$validate(paramValues) {
30798 var keys = this.$$keys(), i, param, rawVal, normalized, encoded;
30799 for (i = 0; i < keys.length; i++) {
30800 param = this[keys[i]];
30801 rawVal = paramValues[keys[i]];
30802 if ((rawVal === undefined || rawVal === null) && param.isOptional)
30803 break; // There was no parameter value, but the param is optional
30804 normalized = param.type.$normalize(rawVal);
30805 if (!param.type.is(normalized))
30806 return false; // The value was not of the correct Type, and could not be decoded to the correct Type
30807 encoded = param.type.encode(normalized);
30808 if (angular.isString(encoded) && !param.type.pattern.exec(encoded))
30809 return false; // The value was of the correct type, but when encoded, did not match the Type's regexp
30813 $$parent: undefined
30816 this.ParamSet = ParamSet;
30819 // Register as a provider so it's available to other providers
30820 angular.module('ui.router.util').provider('$urlMatcherFactory', $UrlMatcherFactory);
30821 angular.module('ui.router.util').run(['$urlMatcherFactory', function($urlMatcherFactory) { }]);
30825 * @name ui.router.router.$urlRouterProvider
30827 * @requires ui.router.util.$urlMatcherFactoryProvider
30828 * @requires $locationProvider
30831 * `$urlRouterProvider` has the responsibility of watching `$location`.
30832 * When `$location` changes it runs through a list of rules one by one until a
30833 * match is found. `$urlRouterProvider` is used behind the scenes anytime you specify
30834 * a url in a state configuration. All urls are compiled into a UrlMatcher object.
30836 * There are several methods on `$urlRouterProvider` that make it useful to use directly
30837 * in your module config.
30839 $UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider'];
30840 function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
30841 var rules = [], otherwise = null, interceptDeferred = false, listener;
30843 // Returns a string that is a prefix of all strings matching the RegExp
30844 function regExpPrefix(re) {
30845 var prefix = /^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(re.source);
30846 return (prefix != null) ? prefix[1].replace(/\\(.)/g, "$1") : '';
30849 // Interpolates matched values into a String.replace()-style pattern
30850 function interpolate(pattern, match) {
30851 return pattern.replace(/\$(\$|\d{1,2})/, function (m, what) {
30852 return match[what === '$' ? 0 : Number(what)];
30858 * @name ui.router.router.$urlRouterProvider#rule
30859 * @methodOf ui.router.router.$urlRouterProvider
30862 * Defines rules that are used by `$urlRouterProvider` to find matches for
30867 * var app = angular.module('app', ['ui.router.router']);
30869 * app.config(function ($urlRouterProvider) {
30870 * // Here's an example of how you might allow case insensitive urls
30871 * $urlRouterProvider.rule(function ($injector, $location) {
30872 * var path = $location.path(),
30873 * normalized = path.toLowerCase();
30875 * if (path !== normalized) {
30876 * return normalized;
30882 * @param {object} rule Handler function that takes `$injector` and `$location`
30883 * services as arguments. You can use them to return a valid path as a string.
30885 * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
30887 this.rule = function (rule) {
30888 if (!isFunction(rule)) throw new Error("'rule' must be a function");
30895 * @name ui.router.router.$urlRouterProvider#otherwise
30896 * @methodOf ui.router.router.$urlRouterProvider
30899 * Defines a path that is used when an invalid route is requested.
30903 * var app = angular.module('app', ['ui.router.router']);
30905 * app.config(function ($urlRouterProvider) {
30906 * // if the path doesn't match any of the urls you configured
30907 * // otherwise will take care of routing the user to the
30909 * $urlRouterProvider.otherwise('/index');
30911 * // Example of using function rule as param
30912 * $urlRouterProvider.otherwise(function ($injector, $location) {
30913 * return '/a/valid/url';
30918 * @param {string|object} rule The url path you want to redirect to or a function
30919 * rule that returns the url path. The function version is passed two params:
30920 * `$injector` and `$location` services, and must return a url string.
30922 * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
30924 this.otherwise = function (rule) {
30925 if (isString(rule)) {
30926 var redirect = rule;
30927 rule = function () { return redirect; };
30929 else if (!isFunction(rule)) throw new Error("'rule' must be a function");
30935 function handleIfMatch($injector, handler, match) {
30936 if (!match) return false;
30937 var result = $injector.invoke(handler, handler, { $match: match });
30938 return isDefined(result) ? result : true;
30943 * @name ui.router.router.$urlRouterProvider#when
30944 * @methodOf ui.router.router.$urlRouterProvider
30947 * Registers a handler for a given url matching. if handle is a string, it is
30948 * treated as a redirect, and is interpolated according to the syntax of match
30949 * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise).
30951 * If the handler is a function, it is injectable. It gets invoked if `$location`
30952 * matches. You have the option of inject the match object as `$match`.
30954 * The handler can return
30956 * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter`
30957 * will continue trying to find another one that matches.
30958 * - **string** which is treated as a redirect and passed to `$location.url()`
30959 * - **void** or any **truthy** value tells `$urlRouter` that the url was handled.
30963 * var app = angular.module('app', ['ui.router.router']);
30965 * app.config(function ($urlRouterProvider) {
30966 * $urlRouterProvider.when($state.url, function ($match, $stateParams) {
30967 * if ($state.$current.navigable !== state ||
30968 * !equalForKeys($match, $stateParams) {
30969 * $state.transitionTo(state, $match, false);
30975 * @param {string|object} what The incoming path that you want to redirect.
30976 * @param {string|object} handler The path you want to redirect your user to.
30978 this.when = function (what, handler) {
30979 var redirect, handlerIsString = isString(handler);
30980 if (isString(what)) what = $urlMatcherFactory.compile(what);
30982 if (!handlerIsString && !isFunction(handler) && !isArray(handler))
30983 throw new Error("invalid 'handler' in when()");
30986 matcher: function (what, handler) {
30987 if (handlerIsString) {
30988 redirect = $urlMatcherFactory.compile(handler);
30989 handler = ['$match', function ($match) { return redirect.format($match); }];
30991 return extend(function ($injector, $location) {
30992 return handleIfMatch($injector, handler, what.exec($location.path(), $location.search()));
30994 prefix: isString(what.prefix) ? what.prefix : ''
30997 regex: function (what, handler) {
30998 if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky");
31000 if (handlerIsString) {
31001 redirect = handler;
31002 handler = ['$match', function ($match) { return interpolate(redirect, $match); }];
31004 return extend(function ($injector, $location) {
31005 return handleIfMatch($injector, handler, what.exec($location.path()));
31007 prefix: regExpPrefix(what)
31012 var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp };
31014 for (var n in check) {
31015 if (check[n]) return this.rule(strategies[n](what, handler));
31018 throw new Error("invalid 'what' in when()");
31023 * @name ui.router.router.$urlRouterProvider#deferIntercept
31024 * @methodOf ui.router.router.$urlRouterProvider
31027 * Disables (or enables) deferring location change interception.
31029 * If you wish to customize the behavior of syncing the URL (for example, if you wish to
31030 * defer a transition but maintain the current URL), call this method at configuration time.
31031 * Then, at run time, call `$urlRouter.listen()` after you have configured your own
31032 * `$locationChangeSuccess` event handler.
31036 * var app = angular.module('app', ['ui.router.router']);
31038 * app.config(function ($urlRouterProvider) {
31040 * // Prevent $urlRouter from automatically intercepting URL changes;
31041 * // this allows you to configure custom behavior in between
31042 * // location changes and route synchronization:
31043 * $urlRouterProvider.deferIntercept();
31045 * }).run(function ($rootScope, $urlRouter, UserService) {
31047 * $rootScope.$on('$locationChangeSuccess', function(e) {
31048 * // UserService is an example service for managing user state
31049 * if (UserService.isLoggedIn()) return;
31051 * // Prevent $urlRouter's default handler from firing
31052 * e.preventDefault();
31054 * UserService.handleLogin().then(function() {
31055 * // Once the user has logged in, sync the current URL
31056 * // to the router:
31057 * $urlRouter.sync();
31061 * // Configures $urlRouter's listener *after* your custom listener
31062 * $urlRouter.listen();
31066 * @param {boolean} defer Indicates whether to defer location change interception. Passing
31067 no parameter is equivalent to `true`.
31069 this.deferIntercept = function (defer) {
31070 if (defer === undefined) defer = true;
31071 interceptDeferred = defer;
31076 * @name ui.router.router.$urlRouter
31078 * @requires $location
31079 * @requires $rootScope
31080 * @requires $injector
31081 * @requires $browser
31087 $get.$inject = ['$location', '$rootScope', '$injector', '$browser'];
31088 function $get( $location, $rootScope, $injector, $browser) {
31090 var baseHref = $browser.baseHref(), location = $location.url(), lastPushedUrl;
31092 function appendBasePath(url, isHtml5, absolute) {
31093 if (baseHref === '/') return url;
31094 if (isHtml5) return baseHref.slice(0, -1) + url;
31095 if (absolute) return baseHref.slice(1) + url;
31099 // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree
31100 function update(evt) {
31101 if (evt && evt.defaultPrevented) return;
31102 var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl;
31103 lastPushedUrl = undefined;
31104 // TODO: Re-implement this in 1.0 for https://github.com/angular-ui/ui-router/issues/1573
31105 //if (ignoreUpdate) return true;
31107 function check(rule) {
31108 var handled = rule($injector, $location);
31110 if (!handled) return false;
31111 if (isString(handled)) $location.replace().url(handled);
31114 var n = rules.length, i;
31116 for (i = 0; i < n; i++) {
31117 if (check(rules[i])) return;
31119 // always check otherwise last to allow dynamic updates to the set of rules
31120 if (otherwise) check(otherwise);
31123 function listen() {
31124 listener = listener || $rootScope.$on('$locationChangeSuccess', update);
31128 if (!interceptDeferred) listen();
31133 * @name ui.router.router.$urlRouter#sync
31134 * @methodOf ui.router.router.$urlRouter
31137 * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`.
31138 * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event,
31139 * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed
31140 * with the transition by calling `$urlRouter.sync()`.
31144 * angular.module('app', ['ui.router'])
31145 * .run(function($rootScope, $urlRouter) {
31146 * $rootScope.$on('$locationChangeSuccess', function(evt) {
31147 * // Halt state change from even starting
31148 * evt.preventDefault();
31149 * // Perform custom logic
31150 * var meetsRequirement = ...
31151 * // Continue with the update and state transition if logic allows
31152 * if (meetsRequirement) $urlRouter.sync();
31161 listen: function() {
31165 update: function(read) {
31167 location = $location.url();
31170 if ($location.url() === location) return;
31172 $location.url(location);
31173 $location.replace();
31176 push: function(urlMatcher, params, options) {
31177 var url = urlMatcher.format(params || {});
31179 // Handle the special hash param, if needed
31180 if (url !== null && params && params['#']) {
31181 url += '#' + params['#'];
31184 $location.url(url);
31185 lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined;
31186 if (options && options.replace) $location.replace();
31191 * @name ui.router.router.$urlRouter#href
31192 * @methodOf ui.router.router.$urlRouter
31195 * A URL generation method that returns the compiled URL for a given
31196 * {@link ui.router.util.type:UrlMatcher `UrlMatcher`}, populated with the provided parameters.
31200 * $bob = $urlRouter.href(new UrlMatcher("/about/:person"), {
31203 * // $bob == "/about/bob";
31206 * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate.
31207 * @param {object=} params An object of parameter values to fill the matcher's required parameters.
31208 * @param {object=} options Options object. The options are:
31210 * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
31212 * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher`
31214 href: function(urlMatcher, params, options) {
31215 if (!urlMatcher.validates(params)) return null;
31217 var isHtml5 = $locationProvider.html5Mode();
31218 if (angular.isObject(isHtml5)) {
31219 isHtml5 = isHtml5.enabled;
31222 var url = urlMatcher.format(params);
31223 options = options || {};
31225 if (!isHtml5 && url !== null) {
31226 url = "#" + $locationProvider.hashPrefix() + url;
31229 // Handle special hash param, if needed
31230 if (url !== null && params && params['#']) {
31231 url += '#' + params['#'];
31234 url = appendBasePath(url, isHtml5, options.absolute);
31236 if (!options.absolute || !url) {
31240 var slash = (!isHtml5 && url ? '/' : ''), port = $location.port();
31241 port = (port === 80 || port === 443 ? '' : ':' + port);
31243 return [$location.protocol(), '://', $location.host(), port, slash, url].join('');
31249 angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);
31253 * @name ui.router.state.$stateProvider
31255 * @requires ui.router.router.$urlRouterProvider
31256 * @requires ui.router.util.$urlMatcherFactoryProvider
31259 * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely
31262 * A state corresponds to a "place" in the application in terms of the overall UI and
31263 * navigation. A state describes (via the controller / template / view properties) what
31264 * the UI looks like and does at that place.
31266 * States often have things in common, and the primary way of factoring out these
31267 * commonalities in this model is via the state hierarchy, i.e. parent/child states aka
31270 * The `$stateProvider` provides interfaces to declare these states for your app.
31272 $StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider'];
31273 function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
31275 var root, states = {}, $state, queue = {}, abstractKey = 'abstract';
31277 // Builds state properties from definition passed to registerState()
31278 var stateBuilder = {
31280 // Derive parent state from a hierarchical name only if 'parent' is not explicitly defined.
31281 // state.children = [];
31282 // if (parent) parent.children.push(state);
31283 parent: function(state) {
31284 if (isDefined(state.parent) && state.parent) return findState(state.parent);
31285 // regex matches any valid composite state name
31286 // would match "contact.list" but not "contacts"
31287 var compositeName = /^(.+)\.[^.]+$/.exec(state.name);
31288 return compositeName ? findState(compositeName[1]) : root;
31291 // inherit 'data' from parent and override by own values (if any)
31292 data: function(state) {
31293 if (state.parent && state.parent.data) {
31294 state.data = state.self.data = extend({}, state.parent.data, state.data);
31299 // Build a URLMatcher if necessary, either via a relative or absolute URL
31300 url: function(state) {
31301 var url = state.url, config = { params: state.params || {} };
31303 if (isString(url)) {
31304 if (url.charAt(0) == '^') return $urlMatcherFactory.compile(url.substring(1), config);
31305 return (state.parent.navigable || root).url.concat(url, config);
31308 if (!url || $urlMatcherFactory.isMatcher(url)) return url;
31309 throw new Error("Invalid url '" + url + "' in state '" + state + "'");
31312 // Keep track of the closest ancestor state that has a URL (i.e. is navigable)
31313 navigable: function(state) {
31314 return state.url ? state : (state.parent ? state.parent.navigable : null);
31317 // Own parameters for this state. state.url.params is already built at this point. Create and add non-url params
31318 ownParams: function(state) {
31319 var params = state.url && state.url.params || new $$UMFP.ParamSet();
31320 forEach(state.params || {}, function(config, id) {
31321 if (!params[id]) params[id] = new $$UMFP.Param(id, null, config, "config");
31326 // Derive parameters for this state and ensure they're a super-set of parent's parameters
31327 params: function(state) {
31328 return state.parent && state.parent.params ? extend(state.parent.params.$$new(), state.ownParams) : new $$UMFP.ParamSet();
31331 // If there is no explicit multi-view configuration, make one up so we don't have
31332 // to handle both cases in the view directive later. Note that having an explicit
31333 // 'views' property will mean the default unnamed view properties are ignored. This
31334 // is also a good time to resolve view names to absolute names, so everything is a
31335 // straight lookup at link time.
31336 views: function(state) {
31339 forEach(isDefined(state.views) ? state.views : { '': state }, function (view, name) {
31340 if (name.indexOf('@') < 0) name += '@' + state.parent.name;
31341 views[name] = view;
31346 // Keep a full path from the root down to this state as this is needed for state activation.
31347 path: function(state) {
31348 return state.parent ? state.parent.path.concat(state) : []; // exclude root from path
31351 // Speed up $state.contains() as it's used a lot
31352 includes: function(state) {
31353 var includes = state.parent ? extend({}, state.parent.includes) : {};
31354 includes[state.name] = true;
31361 function isRelative(stateName) {
31362 return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0;
31365 function findState(stateOrName, base) {
31366 if (!stateOrName) return undefined;
31368 var isStr = isString(stateOrName),
31369 name = isStr ? stateOrName : stateOrName.name,
31370 path = isRelative(name);
31373 if (!base) throw new Error("No reference point given for path '" + name + "'");
31374 base = findState(base);
31376 var rel = name.split("."), i = 0, pathLength = rel.length, current = base;
31378 for (; i < pathLength; i++) {
31379 if (rel[i] === "" && i === 0) {
31383 if (rel[i] === "^") {
31384 if (!current.parent) throw new Error("Path '" + name + "' not valid for state '" + base.name + "'");
31385 current = current.parent;
31390 rel = rel.slice(i).join(".");
31391 name = current.name + (current.name && rel ? "." : "") + rel;
31393 var state = states[name];
31395 if (state && (isStr || (!isStr && (state === stateOrName || state.self === stateOrName)))) {
31401 function queueState(parentName, state) {
31402 if (!queue[parentName]) {
31403 queue[parentName] = [];
31405 queue[parentName].push(state);
31408 function flushQueuedChildren(parentName) {
31409 var queued = queue[parentName] || [];
31410 while(queued.length) {
31411 registerState(queued.shift());
31415 function registerState(state) {
31416 // Wrap a new object around the state so we can store our private details easily.
31417 state = inherit(state, {
31419 resolve: state.resolve || {},
31420 toString: function() { return this.name; }
31423 var name = state.name;
31424 if (!isString(name) || name.indexOf('@') >= 0) throw new Error("State must have a valid name");
31425 if (states.hasOwnProperty(name)) throw new Error("State '" + name + "'' is already defined");
31428 var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.'))
31429 : (isString(state.parent)) ? state.parent
31430 : (isObject(state.parent) && isString(state.parent.name)) ? state.parent.name
31433 // If parent is not registered yet, add state to queue and register later
31434 if (parentName && !states[parentName]) {
31435 return queueState(parentName, state.self);
31438 for (var key in stateBuilder) {
31439 if (isFunction(stateBuilder[key])) state[key] = stateBuilder[key](state, stateBuilder.$delegates[key]);
31441 states[name] = state;
31443 // Register the state in the global state list and with $urlRouter if necessary.
31444 if (!state[abstractKey] && state.url) {
31445 $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) {
31446 if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) {
31447 $state.transitionTo(state, $match, { inherit: true, location: false });
31452 // Register any queued children
31453 flushQueuedChildren(name);
31458 // Checks text to see if it looks like a glob.
31459 function isGlob (text) {
31460 return text.indexOf('*') > -1;
31463 // Returns true if glob matches current $state name.
31464 function doesStateMatchGlob (glob) {
31465 var globSegments = glob.split('.'),
31466 segments = $state.$current.name.split('.');
31468 //match single stars
31469 for (var i = 0, l = globSegments.length; i < l; i++) {
31470 if (globSegments[i] === '*') {
31475 //match greedy starts
31476 if (globSegments[0] === '**') {
31477 segments = segments.slice(indexOf(segments, globSegments[1]));
31478 segments.unshift('**');
31480 //match greedy ends
31481 if (globSegments[globSegments.length - 1] === '**') {
31482 segments.splice(indexOf(segments, globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE);
31483 segments.push('**');
31486 if (globSegments.length != segments.length) {
31490 return segments.join('') === globSegments.join('');
31494 // Implicit root state that is always active
31495 root = registerState({
31501 root.navigable = null;
31506 * @name ui.router.state.$stateProvider#decorator
31507 * @methodOf ui.router.state.$stateProvider
31510 * Allows you to extend (carefully) or override (at your own peril) the
31511 * `stateBuilder` object used internally by `$stateProvider`. This can be used
31512 * to add custom functionality to ui-router, for example inferring templateUrl
31513 * based on the state name.
31515 * When passing only a name, it returns the current (original or decorated) builder
31516 * function that matches `name`.
31518 * The builder functions that can be decorated are listed below. Though not all
31519 * necessarily have a good use case for decoration, that is up to you to decide.
31521 * In addition, users can attach custom decorators, which will generate new
31522 * properties within the state's internal definition. There is currently no clear
31523 * use-case for this beyond accessing internal states (i.e. $state.$current),
31524 * however, expect this to become increasingly relevant as we introduce additional
31525 * meta-programming features.
31527 * **Warning**: Decorators should not be interdependent because the order of
31528 * execution of the builder functions in non-deterministic. Builder functions
31529 * should only be dependent on the state definition object and super function.
31532 * Existing builder functions and current return values:
31534 * - **parent** `{object}` - returns the parent state object.
31535 * - **data** `{object}` - returns state data, including any inherited data that is not
31536 * overridden by own values (if any).
31537 * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher}
31539 * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is
31541 * - **params** `{object}` - returns an array of state params that are ensured to
31542 * be a super-set of parent's params.
31543 * - **views** `{object}` - returns a views object where each key is an absolute view
31544 * name (i.e. "viewName@stateName") and each value is the config object
31545 * (template, controller) for the view. Even when you don't use the views object
31546 * explicitly on a state config, one is still created for you internally.
31547 * So by decorating this builder function you have access to decorating template
31548 * and controller properties.
31549 * - **ownParams** `{object}` - returns an array of params that belong to the state,
31550 * not including any params defined by ancestor states.
31551 * - **path** `{string}` - returns the full path from the root down to this state.
31552 * Needed for state activation.
31553 * - **includes** `{object}` - returns an object that includes every state that
31554 * would pass a `$state.includes()` test.
31558 * // Override the internal 'views' builder with a function that takes the state
31559 * // definition, and a reference to the internal function being overridden:
31560 * $stateProvider.decorator('views', function (state, parent) {
31562 * views = parent(state);
31564 * angular.forEach(views, function (config, name) {
31565 * var autoName = (state.name + '.' + name).replace('.', '/');
31566 * config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';
31567 * result[name] = config;
31572 * $stateProvider.state('home', {
31574 * 'contact.list': { controller: 'ListController' },
31575 * 'contact.item': { controller: 'ItemController' }
31581 * $state.go('home');
31582 * // Auto-populates list and item views with /partials/home/contact/list.html,
31583 * // and /partials/home/contact/item.html, respectively.
31586 * @param {string} name The name of the builder function to decorate.
31587 * @param {object} func A function that is responsible for decorating the original
31588 * builder function. The function receives two parameters:
31590 * - `{object}` - state - The state config object.
31591 * - `{object}` - super - The original builder function.
31593 * @return {object} $stateProvider - $stateProvider instance
31595 this.decorator = decorator;
31596 function decorator(name, func) {
31597 /*jshint validthis: true */
31598 if (isString(name) && !isDefined(func)) {
31599 return stateBuilder[name];
31601 if (!isFunction(func) || !isString(name)) {
31604 if (stateBuilder[name] && !stateBuilder.$delegates[name]) {
31605 stateBuilder.$delegates[name] = stateBuilder[name];
31607 stateBuilder[name] = func;
31613 * @name ui.router.state.$stateProvider#state
31614 * @methodOf ui.router.state.$stateProvider
31617 * Registers a state configuration under a given state name. The stateConfig object
31618 * has the following acceptable properties.
31620 * @param {string} name A unique state name, e.g. "home", "about", "contacts".
31621 * To create a parent/child state use a dot, e.g. "about.sales", "home.newest".
31622 * @param {object} stateConfig State configuration object.
31623 * @param {string|function=} stateConfig.template
31624 * <a id='template'></a>
31625 * html template as a string or a function that returns
31626 * an html template as a string which should be used by the uiView directives. This property
31627 * takes precedence over templateUrl.
31629 * If `template` is a function, it will be called with the following parameters:
31631 * - {array.<object>} - state parameters extracted from the current $location.path() by
31632 * applying the current state
31635 * "<h1>inline template definition</h1>" +
31636 * "<div ui-view></div>"</pre>
31637 * <pre>template: function(params) {
31638 * return "<h1>generated template</h1>"; }</pre>
31641 * @param {string|function=} stateConfig.templateUrl
31642 * <a id='templateUrl'></a>
31644 * path or function that returns a path to an html
31645 * template that should be used by uiView.
31647 * If `templateUrl` is a function, it will be called with the following parameters:
31649 * - {array.<object>} - state parameters extracted from the current $location.path() by
31650 * applying the current state
31652 * <pre>templateUrl: "home.html"</pre>
31653 * <pre>templateUrl: function(params) {
31654 * return myTemplates[params.pageId]; }</pre>
31656 * @param {function=} stateConfig.templateProvider
31657 * <a id='templateProvider'></a>
31658 * Provider function that returns HTML content string.
31659 * <pre> templateProvider:
31660 * function(MyTemplateService, params) {
31661 * return MyTemplateService.getTemplate(params.pageId);
31664 * @param {string|function=} stateConfig.controller
31665 * <a id='controller'></a>
31667 * Controller fn that should be associated with newly
31668 * related scope or the name of a registered controller if passed as a string.
31669 * Optionally, the ControllerAs may be declared here.
31670 * <pre>controller: "MyRegisteredController"</pre>
31672 * "MyRegisteredController as fooCtrl"}</pre>
31673 * <pre>controller: function($scope, MyService) {
31674 * $scope.data = MyService.getData(); }</pre>
31676 * @param {function=} stateConfig.controllerProvider
31677 * <a id='controllerProvider'></a>
31679 * Injectable provider function that returns the actual controller or string.
31680 * <pre>controllerProvider:
31681 * function(MyResolveData) {
31682 * if (MyResolveData.foo)
31684 * else if (MyResolveData.bar)
31685 * return "BarCtrl";
31686 * else return function($scope) {
31687 * $scope.baz = "Qux";
31691 * @param {string=} stateConfig.controllerAs
31692 * <a id='controllerAs'></a>
31694 * A controller alias name. If present the controller will be
31695 * published to scope under the controllerAs name.
31696 * <pre>controllerAs: "myCtrl"</pre>
31698 * @param {string|object=} stateConfig.parent
31699 * <a id='parent'></a>
31700 * Optionally specifies the parent state of this state.
31702 * <pre>parent: 'parentState'</pre>
31703 * <pre>parent: parentState // JS variable</pre>
31705 * @param {object=} stateConfig.resolve
31706 * <a id='resolve'></a>
31708 * An optional map<string, function> of dependencies which
31709 * should be injected into the controller. If any of these dependencies are promises,
31710 * the router will wait for them all to be resolved before the controller is instantiated.
31711 * If all the promises are resolved successfully, the $stateChangeSuccess event is fired
31712 * and the values of the resolved promises are injected into any controllers that reference them.
31713 * If any of the promises are rejected the $stateChangeError event is fired.
31715 * The map object is:
31717 * - key - {string}: name of dependency to be injected into controller
31718 * - factory - {string|function}: If string then it is alias for service. Otherwise if function,
31719 * it is injected and return value it treated as dependency. If result is a promise, it is
31720 * resolved before its value is injected into controller.
31724 * function($http, $stateParams) {
31725 * return $http.get("/api/foos/"+stateParams.fooID);
31729 * @param {string=} stateConfig.url
31732 * A url fragment with optional parameters. When a state is navigated or
31733 * transitioned to, the `$stateParams` service will be populated with any
31734 * parameters that were passed.
31736 * (See {@link ui.router.util.type:UrlMatcher UrlMatcher} `UrlMatcher`} for
31737 * more details on acceptable patterns )
31740 * <pre>url: "/home"
31741 * url: "/users/:userid"
31742 * url: "/books/{bookid:[a-zA-Z_-]}"
31743 * url: "/books/{categoryid:int}"
31744 * url: "/books/{publishername:string}/{categoryid:int}"
31745 * url: "/messages?before&after"
31746 * url: "/messages?{before:date}&{after:date}"
31747 * url: "/messages/:mailboxid?{before:date}&{after:date}"
31750 * @param {object=} stateConfig.views
31751 * <a id='views'></a>
31752 * an optional map<string, object> which defined multiple views, or targets views
31753 * manually/explicitly.
31757 * Targets three named `ui-view`s in the parent state's template
31760 * controller: "headerCtrl",
31761 * templateUrl: "header.html"
31763 * controller: "bodyCtrl",
31764 * templateUrl: "body.html"
31766 * controller: "footCtrl",
31767 * templateUrl: "footer.html"
31771 * Targets named `ui-view="header"` from grandparent state 'top''s template, and named `ui-view="body" from parent state's template.
31774 * controller: "msgHeaderCtrl",
31775 * templateUrl: "msgHeader.html"
31777 * controller: "messagesCtrl",
31778 * templateUrl: "messages.html"
31782 * @param {boolean=} [stateConfig.abstract=false]
31783 * <a id='abstract'></a>
31784 * An abstract state will never be directly activated,
31785 * but can provide inherited properties to its common children states.
31786 * <pre>abstract: true</pre>
31788 * @param {function=} stateConfig.onEnter
31789 * <a id='onEnter'></a>
31791 * Callback function for when a state is entered. Good way
31792 * to trigger an action or dispatch an event, such as opening a dialog.
31793 * If minifying your scripts, make sure to explictly annotate this function,
31794 * because it won't be automatically annotated by your build tools.
31796 * <pre>onEnter: function(MyService, $stateParams) {
31797 * MyService.foo($stateParams.myParam);
31800 * @param {function=} stateConfig.onExit
31801 * <a id='onExit'></a>
31803 * Callback function for when a state is exited. Good way to
31804 * trigger an action or dispatch an event, such as opening a dialog.
31805 * If minifying your scripts, make sure to explictly annotate this function,
31806 * because it won't be automatically annotated by your build tools.
31808 * <pre>onExit: function(MyService, $stateParams) {
31809 * MyService.cleanup($stateParams.myParam);
31812 * @param {boolean=} [stateConfig.reloadOnSearch=true]
31813 * <a id='reloadOnSearch'></a>
31815 * If `false`, will not retrigger the same state
31816 * just because a search/query parameter has changed (via $location.search() or $location.hash()).
31817 * Useful for when you'd like to modify $location.search() without triggering a reload.
31818 * <pre>reloadOnSearch: false</pre>
31820 * @param {object=} stateConfig.data
31821 * <a id='data'></a>
31823 * Arbitrary data object, useful for custom configuration. The parent state's `data` is
31824 * prototypally inherited. In other words, adding a data property to a state adds it to
31825 * the entire subtree via prototypal inheritance.
31828 * requiredRole: 'foo'
31831 * @param {object=} stateConfig.params
31832 * <a id='params'></a>
31834 * A map which optionally configures parameters declared in the `url`, or
31835 * defines additional non-url parameters. For each parameter being
31836 * configured, add a configuration object keyed to the name of the parameter.
31838 * Each parameter configuration object may contain the following properties:
31840 * - ** value ** - {object|function=}: specifies the default value for this
31841 * parameter. This implicitly sets this parameter as optional.
31843 * When UI-Router routes to a state and no value is
31844 * specified for this parameter in the URL or transition, the
31845 * default value will be used instead. If `value` is a function,
31846 * it will be injected and invoked, and the return value used.
31848 * *Note*: `undefined` is treated as "no default value" while `null`
31849 * is treated as "the default value is `null`".
31851 * *Shorthand*: If you only need to configure the default value of the
31852 * parameter, you may use a shorthand syntax. In the **`params`**
31853 * map, instead mapping the param name to a full parameter configuration
31854 * object, simply set map it to the default parameter value, e.g.:
31856 * <pre>// define a parameter's default value
31858 * param1: { value: "defaultValue" }
31860 * // shorthand default values
31862 * param1: "defaultValue",
31863 * param2: "param2Default"
31866 * - ** array ** - {boolean=}: *(default: false)* If true, the param value will be
31867 * treated as an array of values. If you specified a Type, the value will be
31868 * treated as an array of the specified Type. Note: query parameter values
31869 * default to a special `"auto"` mode.
31871 * For query parameters in `"auto"` mode, if multiple values for a single parameter
31872 * are present in the URL (e.g.: `/foo?bar=1&bar=2&bar=3`) then the values
31873 * are mapped to an array (e.g.: `{ foo: [ '1', '2', '3' ] }`). However, if
31874 * only one value is present (e.g.: `/foo?bar=1`) then the value is treated as single
31875 * value (e.g.: `{ foo: '1' }`).
31878 * param1: { array: true }
31881 * - ** squash ** - {bool|string=}: `squash` configures how a default parameter value is represented in the URL when
31882 * the current parameter value is the same as the default value. If `squash` is not set, it uses the
31883 * configured default squash policy.
31884 * (See {@link ui.router.util.$urlMatcherFactory#methods_defaultSquashPolicy `defaultSquashPolicy()`})
31886 * There are three squash settings:
31888 * - false: The parameter's default value is not squashed. It is encoded and included in the URL
31889 * - true: The parameter's default value is omitted from the URL. If the parameter is preceeded and followed
31890 * by slashes in the state's `url` declaration, then one of those slashes are omitted.
31891 * This can allow for cleaner looking URLs.
31892 * - `"<arbitrary string>"`: The parameter's default value is replaced with an arbitrary placeholder of your choice.
31896 * value: "defaultId",
31899 * // squash "defaultValue" to "~"
31902 * value: "defaultValue",
31910 * // Some state name examples
31912 * // stateName can be a single top-level name (must be unique).
31913 * $stateProvider.state("home", {});
31915 * // Or it can be a nested state name. This state is a child of the
31916 * // above "home" state.
31917 * $stateProvider.state("home.newest", {});
31919 * // Nest states as deeply as needed.
31920 * $stateProvider.state("home.newest.abc.xyz.inception", {});
31922 * // state() returns $stateProvider, so you can chain state declarations.
31924 * .state("home", {})
31925 * .state("about", {})
31926 * .state("contacts", {});
31930 this.state = state;
31931 function state(name, definition) {
31932 /*jshint validthis: true */
31933 if (isObject(name)) definition = name;
31934 else definition.name = name;
31935 registerState(definition);
31941 * @name ui.router.state.$state
31943 * @requires $rootScope
31945 * @requires ui.router.state.$view
31946 * @requires $injector
31947 * @requires ui.router.util.$resolve
31948 * @requires ui.router.state.$stateParams
31949 * @requires ui.router.router.$urlRouter
31951 * @property {object} params A param object, e.g. {sectionId: section.id)}, that
31952 * you'd like to test against the current active state.
31953 * @property {object} current A reference to the state's config object. However
31954 * you passed it in. Useful for accessing custom data.
31955 * @property {object} transition Currently pending transition. A promise that'll
31956 * resolve or reject.
31959 * `$state` service is responsible for representing states as well as transitioning
31960 * between them. It also provides interfaces to ask for current state or even states
31961 * you're coming from.
31964 $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$urlRouter', '$location', '$urlMatcherFactory'];
31965 function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $urlRouter, $location, $urlMatcherFactory) {
31967 var TransitionSuperseded = $q.reject(new Error('transition superseded'));
31968 var TransitionPrevented = $q.reject(new Error('transition prevented'));
31969 var TransitionAborted = $q.reject(new Error('transition aborted'));
31970 var TransitionFailed = $q.reject(new Error('transition failed'));
31972 // Handles the case where a state which is the target of a transition is not found, and the user
31973 // can optionally retry or defer the transition
31974 function handleRedirect(redirect, state, params, options) {
31977 * @name ui.router.state.$state#$stateNotFound
31978 * @eventOf ui.router.state.$state
31979 * @eventType broadcast on root scope
31981 * Fired when a requested state **cannot be found** using the provided state name during transition.
31982 * The event is broadcast allowing any handlers a single chance to deal with the error (usually by
31983 * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler,
31984 * you can see its three properties in the example. You can use `event.preventDefault()` to abort the
31985 * transition and the promise returned from `go` will be rejected with a `'transition aborted'` value.
31987 * @param {Object} event Event object.
31988 * @param {Object} unfoundState Unfound State information. Contains: `to, toParams, options` properties.
31989 * @param {State} fromState Current state object.
31990 * @param {Object} fromParams Current state params.
31995 * // somewhere, assume lazy.state has not been defined
31996 * $state.go("lazy.state", {a:1, b:2}, {inherit:false});
31998 * // somewhere else
31999 * $scope.$on('$stateNotFound',
32000 * function(event, unfoundState, fromState, fromParams){
32001 * console.log(unfoundState.to); // "lazy.state"
32002 * console.log(unfoundState.toParams); // {a:1, b:2}
32003 * console.log(unfoundState.options); // {inherit:false} + default options
32007 var evt = $rootScope.$broadcast('$stateNotFound', redirect, state, params);
32009 if (evt.defaultPrevented) {
32010 $urlRouter.update();
32011 return TransitionAborted;
32018 // Allow the handler to return a promise to defer state lookup retry
32019 if (options.$retry) {
32020 $urlRouter.update();
32021 return TransitionFailed;
32023 var retryTransition = $state.transition = $q.when(evt.retry);
32025 retryTransition.then(function() {
32026 if (retryTransition !== $state.transition) return TransitionSuperseded;
32027 redirect.options.$retry = true;
32028 return $state.transitionTo(redirect.to, redirect.toParams, redirect.options);
32030 return TransitionAborted;
32032 $urlRouter.update();
32034 return retryTransition;
32037 root.locals = { resolve: null, globals: { $stateParams: {} } };
32041 current: root.self,
32048 * @name ui.router.state.$state#reload
32049 * @methodOf ui.router.state.$state
32052 * A method that force reloads the current state. All resolves are re-resolved,
32053 * controllers reinstantiated, and events re-fired.
32057 * var app angular.module('app', ['ui.router']);
32059 * app.controller('ctrl', function ($scope, $state) {
32060 * $scope.reload = function(){
32066 * `reload()` is just an alias for:
32068 * $state.transitionTo($state.current, $stateParams, {
32069 * reload: true, inherit: false, notify: true
32073 * @param {string=|object=} state - A state name or a state object, which is the root of the resolves to be re-resolved.
32076 * //assuming app application consists of 3 states: 'contacts', 'contacts.detail', 'contacts.detail.item'
32077 * //and current state is 'contacts.detail.item'
32078 * var app angular.module('app', ['ui.router']);
32080 * app.controller('ctrl', function ($scope, $state) {
32081 * $scope.reload = function(){
32082 * //will reload 'contact.detail' and 'contact.detail.item' states
32083 * $state.reload('contact.detail');
32088 * `reload()` is just an alias for:
32090 * $state.transitionTo($state.current, $stateParams, {
32091 * reload: true, inherit: false, notify: true
32095 * @returns {promise} A promise representing the state of the new transition. See
32096 * {@link ui.router.state.$state#methods_go $state.go}.
32098 $state.reload = function reload(state) {
32099 return $state.transitionTo($state.current, $stateParams, { reload: state || true, inherit: false, notify: true});
32104 * @name ui.router.state.$state#go
32105 * @methodOf ui.router.state.$state
32108 * Convenience method for transitioning to a new state. `$state.go` calls
32109 * `$state.transitionTo` internally but automatically sets options to
32110 * `{ location: true, inherit: true, relative: $state.$current, notify: true }`.
32111 * This allows you to easily use an absolute or relative to path and specify
32112 * only the parameters you'd like to update (while letting unspecified parameters
32113 * inherit from the currently active ancestor states).
32117 * var app = angular.module('app', ['ui.router']);
32119 * app.controller('ctrl', function ($scope, $state) {
32120 * $scope.changeState = function () {
32121 * $state.go('contact.detail');
32125 * <img src='../ngdoc_assets/StateGoExamples.png'/>
32127 * @param {string} to Absolute state name or relative state path. Some examples:
32129 * - `$state.go('contact.detail')` - will go to the `contact.detail` state
32130 * - `$state.go('^')` - will go to a parent state
32131 * - `$state.go('^.sibling')` - will go to a sibling state
32132 * - `$state.go('.child.grandchild')` - will go to grandchild state
32134 * @param {object=} params A map of the parameters that will be sent to the state,
32135 * will populate $stateParams. Any parameters that are not specified will be inherited from currently
32136 * defined parameters. This allows, for example, going to a sibling state that shares parameters
32137 * specified in a parent state. Parameter inheritance only works between common ancestor states, I.e.
32138 * transitioning to a sibling will get you the parameters for all parents, transitioning to a child
32139 * will get you all current parameters, etc.
32140 * @param {object=} options Options object. The options are:
32142 * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
32143 * will not. If string, must be `"replace"`, which will update url and also replace last history record.
32144 * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
32145 * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
32146 * defines which state to be relative from.
32147 * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
32148 * - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params
32149 * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
32150 * use this when you want to force a reload when *everything* is the same, including search params.
32152 * @returns {promise} A promise representing the state of the new transition.
32154 * Possible success values:
32158 * <br/>Possible rejection values:
32160 * - 'transition superseded' - when a newer transition has been started after this one
32161 * - 'transition prevented' - when `event.preventDefault()` has been called in a `$stateChangeStart` listener
32162 * - 'transition aborted' - when `event.preventDefault()` has been called in a `$stateNotFound` listener or
32163 * when a `$stateNotFound` `event.retry` promise errors.
32164 * - 'transition failed' - when a state has been unsuccessfully found after 2 tries.
32165 * - *resolve error* - when an error has occurred with a `resolve`
32168 $state.go = function go(to, params, options) {
32169 return $state.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options));
32174 * @name ui.router.state.$state#transitionTo
32175 * @methodOf ui.router.state.$state
32178 * Low-level method for transitioning to a new state. {@link ui.router.state.$state#methods_go $state.go}
32179 * uses `transitionTo` internally. `$state.go` is recommended in most situations.
32183 * var app = angular.module('app', ['ui.router']);
32185 * app.controller('ctrl', function ($scope, $state) {
32186 * $scope.changeState = function () {
32187 * $state.transitionTo('contact.detail');
32192 * @param {string} to State name.
32193 * @param {object=} toParams A map of the parameters that will be sent to the state,
32194 * will populate $stateParams.
32195 * @param {object=} options Options object. The options are:
32197 * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
32198 * will not. If string, must be `"replace"`, which will update url and also replace last history record.
32199 * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url.
32200 * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'),
32201 * defines which state to be relative from.
32202 * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
32203 * - **`reload`** (v0.2.5) - {boolean=false|string=|object=}, If `true` will force transition even if the state or params
32204 * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
32205 * use this when you want to force a reload when *everything* is the same, including search params.
32206 * if String, then will reload the state with the name given in reload, and any children.
32207 * if Object, then a stateObj is expected, will reload the state found in stateObj, and any children.
32209 * @returns {promise} A promise representing the state of the new transition. See
32210 * {@link ui.router.state.$state#methods_go $state.go}.
32212 $state.transitionTo = function transitionTo(to, toParams, options) {
32213 toParams = toParams || {};
32215 location: true, inherit: false, relative: null, notify: true, reload: false, $retry: false
32218 var from = $state.$current, fromParams = $state.params, fromPath = from.path;
32219 var evt, toState = findState(to, options.relative);
32221 // Store the hash param for later (since it will be stripped out by various methods)
32222 var hash = toParams['#'];
32224 if (!isDefined(toState)) {
32225 var redirect = { to: to, toParams: toParams, options: options };
32226 var redirectResult = handleRedirect(redirect, from.self, fromParams, options);
32228 if (redirectResult) {
32229 return redirectResult;
32232 // Always retry once if the $stateNotFound was not prevented
32233 // (handles either redirect changed or state lazy-definition)
32235 toParams = redirect.toParams;
32236 options = redirect.options;
32237 toState = findState(to, options.relative);
32239 if (!isDefined(toState)) {
32240 if (!options.relative) throw new Error("No such state '" + to + "'");
32241 throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'");
32244 if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'");
32245 if (options.inherit) toParams = inheritParams($stateParams, toParams || {}, $state.$current, toState);
32246 if (!toState.params.$$validates(toParams)) return TransitionFailed;
32248 toParams = toState.params.$$values(toParams);
32251 var toPath = to.path;
32253 // Starting from the root of the path, keep all levels that haven't changed
32254 var keep = 0, state = toPath[keep], locals = root.locals, toLocals = [];
32256 if (!options.reload) {
32257 while (state && state === fromPath[keep] && state.ownParams.$$equals(toParams, fromParams)) {
32258 locals = toLocals[keep] = state.locals;
32260 state = toPath[keep];
32262 } else if (isString(options.reload) || isObject(options.reload)) {
32263 if (isObject(options.reload) && !options.reload.name) {
32264 throw new Error('Invalid reload state object');
32267 var reloadState = options.reload === true ? fromPath[0] : findState(options.reload);
32268 if (options.reload && !reloadState) {
32269 throw new Error("No such reload state '" + (isString(options.reload) ? options.reload : options.reload.name) + "'");
32272 while (state && state === fromPath[keep] && state !== reloadState) {
32273 locals = toLocals[keep] = state.locals;
32275 state = toPath[keep];
32279 // If we're going to the same state and all locals are kept, we've got nothing to do.
32280 // But clear 'transition', as we still want to cancel any other pending transitions.
32281 // TODO: We may not want to bump 'transition' if we're called from a location change
32282 // that we've initiated ourselves, because we might accidentally abort a legitimate
32283 // transition initiated from code?
32284 if (shouldSkipReload(to, toParams, from, fromParams, locals, options)) {
32285 if (hash) toParams['#'] = hash;
32286 $state.params = toParams;
32287 copy($state.params, $stateParams);
32288 if (options.location && to.navigable && to.navigable.url) {
32289 $urlRouter.push(to.navigable.url, toParams, {
32290 $$avoidResync: true, replace: options.location === 'replace'
32292 $urlRouter.update(true);
32294 $state.transition = null;
32295 return $q.when($state.current);
32298 // Filter parameters before we pass them to event handlers etc.
32299 toParams = filterByKeys(to.params.$$keys(), toParams || {});
32301 // Broadcast start event and cancel the transition if requested
32302 if (options.notify) {
32305 * @name ui.router.state.$state#$stateChangeStart
32306 * @eventOf ui.router.state.$state
32307 * @eventType broadcast on root scope
32309 * Fired when the state transition **begins**. You can use `event.preventDefault()`
32310 * to prevent the transition from happening and then the transition promise will be
32311 * rejected with a `'transition prevented'` value.
32313 * @param {Object} event Event object.
32314 * @param {State} toState The state being transitioned to.
32315 * @param {Object} toParams The params supplied to the `toState`.
32316 * @param {State} fromState The current state, pre-transition.
32317 * @param {Object} fromParams The params supplied to the `fromState`.
32322 * $rootScope.$on('$stateChangeStart',
32323 * function(event, toState, toParams, fromState, fromParams){
32324 * event.preventDefault();
32325 * // transitionTo() promise will be rejected with
32326 * // a 'transition prevented' error
32330 if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams).defaultPrevented) {
32331 $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams);
32332 $urlRouter.update();
32333 return TransitionPrevented;
32337 // Resolve locals for the remaining states, but don't update any global state just
32338 // yet -- if anything fails to resolve the current state needs to remain untouched.
32339 // We also set up an inheritance chain for the locals here. This allows the view directive
32340 // to quickly look up the correct definition for each view in the current state. Even
32341 // though we create the locals object itself outside resolveState(), it is initially
32342 // empty and gets filled asynchronously. We need to keep track of the promise for the
32343 // (fully resolved) current locals, and pass this down the chain.
32344 var resolved = $q.when(locals);
32346 for (var l = keep; l < toPath.length; l++, state = toPath[l]) {
32347 locals = toLocals[l] = inherit(locals);
32348 resolved = resolveState(state, toParams, state === to, resolved, locals, options);
32351 // Once everything is resolved, we are ready to perform the actual transition
32352 // and return a promise for the new state. We also keep track of what the
32353 // current promise is, so that we can detect overlapping transitions and
32354 // keep only the outcome of the last transition.
32355 var transition = $state.transition = resolved.then(function () {
32356 var l, entering, exiting;
32358 if ($state.transition !== transition) return TransitionSuperseded;
32360 // Exit 'from' states not kept
32361 for (l = fromPath.length - 1; l >= keep; l--) {
32362 exiting = fromPath[l];
32363 if (exiting.self.onExit) {
32364 $injector.invoke(exiting.self.onExit, exiting.self, exiting.locals.globals);
32366 exiting.locals = null;
32369 // Enter 'to' states not kept
32370 for (l = keep; l < toPath.length; l++) {
32371 entering = toPath[l];
32372 entering.locals = toLocals[l];
32373 if (entering.self.onEnter) {
32374 $injector.invoke(entering.self.onEnter, entering.self, entering.locals.globals);
32378 // Re-add the saved hash before we start returning things
32379 if (hash) toParams['#'] = hash;
32381 // Run it again, to catch any transitions in callbacks
32382 if ($state.transition !== transition) return TransitionSuperseded;
32384 // Update globals in $state
32385 $state.$current = to;
32386 $state.current = to.self;
32387 $state.params = toParams;
32388 copy($state.params, $stateParams);
32389 $state.transition = null;
32391 if (options.location && to.navigable) {
32392 $urlRouter.push(to.navigable.url, to.navigable.locals.globals.$stateParams, {
32393 $$avoidResync: true, replace: options.location === 'replace'
32397 if (options.notify) {
32400 * @name ui.router.state.$state#$stateChangeSuccess
32401 * @eventOf ui.router.state.$state
32402 * @eventType broadcast on root scope
32404 * Fired once the state transition is **complete**.
32406 * @param {Object} event Event object.
32407 * @param {State} toState The state being transitioned to.
32408 * @param {Object} toParams The params supplied to the `toState`.
32409 * @param {State} fromState The current state, pre-transition.
32410 * @param {Object} fromParams The params supplied to the `fromState`.
32412 $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams);
32414 $urlRouter.update(true);
32416 return $state.current;
32417 }, function (error) {
32418 if ($state.transition !== transition) return TransitionSuperseded;
32420 $state.transition = null;
32423 * @name ui.router.state.$state#$stateChangeError
32424 * @eventOf ui.router.state.$state
32425 * @eventType broadcast on root scope
32427 * Fired when an **error occurs** during transition. It's important to note that if you
32428 * have any errors in your resolve functions (javascript errors, non-existent services, etc)
32429 * they will not throw traditionally. You must listen for this $stateChangeError event to
32430 * catch **ALL** errors.
32432 * @param {Object} event Event object.
32433 * @param {State} toState The state being transitioned to.
32434 * @param {Object} toParams The params supplied to the `toState`.
32435 * @param {State} fromState The current state, pre-transition.
32436 * @param {Object} fromParams The params supplied to the `fromState`.
32437 * @param {Error} error The resolve error object.
32439 evt = $rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error);
32441 if (!evt.defaultPrevented) {
32442 $urlRouter.update();
32445 return $q.reject(error);
32453 * @name ui.router.state.$state#is
32454 * @methodOf ui.router.state.$state
32457 * Similar to {@link ui.router.state.$state#methods_includes $state.includes},
32458 * but only checks for the full state name. If params is supplied then it will be
32459 * tested for strict equality against the current active params object, so all params
32460 * must match with none missing and no extras.
32464 * $state.$current.name = 'contacts.details.item';
32467 * $state.is('contact.details.item'); // returns true
32468 * $state.is(contactDetailItemStateObject); // returns true
32470 * // relative name (. and ^), typically from a template
32471 * // E.g. from the 'contacts.details' template
32472 * <div ng-class="{highlighted: $state.is('.item')}">Item</div>
32475 * @param {string|object} stateOrName The state name (absolute or relative) or state object you'd like to check.
32476 * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like
32477 * to test against the current active state.
32478 * @param {object=} options An options object. The options are:
32480 * - **`relative`** - {string|object} - If `stateOrName` is a relative state name and `options.relative` is set, .is will
32481 * test relative to `options.relative` state (or name).
32483 * @returns {boolean} Returns true if it is the state.
32485 $state.is = function is(stateOrName, params, options) {
32486 options = extend({ relative: $state.$current }, options || {});
32487 var state = findState(stateOrName, options.relative);
32489 if (!isDefined(state)) { return undefined; }
32490 if ($state.$current !== state) { return false; }
32491 return params ? equalForKeys(state.params.$$values(params), $stateParams) : true;
32496 * @name ui.router.state.$state#includes
32497 * @methodOf ui.router.state.$state
32500 * A method to determine if the current active state is equal to or is the child of the
32501 * state stateName. If any params are passed then they will be tested for a match as well.
32502 * Not all the parameters need to be passed, just the ones you'd like to test for equality.
32505 * Partial and relative names
32507 * $state.$current.name = 'contacts.details.item';
32509 * // Using partial names
32510 * $state.includes("contacts"); // returns true
32511 * $state.includes("contacts.details"); // returns true
32512 * $state.includes("contacts.details.item"); // returns true
32513 * $state.includes("contacts.list"); // returns false
32514 * $state.includes("about"); // returns false
32516 * // Using relative names (. and ^), typically from a template
32517 * // E.g. from the 'contacts.details' template
32518 * <div ng-class="{highlighted: $state.includes('.item')}">Item</div>
32521 * Basic globbing patterns
32523 * $state.$current.name = 'contacts.details.item.url';
32525 * $state.includes("*.details.*.*"); // returns true
32526 * $state.includes("*.details.**"); // returns true
32527 * $state.includes("**.item.**"); // returns true
32528 * $state.includes("*.details.item.url"); // returns true
32529 * $state.includes("*.details.*.url"); // returns true
32530 * $state.includes("*.details.*"); // returns false
32531 * $state.includes("item.**"); // returns false
32534 * @param {string} stateOrName A partial name, relative name, or glob pattern
32535 * to be searched for within the current state name.
32536 * @param {object=} params A param object, e.g. `{sectionId: section.id}`,
32537 * that you'd like to test against the current active state.
32538 * @param {object=} options An options object. The options are:
32540 * - **`relative`** - {string|object=} - If `stateOrName` is a relative state reference and `options.relative` is set,
32541 * .includes will test relative to `options.relative` state (or name).
32543 * @returns {boolean} Returns true if it does include the state
32545 $state.includes = function includes(stateOrName, params, options) {
32546 options = extend({ relative: $state.$current }, options || {});
32547 if (isString(stateOrName) && isGlob(stateOrName)) {
32548 if (!doesStateMatchGlob(stateOrName)) {
32551 stateOrName = $state.$current.name;
32554 var state = findState(stateOrName, options.relative);
32555 if (!isDefined(state)) { return undefined; }
32556 if (!isDefined($state.$current.includes[state.name])) { return false; }
32557 return params ? equalForKeys(state.params.$$values(params), $stateParams, objectKeys(params)) : true;
32563 * @name ui.router.state.$state#href
32564 * @methodOf ui.router.state.$state
32567 * A url generation method that returns the compiled url for the given state populated with the given params.
32571 * expect($state.href("about.person", { person: "bob" })).toEqual("/about/bob");
32574 * @param {string|object} stateOrName The state name or state object you'd like to generate a url from.
32575 * @param {object=} params An object of parameter values to fill the state's required parameters.
32576 * @param {object=} options Options object. The options are:
32578 * - **`lossy`** - {boolean=true} - If true, and if there is no url associated with the state provided in the
32579 * first parameter, then the constructed href url will be built from the first navigable ancestor (aka
32580 * ancestor with a valid url).
32581 * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
32582 * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
32583 * defines which state to be relative from.
32584 * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
32586 * @returns {string} compiled state url
32588 $state.href = function href(stateOrName, params, options) {
32593 relative: $state.$current
32596 var state = findState(stateOrName, options.relative);
32598 if (!isDefined(state)) return null;
32599 if (options.inherit) params = inheritParams($stateParams, params || {}, $state.$current, state);
32601 var nav = (state && options.lossy) ? state.navigable : state;
32603 if (!nav || nav.url === undefined || nav.url === null) {
32606 return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys().concat('#'), params || {}), {
32607 absolute: options.absolute
32613 * @name ui.router.state.$state#get
32614 * @methodOf ui.router.state.$state
32617 * Returns the state configuration object for any specific state or all states.
32619 * @param {string|object=} stateOrName (absolute or relative) If provided, will only get the config for
32620 * the requested state. If not provided, returns an array of ALL state configs.
32621 * @param {string|object=} context When stateOrName is a relative state reference, the state will be retrieved relative to context.
32622 * @returns {Object|Array} State configuration object or array of all objects.
32624 $state.get = function (stateOrName, context) {
32625 if (arguments.length === 0) return map(objectKeys(states), function(name) { return states[name].self; });
32626 var state = findState(stateOrName, context || $state.$current);
32627 return (state && state.self) ? state.self : null;
32630 function resolveState(state, params, paramsAreFiltered, inherited, dst, options) {
32631 // Make a restricted $stateParams with only the parameters that apply to this state if
32632 // necessary. In addition to being available to the controller and onEnter/onExit callbacks,
32633 // we also need $stateParams to be available for any $injector calls we make during the
32634 // dependency resolution process.
32635 var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params);
32636 var locals = { $stateParams: $stateParams };
32638 // Resolve 'global' dependencies for the state, i.e. those not specific to a view.
32639 // We're also including $stateParams in this; that way the parameters are restricted
32640 // to the set that should be visible to the state, and are independent of when we update
32641 // the global $state and $stateParams values.
32642 dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state);
32643 var promises = [dst.resolve.then(function (globals) {
32644 dst.globals = globals;
32646 if (inherited) promises.push(inherited);
32648 function resolveViews() {
32649 var viewsPromises = [];
32651 // Resolve template and dependencies for all views.
32652 forEach(state.views, function (view, name) {
32653 var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {});
32654 injectables.$template = [ function () {
32655 return $view.load(name, { view: view, locals: dst.globals, params: $stateParams, notify: options.notify }) || '';
32658 viewsPromises.push($resolve.resolve(injectables, dst.globals, dst.resolve, state).then(function (result) {
32659 // References to the controller (only instantiated at link time)
32660 if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) {
32661 var injectLocals = angular.extend({}, injectables, dst.globals);
32662 result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals);
32664 result.$$controller = view.controller;
32666 // Provide access to the state itself for internal use
32667 result.$$state = state;
32668 result.$$controllerAs = view.controllerAs;
32669 dst[name] = result;
32673 return $q.all(viewsPromises).then(function(){
32674 return dst.globals;
32678 // Wait for all the promises and then return the activation object
32679 return $q.all(promises).then(resolveViews).then(function (values) {
32687 function shouldSkipReload(to, toParams, from, fromParams, locals, options) {
32688 // Return true if there are no differences in non-search (path/object) params, false if there are differences
32689 function nonSearchParamsEqual(fromAndToState, fromParams, toParams) {
32690 // Identify whether all the parameters that differ between `fromParams` and `toParams` were search params.
32691 function notSearchParam(key) {
32692 return fromAndToState.params[key].location != "search";
32694 var nonQueryParamKeys = fromAndToState.params.$$keys().filter(notSearchParam);
32695 var nonQueryParams = pick.apply({}, [fromAndToState.params].concat(nonQueryParamKeys));
32696 var nonQueryParamSet = new $$UMFP.ParamSet(nonQueryParams);
32697 return nonQueryParamSet.$$equals(fromParams, toParams);
32700 // If reload was not explicitly requested
32701 // and we're transitioning to the same state we're already in
32702 // and the locals didn't change
32703 // or they changed in a way that doesn't merit reloading
32704 // (reloadOnParams:false, or reloadOnSearch.false and only search params changed)
32705 // Then return true.
32706 if (!options.reload && to === from &&
32707 (locals === from.locals || (to.self.reloadOnSearch === false && nonSearchParamsEqual(from, fromParams, toParams)))) {
32713 angular.module('ui.router.state')
32714 .value('$stateParams', {})
32715 .provider('$state', $StateProvider);
32718 $ViewProvider.$inject = [];
32719 function $ViewProvider() {
32724 * @name ui.router.state.$view
32726 * @requires ui.router.util.$templateFactory
32727 * @requires $rootScope
32732 $get.$inject = ['$rootScope', '$templateFactory'];
32733 function $get( $rootScope, $templateFactory) {
32735 // $view.load('full.viewName', { template: ..., controller: ..., resolve: ..., async: false, params: ... })
32738 * @name ui.router.state.$view#load
32739 * @methodOf ui.router.state.$view
32743 * @param {string} name name
32744 * @param {object} options option object.
32746 load: function load(name, options) {
32747 var result, defaults = {
32748 template: null, controller: null, view: null, locals: null, notify: true, async: true, params: {}
32750 options = extend(defaults, options);
32752 if (options.view) {
32753 result = $templateFactory.fromConfig(options.view, options.params, options.locals);
32755 if (result && options.notify) {
32758 * @name ui.router.state.$state#$viewContentLoading
32759 * @eventOf ui.router.state.$view
32760 * @eventType broadcast on root scope
32763 * Fired once the view **begins loading**, *before* the DOM is rendered.
32765 * @param {Object} event Event object.
32766 * @param {Object} viewConfig The view config properties (template, controller, etc).
32771 * $scope.$on('$viewContentLoading',
32772 * function(event, viewConfig){
32773 * // Access to all the view config properties.
32774 * // and one special property 'targetView'
32775 * // viewConfig.targetView
32779 $rootScope.$broadcast('$viewContentLoading', options);
32787 angular.module('ui.router.state').provider('$view', $ViewProvider);
32791 * @name ui.router.state.$uiViewScrollProvider
32794 * Provider that returns the {@link ui.router.state.$uiViewScroll} service function.
32796 function $ViewScrollProvider() {
32798 var useAnchorScroll = false;
32802 * @name ui.router.state.$uiViewScrollProvider#useAnchorScroll
32803 * @methodOf ui.router.state.$uiViewScrollProvider
32806 * Reverts back to using the core [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) service for
32807 * scrolling based on the url anchor.
32809 this.useAnchorScroll = function () {
32810 useAnchorScroll = true;
32815 * @name ui.router.state.$uiViewScroll
32817 * @requires $anchorScroll
32818 * @requires $timeout
32821 * When called with a jqLite element, it scrolls the element into view (after a
32822 * `$timeout` so the DOM has time to refresh).
32824 * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor,
32825 * this can be enabled by calling {@link ui.router.state.$uiViewScrollProvider#methods_useAnchorScroll `$uiViewScrollProvider.useAnchorScroll()`}.
32827 this.$get = ['$anchorScroll', '$timeout', function ($anchorScroll, $timeout) {
32828 if (useAnchorScroll) {
32829 return $anchorScroll;
32832 return function ($element) {
32833 return $timeout(function () {
32834 $element[0].scrollIntoView();
32840 angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider);
32844 * @name ui.router.state.directive:ui-view
32846 * @requires ui.router.state.$state
32847 * @requires $compile
32848 * @requires $controller
32849 * @requires $injector
32850 * @requires ui.router.state.$uiViewScroll
32851 * @requires $document
32856 * The ui-view directive tells $state where to place your templates.
32858 * @param {string=} name A view name. The name should be unique amongst the other views in the
32859 * same state. You can have views of the same name that live in different states.
32861 * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window
32862 * when a view is populated. By default, $anchorScroll is overridden by ui-router's custom scroll
32863 * service, {@link ui.router.state.$uiViewScroll}. This custom service let's you
32864 * scroll ui-view elements into view when they are populated during a state activation.
32866 * *Note: To revert back to old [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll)
32867 * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.*
32869 * @param {string=} onload Expression to evaluate whenever the view updates.
32872 * A view can be unnamed or named.
32875 * <div ui-view></div>
32878 * <div ui-view="viewName"></div>
32881 * You can only have one unnamed view within any template (or root html). If you are only using a
32882 * single view and it is unnamed then you can populate it like so:
32884 * <div ui-view></div>
32885 * $stateProvider.state("home", {
32886 * template: "<h1>HELLO!</h1>"
32890 * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#views `views`}
32891 * config property, by name, in this case an empty name:
32893 * $stateProvider.state("home", {
32896 * template: "<h1>HELLO!</h1>"
32902 * But typically you'll only use the views property if you name your view or have more than one view
32903 * in the same template. There's not really a compelling reason to name a view if its the only one,
32904 * but you could if you wanted, like so:
32906 * <div ui-view="main"></div>
32909 * $stateProvider.state("home", {
32912 * template: "<h1>HELLO!</h1>"
32918 * Really though, you'll use views to set up multiple views:
32920 * <div ui-view></div>
32921 * <div ui-view="chart"></div>
32922 * <div ui-view="data"></div>
32926 * $stateProvider.state("home", {
32929 * template: "<h1>HELLO!</h1>"
32932 * template: "<chart_thing/>"
32935 * template: "<data_thing/>"
32941 * Examples for `autoscroll`:
32944 * <!-- If autoscroll present with no expression,
32945 * then scroll ui-view into view -->
32946 * <ui-view autoscroll/>
32948 * <!-- If autoscroll present with valid expression,
32949 * then scroll ui-view into view if expression evaluates to true -->
32950 * <ui-view autoscroll='true'/>
32951 * <ui-view autoscroll='false'/>
32952 * <ui-view autoscroll='scopeVariable'/>
32955 $ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll', '$interpolate'];
32956 function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate) {
32958 function getService() {
32959 return ($injector.has) ? function(service) {
32960 return $injector.has(service) ? $injector.get(service) : null;
32961 } : function(service) {
32963 return $injector.get(service);
32970 var service = getService(),
32971 $animator = service('$animator'),
32972 $animate = service('$animate');
32974 // Returns a set of DOM manipulation functions based on which Angular version
32976 function getRenderer(attrs, scope) {
32977 var statics = function() {
32979 enter: function (element, target, cb) { target.after(element); cb(); },
32980 leave: function (element, cb) { element.remove(); cb(); }
32986 enter: function(element, target, cb) {
32987 var promise = $animate.enter(element, null, target, cb);
32988 if (promise && promise.then) promise.then(cb);
32990 leave: function(element, cb) {
32991 var promise = $animate.leave(element, cb);
32992 if (promise && promise.then) promise.then(cb);
32998 var animate = $animator && $animator(scope, attrs);
33001 enter: function(element, target, cb) {animate.enter(element, null, target); cb(); },
33002 leave: function(element, cb) { animate.leave(element); cb(); }
33013 transclude: 'element',
33014 compile: function (tElement, tAttrs, $transclude) {
33015 return function (scope, $element, attrs) {
33016 var previousEl, currentEl, currentScope, latestLocals,
33017 onloadExp = attrs.onload || '',
33018 autoScrollExp = attrs.autoscroll,
33019 renderer = getRenderer(attrs, scope);
33021 scope.$on('$stateChangeSuccess', function() {
33024 scope.$on('$viewContentLoading', function() {
33030 function cleanupLastView() {
33032 previousEl.remove();
33036 if (currentScope) {
33037 currentScope.$destroy();
33038 currentScope = null;
33042 renderer.leave(currentEl, function() {
33046 previousEl = currentEl;
33051 function updateView(firstTime) {
33053 name = getUiViewName(scope, attrs, $element, $interpolate),
33054 previousLocals = name && $state.$current && $state.$current.locals[name];
33056 if (!firstTime && previousLocals === latestLocals) return; // nothing to do
33057 newScope = scope.$new();
33058 latestLocals = $state.$current.locals[name];
33060 var clone = $transclude(newScope, function(clone) {
33061 renderer.enter(clone, $element, function onUiViewEnter() {
33063 currentScope.$emit('$viewContentAnimationEnded');
33066 if (angular.isDefined(autoScrollExp) && !autoScrollExp || scope.$eval(autoScrollExp)) {
33067 $uiViewScroll(clone);
33074 currentScope = newScope;
33077 * @name ui.router.state.directive:ui-view#$viewContentLoaded
33078 * @eventOf ui.router.state.directive:ui-view
33079 * @eventType emits on ui-view directive scope
33081 * Fired once the view is **loaded**, *after* the DOM is rendered.
33083 * @param {Object} event Event object.
33085 currentScope.$emit('$viewContentLoaded');
33086 currentScope.$eval(onloadExp);
33095 $ViewDirectiveFill.$inject = ['$compile', '$controller', '$state', '$interpolate'];
33096 function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate) {
33100 compile: function (tElement) {
33101 var initial = tElement.html();
33102 return function (scope, $element, attrs) {
33103 var current = $state.$current,
33104 name = getUiViewName(scope, attrs, $element, $interpolate),
33105 locals = current && current.locals[name];
33111 $element.data('$uiView', { name: name, state: locals.$$state });
33112 $element.html(locals.$template ? locals.$template : initial);
33114 var link = $compile($element.contents());
33116 if (locals.$$controller) {
33117 locals.$scope = scope;
33118 locals.$element = $element;
33119 var controller = $controller(locals.$$controller, locals);
33120 if (locals.$$controllerAs) {
33121 scope[locals.$$controllerAs] = controller;
33123 $element.data('$ngControllerController', controller);
33124 $element.children().data('$ngControllerController', controller);
33134 * Shared ui-view code for both directives:
33135 * Given scope, element, and its attributes, return the view's name
33137 function getUiViewName(scope, attrs, element, $interpolate) {
33138 var name = $interpolate(attrs.uiView || attrs.name || '')(scope);
33139 var inherited = element.inheritedData('$uiView');
33140 return name.indexOf('@') >= 0 ? name : (name + '@' + (inherited ? inherited.state.name : ''));
33143 angular.module('ui.router.state').directive('uiView', $ViewDirective);
33144 angular.module('ui.router.state').directive('uiView', $ViewDirectiveFill);
33146 function parseStateRef(ref, current) {
33147 var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed;
33148 if (preparsed) ref = current + '(' + preparsed[1] + ')';
33149 parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
33150 if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'");
33151 return { state: parsed[1], paramExpr: parsed[3] || null };
33154 function stateContext(el) {
33155 var stateData = el.parent().inheritedData('$uiView');
33157 if (stateData && stateData.state && stateData.state.name) {
33158 return stateData.state;
33164 * @name ui.router.state.directive:ui-sref
33166 * @requires ui.router.state.$state
33167 * @requires $timeout
33172 * A directive that binds a link (`<a>` tag) to a state. If the state has an associated
33173 * URL, the directive will automatically generate & update the `href` attribute via
33174 * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking
33175 * the link will trigger a state transition with optional parameters.
33177 * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
33178 * handled natively by the browser.
33180 * You can also use relative state paths within ui-sref, just like the relative
33181 * paths passed to `$state.go()`. You just need to be aware that the path is relative
33182 * to the state that the link lives in, in other words the state that loaded the
33183 * template containing the link.
33185 * You can specify options to pass to {@link ui.router.state.$state#go $state.go()}
33186 * using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`,
33190 * Here's an example of how you'd use ui-sref and how it would compile. If you have the
33191 * following template:
33193 * <a ui-sref="home">Home</a> | <a ui-sref="about">About</a> | <a ui-sref="{page: 2}">Next page</a>
33196 * <li ng-repeat="contact in contacts">
33197 * <a ui-sref="contacts.detail({ id: contact.id })">{{ contact.name }}</a>
33202 * Then the compiled html would be (assuming Html5Mode is off and current state is contacts):
33204 * <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>
33207 * <li ng-repeat="contact in contacts">
33208 * <a href="#/contacts/1" ui-sref="contacts.detail({ id: contact.id })">Joe</a>
33210 * <li ng-repeat="contact in contacts">
33211 * <a href="#/contacts/2" ui-sref="contacts.detail({ id: contact.id })">Alice</a>
33213 * <li ng-repeat="contact in contacts">
33214 * <a href="#/contacts/3" ui-sref="contacts.detail({ id: contact.id })">Bob</a>
33218 * <a ui-sref="home" ui-sref-opts="{reload: true}">Home</a>
33221 * @param {string} ui-sref 'stateName' can be any valid absolute or relative state
33222 * @param {Object} ui-sref-opts options to pass to {@link ui.router.state.$state#go $state.go()}
33224 $StateRefDirective.$inject = ['$state', '$timeout'];
33225 function $StateRefDirective($state, $timeout) {
33226 var allowedOptions = ['location', 'inherit', 'reload', 'absolute'];
33230 require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
33231 link: function(scope, element, attrs, uiSrefActive) {
33232 var ref = parseStateRef(attrs.uiSref, $state.current.name);
33233 var params = null, url = null, base = stateContext(element) || $state.$current;
33234 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
33235 var hrefKind = Object.prototype.toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
33236 'xlink:href' : 'href';
33237 var newHref = null, isAnchor = element.prop("tagName").toUpperCase() === "A";
33238 var isForm = element[0].nodeName === "FORM";
33239 var attr = isForm ? "action" : hrefKind, nav = true;
33241 var options = { relative: base, inherit: true };
33242 var optionsOverride = scope.$eval(attrs.uiSrefOpts) || {};
33244 angular.forEach(allowedOptions, function(option) {
33245 if (option in optionsOverride) {
33246 options[option] = optionsOverride[option];
33250 var update = function(newVal) {
33251 if (newVal) params = angular.copy(newVal);
33254 newHref = $state.href(ref.state, params, options);
33256 var activeDirective = uiSrefActive[1] || uiSrefActive[0];
33257 if (activeDirective) {
33258 activeDirective.$$addStateInfo(ref.state, params);
33260 if (newHref === null) {
33264 attrs.$set(attr, newHref);
33267 if (ref.paramExpr) {
33268 scope.$watch(ref.paramExpr, function(newVal, oldVal) {
33269 if (newVal !== params) update(newVal);
33271 params = angular.copy(scope.$eval(ref.paramExpr));
33275 if (isForm) return;
33277 element.bind("click", function(e) {
33278 var button = e.which || e.button;
33279 if ( !(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || element.attr('target')) ) {
33280 // HACK: This is to allow ng-clicks to be processed before the transition is initiated:
33281 var transition = $timeout(function() {
33282 $state.go(ref.state, params, options);
33284 e.preventDefault();
33286 // if the state has no URL, ignore one preventDefault from the <a> directive.
33287 var ignorePreventDefaultCount = isAnchor && !newHref ? 1: 0;
33288 e.preventDefault = function() {
33289 if (ignorePreventDefaultCount-- <= 0)
33290 $timeout.cancel(transition);
33300 * @name ui.router.state.directive:ui-sref-active
33302 * @requires ui.router.state.$state
33303 * @requires ui.router.state.$stateParams
33304 * @requires $interpolate
33309 * A directive working alongside ui-sref to add classes to an element when the
33310 * related ui-sref directive's state is active, and removing them when it is inactive.
33311 * The primary use-case is to simplify the special appearance of navigation menus
33312 * relying on `ui-sref`, by having the "active" state's menu button appear different,
33313 * distinguishing it from the inactive menu items.
33315 * ui-sref-active can live on the same element as ui-sref or on a parent element. The first
33316 * ui-sref-active found at the same level or above the ui-sref will be used.
33318 * Will activate when the ui-sref's target state or any child state is active. If you
33319 * need to activate only when the ui-sref target state is active and *not* any of
33320 * it's children, then you will use
33321 * {@link ui.router.state.directive:ui-sref-active-eq ui-sref-active-eq}
33324 * Given the following template:
33327 * <li ui-sref-active="active" class="item">
33328 * <a href ui-sref="app.user({user: 'bilbobaggins'})">@bilbobaggins</a>
33334 * When the app state is "app.user" (or any children states), and contains the state parameter "user" with value "bilbobaggins",
33335 * the resulting HTML will appear as (note the 'active' class):
33338 * <li ui-sref-active="active" class="item active">
33339 * <a ui-sref="app.user({user: 'bilbobaggins'})" href="/users/bilbobaggins">@bilbobaggins</a>
33344 * The class name is interpolated **once** during the directives link time (any further changes to the
33345 * interpolated value are ignored).
33347 * Multiple classes may be specified in a space-separated format:
33350 * <li ui-sref-active='class1 class2 class3'>
33351 * <a ui-sref="app.user">link</a>
33359 * @name ui.router.state.directive:ui-sref-active-eq
33361 * @requires ui.router.state.$state
33362 * @requires ui.router.state.$stateParams
33363 * @requires $interpolate
33368 * The same as {@link ui.router.state.directive:ui-sref-active ui-sref-active} but will only activate
33369 * when the exact target state used in the `ui-sref` is active; no child states.
33372 $StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
33373 function $StateRefActiveDirective($state, $stateParams, $interpolate) {
33376 controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
33377 var states = [], activeClass;
33379 // There probably isn't much point in $observing this
33380 // uiSrefActive and uiSrefActiveEq share the same directive object with some
33381 // slight difference in logic routing
33382 activeClass = $interpolate($attrs.uiSrefActiveEq || $attrs.uiSrefActive || '', false)($scope);
33384 // Allow uiSref to communicate with uiSrefActive[Equals]
33385 this.$$addStateInfo = function (newState, newParams) {
33386 var state = $state.get(newState, stateContext($element));
33389 state: state || { name: newState },
33396 $scope.$on('$stateChangeSuccess', update);
33398 // Update route state
33399 function update() {
33401 $element.addClass(activeClass);
33403 $element.removeClass(activeClass);
33407 function anyMatch() {
33408 for (var i = 0; i < states.length; i++) {
33409 if (isMatch(states[i].state, states[i].params)) {
33416 function isMatch(state, params) {
33417 if (typeof $attrs.uiSrefActiveEq !== 'undefined') {
33418 return $state.is(state.name, params);
33420 return $state.includes(state.name, params);
33427 angular.module('ui.router.state')
33428 .directive('uiSref', $StateRefDirective)
33429 .directive('uiSrefActive', $StateRefActiveDirective)
33430 .directive('uiSrefActiveEq', $StateRefActiveDirective);
33434 * @name ui.router.state.filter:isState
33436 * @requires ui.router.state.$state
33439 * Translates to {@link ui.router.state.$state#methods_is $state.is("stateName")}.
33441 $IsStateFilter.$inject = ['$state'];
33442 function $IsStateFilter($state) {
33443 var isFilter = function (state) {
33444 return $state.is(state);
33446 isFilter.$stateful = true;
33452 * @name ui.router.state.filter:includedByState
33454 * @requires ui.router.state.$state
33457 * Translates to {@link ui.router.state.$state#methods_includes $state.includes('fullOrPartialStateName')}.
33459 $IncludedByStateFilter.$inject = ['$state'];
33460 function $IncludedByStateFilter($state) {
33461 var includesFilter = function (state) {
33462 return $state.includes(state);
33464 includesFilter.$stateful = true;
33465 return includesFilter;
33468 angular.module('ui.router.state')
33469 .filter('isState', $IsStateFilter)
33470 .filter('includedByState', $IncludedByStateFilter);
33471 })(window, window.angular);
33475 /***/ function(module, exports, __webpack_require__) {
33477 __webpack_require__(5);
33478 module.exports = 'ngResource';
33483 /***/ function(module, exports) {
33486 * @license AngularJS v1.4.8
33487 * (c) 2010-2015 Google, Inc. http://angularjs.org
33490 (function(window, angular, undefined) {'use strict';
33492 var $resourceMinErr = angular.$$minErr('$resource');
33494 // Helper functions and regex to lookup a dotted path on an object
33495 // stopping at undefined/null. The path must be composed of ASCII
33496 // identifiers (just like $parse)
33497 var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/;
33499 function isValidDottedPath(path) {
33500 return (path != null && path !== '' && path !== 'hasOwnProperty' &&
33501 MEMBER_NAME_REGEX.test('.' + path));
33504 function lookupDottedPath(obj, path) {
33505 if (!isValidDottedPath(path)) {
33506 throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path);
33508 var keys = path.split('.');
33509 for (var i = 0, ii = keys.length; i < ii && angular.isDefined(obj); i++) {
33511 obj = (obj !== null) ? obj[key] : undefined;
33517 * Create a shallow copy of an object and clear other fields from the destination
33519 function shallowClearAndCopy(src, dst) {
33522 angular.forEach(dst, function(value, key) {
33526 for (var key in src) {
33527 if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
33528 dst[key] = src[key];
33542 * The `ngResource` module provides interaction support with RESTful services
33543 * via the $resource service.
33546 * <div doc-module-components="ngResource"></div>
33548 * See {@link ngResource.$resource `$resource`} for usage.
33557 * A factory which creates a resource object that lets you interact with
33558 * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
33560 * The returned resource object has action methods which provide high-level behaviors without
33561 * the need to interact with the low level {@link ng.$http $http} service.
33563 * Requires the {@link ngResource `ngResource`} module to be installed.
33565 * By default, trailing slashes will be stripped from the calculated URLs,
33566 * which can pose problems with server backends that do not expect that
33567 * behavior. This can be disabled by configuring the `$resourceProvider` like
33571 app.config(['$resourceProvider', function($resourceProvider) {
33572 // Don't strip trailing slashes from calculated URLs
33573 $resourceProvider.defaults.stripTrailingSlashes = false;
33577 * @param {string} url A parameterized URL template with parameters prefixed by `:` as in
33578 * `/user/:username`. If you are using a URL with a port number (e.g.
33579 * `http://example.com:8080/api`), it will be respected.
33581 * If you are using a url with a suffix, just add the suffix, like this:
33582 * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')`
33583 * or even `$resource('http://example.com/resource/:resource_id.:format')`
33584 * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be
33585 * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you
33586 * can escape it with `/\.`.
33588 * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
33589 * `actions` methods. If any of the parameter value is a function, it will be executed every time
33590 * when a param value needs to be obtained for a request (unless the param was overridden).
33592 * Each key value in the parameter object is first bound to url template if present and then any
33593 * excess keys are appended to the url search query after the `?`.
33595 * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
33596 * URL `/path/greet?salutation=Hello`.
33598 * If the parameter value is prefixed with `@` then the value for that parameter will be extracted
33599 * from the corresponding property on the `data` object (provided when calling an action method). For
33600 * example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of `someParam`
33601 * will be `data.someProp`.
33603 * @param {Object.<Object>=} actions Hash with declaration of custom actions that should extend
33604 * the default set of resource actions. The declaration should be created in the format of {@link
33605 * ng.$http#usage $http.config}:
33607 * {action1: {method:?, params:?, isArray:?, headers:?, ...},
33608 * action2: {method:?, params:?, isArray:?, headers:?, ...},
33613 * - **`action`** – {string} – The name of action. This name becomes the name of the method on
33614 * your resource object.
33615 * - **`method`** – {string} – Case insensitive HTTP method (e.g. `GET`, `POST`, `PUT`,
33616 * `DELETE`, `JSONP`, etc).
33617 * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of
33618 * the parameter value is a function, it will be executed every time when a param value needs to
33619 * be obtained for a request (unless the param was overridden).
33620 * - **`url`** – {string} – action specific `url` override. The url templating is supported just
33621 * like for the resource-level urls.
33622 * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array,
33623 * see `returns` section.
33624 * - **`transformRequest`** –
33625 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
33626 * transform function or an array of such functions. The transform function takes the http
33627 * request body and headers and returns its transformed (typically serialized) version.
33628 * By default, transformRequest will contain one function that checks if the request data is
33629 * an object and serializes to using `angular.toJson`. To prevent this behavior, set
33630 * `transformRequest` to an empty array: `transformRequest: []`
33631 * - **`transformResponse`** –
33632 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
33633 * transform function or an array of such functions. The transform function takes the http
33634 * response body and headers and returns its transformed (typically deserialized) version.
33635 * By default, transformResponse will contain one function that checks if the response looks like
33636 * a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior, set
33637 * `transformResponse` to an empty array: `transformResponse: []`
33638 * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
33639 * GET request, otherwise if a cache instance built with
33640 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
33642 * - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that
33643 * should abort the request when resolved.
33644 * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the
33646 * [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5)
33647 * for more information.
33648 * - **`responseType`** - `{string}` - see
33649 * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType).
33650 * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
33651 * `response` and `responseError`. Both `response` and `responseError` interceptors get called
33652 * with `http response` object. See {@link ng.$http $http interceptors}.
33654 * @param {Object} options Hash with custom settings that should extend the
33655 * default `$resourceProvider` behavior. The only supported option is
33659 * - **`stripTrailingSlashes`** – {boolean} – If true then the trailing
33660 * slashes from any calculated URL will be stripped. (Defaults to true.)
33662 * @returns {Object} A resource "class" object with methods for the default set of resource actions
33663 * optionally extended with custom `actions`. The default set contains these actions:
33665 * { 'get': {method:'GET'},
33666 * 'save': {method:'POST'},
33667 * 'query': {method:'GET', isArray:true},
33668 * 'remove': {method:'DELETE'},
33669 * 'delete': {method:'DELETE'} };
33672 * Calling these methods invoke an {@link ng.$http} with the specified http method,
33673 * destination and parameters. When the data is returned from the server then the object is an
33674 * instance of the resource class. The actions `save`, `remove` and `delete` are available on it
33675 * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
33676 * read, update, delete) on server-side data like this:
33678 * var User = $resource('/user/:userId', {userId:'@id'});
33679 * var user = User.get({userId:123}, function() {
33685 * It is important to realize that invoking a $resource object method immediately returns an
33686 * empty reference (object or array depending on `isArray`). Once the data is returned from the
33687 * server the existing reference is populated with the actual data. This is a useful trick since
33688 * usually the resource is assigned to a model which is then rendered by the view. Having an empty
33689 * object results in no rendering, once the data arrives from the server then the object is
33690 * populated with the data and the view automatically re-renders itself showing the new data. This
33691 * means that in most cases one never has to write a callback function for the action methods.
33693 * The action methods on the class object or instance object can be invoked with the following
33696 * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
33697 * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
33698 * - non-GET instance actions: `instance.$action([parameters], [success], [error])`
33701 * Success callback is called with (value, responseHeaders) arguments, where the value is
33702 * the populated resource instance or collection object. The error callback is called
33703 * with (httpResponse) argument.
33705 * Class actions return empty instance (with additional properties below).
33706 * Instance actions return promise of the action.
33708 * The Resource instances and collection have these additional properties:
33710 * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this
33711 * instance or collection.
33713 * On success, the promise is resolved with the same resource instance or collection object,
33714 * updated with data from server. This makes it easy to use in
33715 * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view
33716 * rendering until the resource(s) are loaded.
33718 * On failure, the promise is resolved with the {@link ng.$http http response} object, without
33719 * the `resource` property.
33721 * If an interceptor object was provided, the promise will instead be resolved with the value
33722 * returned by the interceptor.
33724 * - `$resolved`: `true` after first server interaction is completed (either with success or
33725 * rejection), `false` before that. Knowing if the Resource has been resolved is useful in
33730 * # Credit card resource
33733 // Define CreditCard class
33734 var CreditCard = $resource('/user/:userId/card/:cardId',
33735 {userId:123, cardId:'@id'}, {
33736 charge: {method:'POST', params:{charge:true}}
33739 // We can retrieve a collection from the server
33740 var cards = CreditCard.query(function() {
33741 // GET: /user/123/card
33742 // server returns: [ {id:456, number:'1234', name:'Smith'} ];
33744 var card = cards[0];
33745 // each item is an instance of CreditCard
33746 expect(card instanceof CreditCard).toEqual(true);
33747 card.name = "J. Smith";
33748 // non GET methods are mapped onto the instances
33750 // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
33751 // server returns: {id:456, number:'1234', name: 'J. Smith'};
33753 // our custom method is mapped as well.
33754 card.$charge({amount:9.99});
33755 // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
33758 // we can create an instance as well
33759 var newCard = new CreditCard({number:'0123'});
33760 newCard.name = "Mike Smith";
33762 // POST: /user/123/card {number:'0123', name:'Mike Smith'}
33763 // server returns: {id:789, number:'0123', name: 'Mike Smith'};
33764 expect(newCard.id).toEqual(789);
33767 * The object returned from this function execution is a resource "class" which has "static" method
33768 * for each action in the definition.
33770 * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and
33772 * When the data is returned from the server then the object is an instance of the resource type and
33773 * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
33774 * operations (create, read, update, delete) on server-side data.
33777 var User = $resource('/user/:userId', {userId:'@id'});
33778 User.get({userId:123}, function(user) {
33784 * It's worth noting that the success callback for `get`, `query` and other methods gets passed
33785 * in the response that came from the server as well as $http header getter function, so one
33786 * could rewrite the above example and get access to http headers as:
33789 var User = $resource('/user/:userId', {userId:'@id'});
33790 User.get({userId:123}, function(u, getResponseHeaders){
33792 u.$save(function(u, putResponseHeaders) {
33793 //u => saved user object
33794 //putResponseHeaders => $http header getter
33799 * You can also access the raw `$http` promise via the `$promise` property on the object returned
33802 var User = $resource('/user/:userId', {userId:'@id'});
33803 User.get({userId:123})
33804 .$promise.then(function(user) {
33805 $scope.user = user;
33809 * # Creating a custom 'PUT' request
33810 * In this example we create a custom method on our resource to make a PUT request
33812 * var app = angular.module('app', ['ngResource', 'ngRoute']);
33814 * // Some APIs expect a PUT request in the format URL/object/ID
33815 * // Here we are creating an 'update' method
33816 * app.factory('Notes', ['$resource', function($resource) {
33817 * return $resource('/notes/:id', null,
33819 * 'update': { method:'PUT' }
33823 * // In our controller we get the ID from the URL using ngRoute and $routeParams
33824 * // We pass in $routeParams and our Notes factory along with $scope
33825 * app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes',
33826 function($scope, $routeParams, Notes) {
33827 * // First get a note object from the factory
33828 * var note = Notes.get({ id:$routeParams.id });
33831 * // Now call update passing in the ID first then the object you are updating
33832 * Notes.update({ id:$id }, note);
33834 * // This will PUT /notes/ID with the note object in the request payload
33838 angular.module('ngResource', ['ng']).
33839 provider('$resource', function() {
33840 var PROTOCOL_AND_DOMAIN_REGEX = /^https?:\/\/[^\/]*/;
33841 var provider = this;
33844 // Strip slashes by default
33845 stripTrailingSlashes: true,
33847 // Default actions configuration
33849 'get': {method: 'GET'},
33850 'save': {method: 'POST'},
33851 'query': {method: 'GET', isArray: true},
33852 'remove': {method: 'DELETE'},
33853 'delete': {method: 'DELETE'}
33857 this.$get = ['$http', '$q', function($http, $q) {
33859 var noop = angular.noop,
33860 forEach = angular.forEach,
33861 extend = angular.extend,
33862 copy = angular.copy,
33863 isFunction = angular.isFunction;
33866 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
33867 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set
33868 * (pchar) allowed in path segments:
33870 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
33871 * pct-encoded = "%" HEXDIG HEXDIG
33872 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
33873 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
33874 * / "*" / "+" / "," / ";" / "="
33876 function encodeUriSegment(val) {
33877 return encodeUriQuery(val, true).
33878 replace(/%26/gi, '&').
33879 replace(/%3D/gi, '=').
33880 replace(/%2B/gi, '+');
33885 * This method is intended for encoding *key* or *value* parts of query component. We need a
33886 * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't
33887 * have to be encoded per http://tools.ietf.org/html/rfc3986:
33888 * query = *( pchar / "/" / "?" )
33889 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
33890 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
33891 * pct-encoded = "%" HEXDIG HEXDIG
33892 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
33893 * / "*" / "+" / "," / ";" / "="
33895 function encodeUriQuery(val, pctEncodeSpaces) {
33896 return encodeURIComponent(val).
33897 replace(/%40/gi, '@').
33898 replace(/%3A/gi, ':').
33899 replace(/%24/g, '$').
33900 replace(/%2C/gi, ',').
33901 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
33904 function Route(template, defaults) {
33905 this.template = template;
33906 this.defaults = extend({}, provider.defaults, defaults);
33907 this.urlParams = {};
33910 Route.prototype = {
33911 setUrlParams: function(config, params, actionUrl) {
33913 url = actionUrl || self.template,
33916 protocolAndDomain = '';
33918 var urlParams = self.urlParams = {};
33919 forEach(url.split(/\W/), function(param) {
33920 if (param === 'hasOwnProperty') {
33921 throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name.");
33923 if (!(new RegExp("^\\d+$").test(param)) && param &&
33924 (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) {
33925 urlParams[param] = true;
33928 url = url.replace(/\\:/g, ':');
33929 url = url.replace(PROTOCOL_AND_DOMAIN_REGEX, function(match) {
33930 protocolAndDomain = match;
33934 params = params || {};
33935 forEach(self.urlParams, function(_, urlParam) {
33936 val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
33937 if (angular.isDefined(val) && val !== null) {
33938 encodedVal = encodeUriSegment(val);
33939 url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), function(match, p1) {
33940 return encodedVal + p1;
33943 url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match,
33944 leadingSlashes, tail) {
33945 if (tail.charAt(0) == '/') {
33948 return leadingSlashes + tail;
33954 // strip trailing slashes and set the url (unless this behavior is specifically disabled)
33955 if (self.defaults.stripTrailingSlashes) {
33956 url = url.replace(/\/+$/, '') || '/';
33959 // then replace collapse `/.` if found in the last URL path segment before the query
33960 // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x`
33961 url = url.replace(/\/\.(?=\w+($|\?))/, '.');
33962 // replace escaped `/\.` with `/.`
33963 config.url = protocolAndDomain + url.replace(/\/\\\./, '/.');
33966 // set params - delegate param encoding to $http
33967 forEach(params, function(value, key) {
33968 if (!self.urlParams[key]) {
33969 config.params = config.params || {};
33970 config.params[key] = value;
33977 function resourceFactory(url, paramDefaults, actions, options) {
33978 var route = new Route(url, options);
33980 actions = extend({}, provider.defaults.actions, actions);
33982 function extractParams(data, actionParams) {
33984 actionParams = extend({}, paramDefaults, actionParams);
33985 forEach(actionParams, function(value, key) {
33986 if (isFunction(value)) { value = value(); }
33987 ids[key] = value && value.charAt && value.charAt(0) == '@' ?
33988 lookupDottedPath(data, value.substr(1)) : value;
33993 function defaultResponseInterceptor(response) {
33994 return response.resource;
33997 function Resource(value) {
33998 shallowClearAndCopy(value || {}, this);
34001 Resource.prototype.toJSON = function() {
34002 var data = extend({}, this);
34003 delete data.$promise;
34004 delete data.$resolved;
34008 forEach(actions, function(action, name) {
34009 var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method);
34011 Resource[name] = function(a1, a2, a3, a4) {
34012 var params = {}, data, success, error;
34014 /* jshint -W086 */ /* (purposefully fall through case statements) */
34015 switch (arguments.length) {
34022 if (isFunction(a2)) {
34023 if (isFunction(a1)) {
34039 if (isFunction(a1)) success = a1;
34040 else if (hasBody) data = a1;
34045 throw $resourceMinErr('badargs',
34046 "Expected up to 4 arguments [params, data, success, error], got {0} arguments",
34049 /* jshint +W086 */ /* (purposefully fall through case statements) */
34051 var isInstanceCall = this instanceof Resource;
34052 var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data));
34053 var httpConfig = {};
34054 var responseInterceptor = action.interceptor && action.interceptor.response ||
34055 defaultResponseInterceptor;
34056 var responseErrorInterceptor = action.interceptor && action.interceptor.responseError ||
34059 forEach(action, function(value, key) {
34062 httpConfig[key] = copy(value);
34066 case 'interceptor':
34069 httpConfig[key] = value;
34074 if (hasBody) httpConfig.data = data;
34075 route.setUrlParams(httpConfig,
34076 extend({}, extractParams(data, action.params || {}), params),
34079 var promise = $http(httpConfig).then(function(response) {
34080 var data = response.data,
34081 promise = value.$promise;
34084 // Need to convert action.isArray to boolean in case it is undefined
34086 if (angular.isArray(data) !== (!!action.isArray)) {
34087 throw $resourceMinErr('badcfg',
34088 'Error in resource configuration for action `{0}`. Expected response to ' +
34089 'contain an {1} but got an {2} (Request: {3} {4})', name, action.isArray ? 'array' : 'object',
34090 angular.isArray(data) ? 'array' : 'object', httpConfig.method, httpConfig.url);
34093 if (action.isArray) {
34095 forEach(data, function(item) {
34096 if (typeof item === "object") {
34097 value.push(new Resource(item));
34099 // Valid JSON values may be string literals, and these should not be converted
34100 // into objects. These items will not have access to the Resource prototype
34101 // methods, but unfortunately there
34106 shallowClearAndCopy(data, value);
34107 value.$promise = promise;
34111 value.$resolved = true;
34113 response.resource = value;
34116 }, function(response) {
34117 value.$resolved = true;
34119 (error || noop)(response);
34121 return $q.reject(response);
34124 promise = promise.then(
34125 function(response) {
34126 var value = responseInterceptor(response);
34127 (success || noop)(value, response.headers);
34130 responseErrorInterceptor);
34132 if (!isInstanceCall) {
34133 // we are creating instance / collection
34134 // - set the initial promise
34135 // - return the instance / collection
34136 value.$promise = promise;
34137 value.$resolved = false;
34147 Resource.prototype['$' + name] = function(params, success, error) {
34148 if (isFunction(params)) {
34149 error = success; success = params; params = {};
34151 var result = Resource[name].call(this, params, this, success, error);
34152 return result.$promise || result;
34156 Resource.bind = function(additionalParamDefaults) {
34157 return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
34163 return resourceFactory;
34168 })(window, window.angular);
34173 /***/ function(module, exports, __webpack_require__) {
34175 __webpack_require__(7);
34177 module.exports = 'ui.bootstrap';
34182 /***/ function(module, exports) {
34185 * angular-ui-bootstrap
34186 * http://angular-ui.github.io/bootstrap/
34188 * Version: 1.0.0 - 2016-01-08
34191 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"]);
34192 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"]);
34193 angular.module('ui.bootstrap.collapse', [])
34195 .directive('uibCollapse', ['$animate', '$injector', function($animate, $injector) {
34196 var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null;
34198 link: function(scope, element, attrs) {
34199 if (!scope.$eval(attrs.uibCollapse)) {
34200 element.addClass('in')
34201 .addClass('collapse')
34202 .css({height: 'auto'});
34205 function expand() {
34206 element.removeClass('collapse')
34207 .addClass('collapsing')
34208 .attr('aria-expanded', true)
34209 .attr('aria-hidden', false);
34212 $animateCss(element, {
34215 to: { height: element[0].scrollHeight + 'px' }
34216 }).start()['finally'](expandDone);
34218 $animate.addClass(element, 'in', {
34219 to: { height: element[0].scrollHeight + 'px' }
34220 }).then(expandDone);
34224 function expandDone() {
34225 element.removeClass('collapsing')
34226 .addClass('collapse')
34227 .css({height: 'auto'});
34230 function collapse() {
34231 if (!element.hasClass('collapse') && !element.hasClass('in')) {
34232 return collapseDone();
34236 // IMPORTANT: The height must be set before adding "collapsing" class.
34237 // Otherwise, the browser attempts to animate from height 0 (in
34238 // collapsing class) to the given height here.
34239 .css({height: element[0].scrollHeight + 'px'})
34240 // initially all panel collapse have the collapse class, this removal
34241 // prevents the animation from jumping to collapsed state
34242 .removeClass('collapse')
34243 .addClass('collapsing')
34244 .attr('aria-expanded', false)
34245 .attr('aria-hidden', true);
34248 $animateCss(element, {
34251 }).start()['finally'](collapseDone);
34253 $animate.removeClass(element, 'in', {
34255 }).then(collapseDone);
34259 function collapseDone() {
34260 element.css({height: '0'}); // Required so that collapse works when animation is disabled
34261 element.removeClass('collapsing')
34262 .addClass('collapse');
34265 scope.$watch(attrs.uibCollapse, function(shouldCollapse) {
34266 if (shouldCollapse) {
34276 angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
34278 .constant('uibAccordionConfig', {
34282 .controller('UibAccordionController', ['$scope', '$attrs', 'uibAccordionConfig', function($scope, $attrs, accordionConfig) {
34283 // This array keeps track of the accordion groups
34286 // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
34287 this.closeOthers = function(openGroup) {
34288 var closeOthers = angular.isDefined($attrs.closeOthers) ?
34289 $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
34291 angular.forEach(this.groups, function(group) {
34292 if (group !== openGroup) {
34293 group.isOpen = false;
34299 // This is called from the accordion-group directive to add itself to the accordion
34300 this.addGroup = function(groupScope) {
34302 this.groups.push(groupScope);
34304 groupScope.$on('$destroy', function(event) {
34305 that.removeGroup(groupScope);
34309 // This is called from the accordion-group directive when to remove itself
34310 this.removeGroup = function(group) {
34311 var index = this.groups.indexOf(group);
34312 if (index !== -1) {
34313 this.groups.splice(index, 1);
34318 // The accordion directive simply sets up the directive controller
34319 // and adds an accordion CSS class to itself element.
34320 .directive('uibAccordion', function() {
34322 controller: 'UibAccordionController',
34323 controllerAs: 'accordion',
34325 templateUrl: function(element, attrs) {
34326 return attrs.templateUrl || 'uib/template/accordion/accordion.html';
34331 // The accordion-group directive indicates a block of html that will expand and collapse in an accordion
34332 .directive('uibAccordionGroup', function() {
34334 require: '^uibAccordion', // We need this directive to be inside an accordion
34335 transclude: true, // It transcludes the contents of the directive into the template
34336 replace: true, // The element containing the directive will be replaced with the template
34337 templateUrl: function(element, attrs) {
34338 return attrs.templateUrl || 'uib/template/accordion/accordion-group.html';
34341 heading: '@', // Interpolate the heading attribute onto this scope
34345 controller: function() {
34346 this.setHeading = function(element) {
34347 this.heading = element;
34350 link: function(scope, element, attrs, accordionCtrl) {
34351 accordionCtrl.addGroup(scope);
34353 scope.openClass = attrs.openClass || 'panel-open';
34354 scope.panelClass = attrs.panelClass || 'panel-default';
34355 scope.$watch('isOpen', function(value) {
34356 element.toggleClass(scope.openClass, !!value);
34358 accordionCtrl.closeOthers(scope);
34362 scope.toggleOpen = function($event) {
34363 if (!scope.isDisabled) {
34364 if (!$event || $event.which === 32) {
34365 scope.isOpen = !scope.isOpen;
34373 // Use accordion-heading below an accordion-group to provide a heading containing HTML
34374 .directive('uibAccordionHeading', function() {
34376 transclude: true, // Grab the contents to be used as the heading
34377 template: '', // In effect remove this element!
34379 require: '^uibAccordionGroup',
34380 link: function(scope, element, attrs, accordionGroupCtrl, transclude) {
34381 // Pass the heading to the accordion-group controller
34382 // so that it can be transcluded into the right place in the template
34383 // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
34384 accordionGroupCtrl.setHeading(transclude(scope, angular.noop));
34389 // Use in the accordion-group template to indicate where you want the heading to be transcluded
34390 // You must provide the property on the accordion-group controller that will hold the transcluded element
34391 .directive('uibAccordionTransclude', function() {
34393 require: '^uibAccordionGroup',
34394 link: function(scope, element, attrs, controller) {
34395 scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) {
34397 element.find('span').html('');
34398 element.find('span').append(heading);
34405 angular.module('ui.bootstrap.alert', [])
34407 .controller('UibAlertController', ['$scope', '$attrs', '$interpolate', '$timeout', function($scope, $attrs, $interpolate, $timeout) {
34408 $scope.closeable = !!$attrs.close;
34410 var dismissOnTimeout = angular.isDefined($attrs.dismissOnTimeout) ?
34411 $interpolate($attrs.dismissOnTimeout)($scope.$parent) : null;
34413 if (dismissOnTimeout) {
34414 $timeout(function() {
34416 }, parseInt(dismissOnTimeout, 10));
34420 .directive('uibAlert', function() {
34422 controller: 'UibAlertController',
34423 controllerAs: 'alert',
34424 templateUrl: function(element, attrs) {
34425 return attrs.templateUrl || 'uib/template/alert/alert.html';
34436 angular.module('ui.bootstrap.buttons', [])
34438 .constant('uibButtonConfig', {
34439 activeClass: 'active',
34440 toggleEvent: 'click'
34443 .controller('UibButtonsController', ['uibButtonConfig', function(buttonConfig) {
34444 this.activeClass = buttonConfig.activeClass || 'active';
34445 this.toggleEvent = buttonConfig.toggleEvent || 'click';
34448 .directive('uibBtnRadio', ['$parse', function($parse) {
34450 require: ['uibBtnRadio', 'ngModel'],
34451 controller: 'UibButtonsController',
34452 controllerAs: 'buttons',
34453 link: function(scope, element, attrs, ctrls) {
34454 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
34455 var uncheckableExpr = $parse(attrs.uibUncheckable);
34457 element.find('input').css({display: 'none'});
34460 ngModelCtrl.$render = function() {
34461 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.uibBtnRadio)));
34465 element.on(buttonsCtrl.toggleEvent, function() {
34466 if (attrs.disabled) {
34470 var isActive = element.hasClass(buttonsCtrl.activeClass);
34472 if (!isActive || angular.isDefined(attrs.uncheckable)) {
34473 scope.$apply(function() {
34474 ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.uibBtnRadio));
34475 ngModelCtrl.$render();
34480 if (attrs.uibUncheckable) {
34481 scope.$watch(uncheckableExpr, function(uncheckable) {
34482 attrs.$set('uncheckable', uncheckable ? '' : null);
34489 .directive('uibBtnCheckbox', function() {
34491 require: ['uibBtnCheckbox', 'ngModel'],
34492 controller: 'UibButtonsController',
34493 controllerAs: 'button',
34494 link: function(scope, element, attrs, ctrls) {
34495 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
34497 element.find('input').css({display: 'none'});
34499 function getTrueValue() {
34500 return getCheckboxValue(attrs.btnCheckboxTrue, true);
34503 function getFalseValue() {
34504 return getCheckboxValue(attrs.btnCheckboxFalse, false);
34507 function getCheckboxValue(attribute, defaultValue) {
34508 return angular.isDefined(attribute) ? scope.$eval(attribute) : defaultValue;
34512 ngModelCtrl.$render = function() {
34513 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
34517 element.on(buttonsCtrl.toggleEvent, function() {
34518 if (attrs.disabled) {
34522 scope.$apply(function() {
34523 ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
34524 ngModelCtrl.$render();
34531 angular.module('ui.bootstrap.carousel', [])
34533 .controller('UibCarouselController', ['$scope', '$element', '$interval', '$timeout', '$animate', function($scope, $element, $interval, $timeout, $animate) {
34535 slides = self.slides = $scope.slides = [],
34536 SLIDE_DIRECTION = 'uib-slideDirection',
34538 currentInterval, isPlaying, bufferedTransitions = [];
34539 self.currentSlide = null;
34541 var destroyed = false;
34543 self.addSlide = function(slide, element) {
34544 slide.$element = element;
34545 slides.push(slide);
34546 //if this is the first slide or the slide is set to active, select it
34547 if (slides.length === 1 || slide.active) {
34548 if ($scope.$currentTransition) {
34549 $scope.$currentTransition = null;
34552 self.select(slides[slides.length - 1]);
34553 if (slides.length === 1) {
34557 slide.active = false;
34561 self.getCurrentIndex = function() {
34562 if (self.currentSlide && angular.isDefined(self.currentSlide.index)) {
34563 return +self.currentSlide.index;
34565 return currentIndex;
34568 self.next = $scope.next = function() {
34569 var newIndex = (self.getCurrentIndex() + 1) % slides.length;
34571 if (newIndex === 0 && $scope.noWrap()) {
34576 return self.select(getSlideByIndex(newIndex), 'next');
34579 self.prev = $scope.prev = function() {
34580 var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1;
34582 if ($scope.noWrap() && newIndex === slides.length - 1) {
34587 return self.select(getSlideByIndex(newIndex), 'prev');
34590 self.removeSlide = function(slide) {
34591 if (angular.isDefined(slide.index)) {
34592 slides.sort(function(a, b) {
34593 return +a.index > +b.index;
34597 var bufferedIndex = bufferedTransitions.indexOf(slide);
34598 if (bufferedIndex !== -1) {
34599 bufferedTransitions.splice(bufferedIndex, 1);
34601 //get the index of the slide inside the carousel
34602 var index = slides.indexOf(slide);
34603 slides.splice(index, 1);
34604 $timeout(function() {
34605 if (slides.length > 0 && slide.active) {
34606 if (index >= slides.length) {
34607 self.select(slides[index - 1]);
34609 self.select(slides[index]);
34611 } else if (currentIndex > index) {
34616 //clean the currentSlide when no more slide
34617 if (slides.length === 0) {
34618 self.currentSlide = null;
34619 clearBufferedTransitions();
34623 /* direction: "prev" or "next" */
34624 self.select = $scope.select = function(nextSlide, direction) {
34625 var nextIndex = $scope.indexOfSlide(nextSlide);
34626 //Decide direction if it's not given
34627 if (direction === undefined) {
34628 direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
34630 //Prevent this user-triggered transition from occurring if there is already one in progress
34631 if (nextSlide && nextSlide !== self.currentSlide && !$scope.$currentTransition) {
34632 goNext(nextSlide, nextIndex, direction);
34633 } else if (nextSlide && nextSlide !== self.currentSlide && $scope.$currentTransition) {
34634 bufferedTransitions.push(nextSlide);
34638 /* Allow outside people to call indexOf on slides array */
34639 $scope.indexOfSlide = function(slide) {
34640 return angular.isDefined(slide.index) ? +slide.index : slides.indexOf(slide);
34643 $scope.isActive = function(slide) {
34644 return self.currentSlide === slide;
34647 $scope.pause = function() {
34648 if (!$scope.noPause) {
34654 $scope.play = function() {
34661 $scope.$on('$destroy', function() {
34666 $scope.$watch('noTransition', function(noTransition) {
34667 $animate.enabled($element, !noTransition);
34670 $scope.$watch('interval', restartTimer);
34672 $scope.$watchCollection('slides', resetTransition);
34674 function clearBufferedTransitions() {
34675 while (bufferedTransitions.length) {
34676 bufferedTransitions.shift();
34680 function getSlideByIndex(index) {
34681 if (angular.isUndefined(slides[index].index)) {
34682 return slides[index];
34684 for (var i = 0, l = slides.length; i < l; ++i) {
34685 if (slides[i].index === index) {
34691 function goNext(slide, index, direction) {
34692 if (destroyed) { return; }
34694 angular.extend(slide, {direction: direction, active: true});
34695 angular.extend(self.currentSlide || {}, {direction: direction, active: false});
34696 if ($animate.enabled($element) && !$scope.$currentTransition &&
34697 slide.$element && self.slides.length > 1) {
34698 slide.$element.data(SLIDE_DIRECTION, slide.direction);
34699 if (self.currentSlide && self.currentSlide.$element) {
34700 self.currentSlide.$element.data(SLIDE_DIRECTION, slide.direction);
34703 $scope.$currentTransition = true;
34704 $animate.on('addClass', slide.$element, function(element, phase) {
34705 if (phase === 'close') {
34706 $scope.$currentTransition = null;
34707 $animate.off('addClass', element);
34708 if (bufferedTransitions.length) {
34709 var nextSlide = bufferedTransitions.pop();
34710 var nextIndex = $scope.indexOfSlide(nextSlide);
34711 var nextDirection = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
34712 clearBufferedTransitions();
34714 goNext(nextSlide, nextIndex, nextDirection);
34720 self.currentSlide = slide;
34721 currentIndex = index;
34723 //every time you change slides, reset the timer
34727 function resetTimer() {
34728 if (currentInterval) {
34729 $interval.cancel(currentInterval);
34730 currentInterval = null;
34734 function resetTransition(slides) {
34735 if (!slides.length) {
34736 $scope.$currentTransition = null;
34737 clearBufferedTransitions();
34741 function restartTimer() {
34743 var interval = +$scope.interval;
34744 if (!isNaN(interval) && interval > 0) {
34745 currentInterval = $interval(timerFn, interval);
34749 function timerFn() {
34750 var interval = +$scope.interval;
34751 if (isPlaying && !isNaN(interval) && interval > 0 && slides.length) {
34759 .directive('uibCarousel', function() {
34763 controller: 'UibCarouselController',
34764 controllerAs: 'carousel',
34765 templateUrl: function(element, attrs) {
34766 return attrs.templateUrl || 'uib/template/carousel/carousel.html';
34777 .directive('uibSlide', function() {
34779 require: '^uibCarousel',
34782 templateUrl: function(element, attrs) {
34783 return attrs.templateUrl || 'uib/template/carousel/slide.html';
34790 link: function (scope, element, attrs, carouselCtrl) {
34791 carouselCtrl.addSlide(scope, element);
34792 //when the scope is destroyed then remove the slide from the current slides array
34793 scope.$on('$destroy', function() {
34794 carouselCtrl.removeSlide(scope);
34797 scope.$watch('active', function(active) {
34799 carouselCtrl.select(scope);
34806 .animation('.item', ['$animateCss',
34807 function($animateCss) {
34808 var SLIDE_DIRECTION = 'uib-slideDirection';
34810 function removeClass(element, className, callback) {
34811 element.removeClass(className);
34818 beforeAddClass: function(element, className, done) {
34819 if (className === 'active') {
34820 var stopped = false;
34821 var direction = element.data(SLIDE_DIRECTION);
34822 var directionClass = direction === 'next' ? 'left' : 'right';
34823 var removeClassFn = removeClass.bind(this, element,
34824 directionClass + ' ' + direction, done);
34825 element.addClass(direction);
34827 $animateCss(element, {addClass: directionClass})
34829 .done(removeClassFn);
34831 return function() {
34837 beforeRemoveClass: function (element, className, done) {
34838 if (className === 'active') {
34839 var stopped = false;
34840 var direction = element.data(SLIDE_DIRECTION);
34841 var directionClass = direction === 'next' ? 'left' : 'right';
34842 var removeClassFn = removeClass.bind(this, element, directionClass, done);
34844 $animateCss(element, {addClass: directionClass})
34846 .done(removeClassFn);
34848 return function() {
34857 angular.module('ui.bootstrap.dateparser', [])
34859 .service('uibDateParser', ['$log', '$locale', 'orderByFilter', function($log, $locale, orderByFilter) {
34860 // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js
34861 var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
34864 var formatCodeToRegex;
34866 this.init = function() {
34867 localeId = $locale.id;
34871 formatCodeToRegex = [
34875 apply: function(value) { this.year = +value; }
34880 apply: function(value) { this.year = +value + 2000; }
34885 apply: function(value) { this.year = +value; }
34889 regex: '0?[1-9]|1[0-2]',
34890 apply: function(value) { this.month = value - 1; }
34894 regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
34895 apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); }
34899 regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
34900 apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); }
34904 regex: '0[1-9]|1[0-2]',
34905 apply: function(value) { this.month = value - 1; }
34909 regex: '[1-9]|1[0-2]',
34910 apply: function(value) { this.month = value - 1; }
34914 regex: '[0-2]?[0-9]{1}|3[0-1]{1}',
34915 apply: function(value) { this.date = +value; }
34919 regex: '[0-2][0-9]{1}|3[0-1]{1}',
34920 apply: function(value) { this.date = +value; }
34924 regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
34925 apply: function(value) { this.date = +value; }
34929 regex: $locale.DATETIME_FORMATS.DAY.join('|')
34933 regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|')
34937 regex: '(?:0|1)[0-9]|2[0-3]',
34938 apply: function(value) { this.hours = +value; }
34942 regex: '0[0-9]|1[0-2]',
34943 apply: function(value) { this.hours = +value; }
34947 regex: '1?[0-9]|2[0-3]',
34948 apply: function(value) { this.hours = +value; }
34952 regex: '[0-9]|1[0-2]',
34953 apply: function(value) { this.hours = +value; }
34957 regex: '[0-5][0-9]',
34958 apply: function(value) { this.minutes = +value; }
34962 regex: '[0-9]|[1-5][0-9]',
34963 apply: function(value) { this.minutes = +value; }
34967 regex: '[0-9][0-9][0-9]',
34968 apply: function(value) { this.milliseconds = +value; }
34972 regex: '[0-5][0-9]',
34973 apply: function(value) { this.seconds = +value; }
34977 regex: '[0-9]|[1-5][0-9]',
34978 apply: function(value) { this.seconds = +value; }
34982 regex: $locale.DATETIME_FORMATS.AMPMS.join('|'),
34983 apply: function(value) {
34984 if (this.hours === 12) {
34988 if (value === 'PM') {
34995 regex: '[+-]\\d{4}',
34996 apply: function(value) {
34997 var matches = value.match(/([+-])(\d{2})(\d{2})/),
34999 hours = matches[2],
35000 minutes = matches[3];
35001 this.hours += toInt(sign + hours);
35002 this.minutes += toInt(sign + minutes);
35007 regex: '[0-4][0-9]|5[0-3]'
35011 regex: '[0-9]|[1-4][0-9]|5[0-3]'
35015 regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\s/g, '\\s')
35019 regex: $locale.DATETIME_FORMATS.ERAS.join('|')
35023 regex: $locale.DATETIME_FORMATS.ERAS.join('|')
35027 regex: $locale.DATETIME_FORMATS.ERAS.join('|')
35034 function createParser(format) {
35035 var map = [], regex = format.split('');
35037 // check for literal values
35038 var quoteIndex = format.indexOf('\'');
35039 if (quoteIndex > -1) {
35040 var inLiteral = false;
35041 format = format.split('');
35042 for (var i = quoteIndex; i < format.length; i++) {
35044 if (format[i] === '\'') {
35045 if (i + 1 < format.length && format[i+1] === '\'') { // escaped single quote
35048 } else { // end of literal
35055 if (format[i] === '\'') { // start of literal
35063 format = format.join('');
35066 angular.forEach(formatCodeToRegex, function(data) {
35067 var index = format.indexOf(data.key);
35070 format = format.split('');
35072 regex[index] = '(' + data.regex + ')';
35073 format[index] = '$'; // Custom symbol to define consumed part of format
35074 for (var i = index + 1, n = index + data.key.length; i < n; i++) {
35078 format = format.join('');
35083 matcher: data.regex
35089 regex: new RegExp('^' + regex.join('') + '$'),
35090 map: orderByFilter(map, 'index')
35094 this.parse = function(input, format, baseDate) {
35095 if (!angular.isString(input) || !format) {
35099 format = $locale.DATETIME_FORMATS[format] || format;
35100 format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&');
35102 if ($locale.id !== localeId) {
35106 if (!this.parsers[format]) {
35107 this.parsers[format] = createParser(format);
35110 var parser = this.parsers[format],
35111 regex = parser.regex,
35113 results = input.match(regex),
35115 if (results && results.length) {
35117 if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) {
35119 year: baseDate.getFullYear(),
35120 month: baseDate.getMonth(),
35121 date: baseDate.getDate(),
35122 hours: baseDate.getHours(),
35123 minutes: baseDate.getMinutes(),
35124 seconds: baseDate.getSeconds(),
35125 milliseconds: baseDate.getMilliseconds()
35129 $log.warn('dateparser:', 'baseDate is not a valid date');
35131 fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 };
35134 for (var i = 1, n = results.length; i < n; i++) {
35135 var mapper = map[i - 1];
35136 if (mapper.matcher === 'Z') {
35140 if (mapper.apply) {
35141 mapper.apply.call(fields, results[i]);
35145 var datesetter = tzOffset ? Date.prototype.setUTCFullYear :
35146 Date.prototype.setFullYear;
35147 var timesetter = tzOffset ? Date.prototype.setUTCHours :
35148 Date.prototype.setHours;
35150 if (isValid(fields.year, fields.month, fields.date)) {
35151 if (angular.isDate(baseDate) && !isNaN(baseDate.getTime()) && !tzOffset) {
35152 dt = new Date(baseDate);
35153 datesetter.call(dt, fields.year, fields.month, fields.date);
35154 timesetter.call(dt, fields.hours, fields.minutes,
35155 fields.seconds, fields.milliseconds);
35158 datesetter.call(dt, fields.year, fields.month, fields.date);
35159 timesetter.call(dt, fields.hours || 0, fields.minutes || 0,
35160 fields.seconds || 0, fields.milliseconds || 0);
35168 // Check if date is valid for specific month (and year for February).
35169 // Month: 0 = Jan, 1 = Feb, etc
35170 function isValid(year, month, date) {
35175 if (month === 1 && date > 28) {
35176 return date === 29 && (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0);
35179 if (month === 3 || month === 5 || month === 8 || month === 10) {
35186 function toInt(str) {
35187 return parseInt(str, 10);
35190 this.toTimezone = toTimezone;
35191 this.fromTimezone = fromTimezone;
35192 this.timezoneToOffset = timezoneToOffset;
35193 this.addDateMinutes = addDateMinutes;
35194 this.convertTimezoneToLocal = convertTimezoneToLocal;
35196 function toTimezone(date, timezone) {
35197 return date && timezone ? convertTimezoneToLocal(date, timezone) : date;
35200 function fromTimezone(date, timezone) {
35201 return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date;
35204 //https://github.com/angular/angular.js/blob/4daafd3dbe6a80d578f5a31df1bb99c77559543e/src/Angular.js#L1207
35205 function timezoneToOffset(timezone, fallback) {
35206 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
35207 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
35210 function addDateMinutes(date, minutes) {
35211 date = new Date(date.getTime());
35212 date.setMinutes(date.getMinutes() + minutes);
35216 function convertTimezoneToLocal(date, timezone, reverse) {
35217 reverse = reverse ? -1 : 1;
35218 var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
35219 return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
35223 // Avoiding use of ng-class as it creates a lot of watchers when a class is to be applied to
35224 // at most one element.
35225 angular.module('ui.bootstrap.isClass', [])
35226 .directive('uibIsClass', [
35228 function ($animate) {
35229 // 11111111 22222222
35230 var ON_REGEXP = /^\s*([\s\S]+?)\s+on\s+([\s\S]+?)\s*$/;
35231 // 11111111 22222222
35232 var IS_REGEXP = /^\s*([\s\S]+?)\s+for\s+([\s\S]+?)\s*$/;
35234 var dataPerTracked = {};
35238 compile: function (tElement, tAttrs) {
35239 var linkedScopes = [];
35240 var instances = [];
35241 var expToData = {};
35242 var lastActivated = null;
35243 var onExpMatches = tAttrs.uibIsClass.match(ON_REGEXP);
35244 var onExp = onExpMatches[2];
35245 var expsStr = onExpMatches[1];
35246 var exps = expsStr.split(',');
35250 function linkFn(scope, element, attrs) {
35251 linkedScopes.push(scope);
35257 exps.forEach(function (exp, k) {
35258 addForExp(exp, scope);
35261 scope.$on('$destroy', removeScope);
35264 function addForExp(exp, scope) {
35265 var matches = exp.match(IS_REGEXP);
35266 var clazz = scope.$eval(matches[1]);
35267 var compareWithExp = matches[2];
35268 var data = expToData[exp];
35270 var watchFn = function (compareWithVal) {
35271 var newActivated = null;
35272 instances.some(function (instance) {
35273 var thisVal = instance.scope.$eval(onExp);
35274 if (thisVal === compareWithVal) {
35275 newActivated = instance;
35279 if (data.lastActivated !== newActivated) {
35280 if (data.lastActivated) {
35281 $animate.removeClass(data.lastActivated.element, clazz);
35283 if (newActivated) {
35284 $animate.addClass(newActivated.element, clazz);
35286 data.lastActivated = newActivated;
35289 expToData[exp] = data = {
35290 lastActivated: null,
35293 compareWithExp: compareWithExp,
35294 watcher: scope.$watch(compareWithExp, watchFn)
35297 data.watchFn(scope.$eval(compareWithExp));
35300 function removeScope(e) {
35301 var removedScope = e.targetScope;
35302 var index = linkedScopes.indexOf(removedScope);
35303 linkedScopes.splice(index, 1);
35304 instances.splice(index, 1);
35305 if (linkedScopes.length) {
35306 var newWatchScope = linkedScopes[0];
35307 angular.forEach(expToData, function (data) {
35308 if (data.scope === removedScope) {
35309 data.watcher = newWatchScope.$watch(data.compareWithExp, data.watchFn);
35310 data.scope = newWatchScope;
35321 angular.module('ui.bootstrap.position', [])
35324 * A set of utility methods for working with the DOM.
35325 * It is meant to be used where we need to absolute-position elements in
35326 * relation to another element (this is the case for tooltips, popovers,
35327 * typeahead suggestions etc.).
35329 .factory('$uibPosition', ['$document', '$window', function($document, $window) {
35331 * Used by scrollbarWidth() function to cache scrollbar's width.
35332 * Do not access this variable directly, use scrollbarWidth() instead.
35334 var SCROLLBAR_WIDTH;
35335 var OVERFLOW_REGEX = {
35336 normal: /(auto|scroll)/,
35337 hidden: /(auto|scroll|hidden)/
35339 var PLACEMENT_REGEX = {
35340 auto: /\s?auto?\s?/i,
35341 primary: /^(top|bottom|left|right)$/,
35342 secondary: /^(top|bottom|left|right|center)$/,
35343 vertical: /^(top|bottom)$/
35349 * Provides a raw DOM element from a jQuery/jQLite element.
35351 * @param {element} elem - The element to convert.
35353 * @returns {element} A HTML element.
35355 getRawNode: function(elem) {
35356 return elem[0] || elem;
35360 * Provides a parsed number for a style property. Strips
35361 * units and casts invalid numbers to 0.
35363 * @param {string} value - The style value to parse.
35365 * @returns {number} A valid number.
35367 parseStyle: function(value) {
35368 value = parseFloat(value);
35369 return isFinite(value) ? value : 0;
35373 * Provides the closest positioned ancestor.
35375 * @param {element} element - The element to get the offest parent for.
35377 * @returns {element} The closest positioned ancestor.
35379 offsetParent: function(elem) {
35380 elem = this.getRawNode(elem);
35382 var offsetParent = elem.offsetParent || $document[0].documentElement;
35384 function isStaticPositioned(el) {
35385 return ($window.getComputedStyle(el).position || 'static') === 'static';
35388 while (offsetParent && offsetParent !== $document[0].documentElement && isStaticPositioned(offsetParent)) {
35389 offsetParent = offsetParent.offsetParent;
35392 return offsetParent || $document[0].documentElement;
35396 * Provides the scrollbar width, concept from TWBS measureScrollbar()
35397 * function in https://github.com/twbs/bootstrap/blob/master/js/modal.js
35399 * @returns {number} The width of the browser scollbar.
35401 scrollbarWidth: function() {
35402 if (angular.isUndefined(SCROLLBAR_WIDTH)) {
35403 var scrollElem = angular.element('<div style="position: absolute; top: -9999px; width: 50px; height: 50px; overflow: scroll;"></div>');
35404 $document.find('body').append(scrollElem);
35405 SCROLLBAR_WIDTH = scrollElem[0].offsetWidth - scrollElem[0].clientWidth;
35406 SCROLLBAR_WIDTH = isFinite(SCROLLBAR_WIDTH) ? SCROLLBAR_WIDTH : 0;
35407 scrollElem.remove();
35410 return SCROLLBAR_WIDTH;
35414 * Provides the closest scrollable ancestor.
35415 * A port of the jQuery UI scrollParent method:
35416 * https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js
35418 * @param {element} elem - The element to find the scroll parent of.
35419 * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
35420 * default is false.
35422 * @returns {element} A HTML element.
35424 scrollParent: function(elem, includeHidden) {
35425 elem = this.getRawNode(elem);
35427 var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
35428 var documentEl = $document[0].documentElement;
35429 var elemStyle = $window.getComputedStyle(elem);
35430 var excludeStatic = elemStyle.position === 'absolute';
35431 var scrollParent = elem.parentElement || documentEl;
35433 if (scrollParent === documentEl || elemStyle.position === 'fixed') {
35437 while (scrollParent.parentElement && scrollParent !== documentEl) {
35438 var spStyle = $window.getComputedStyle(scrollParent);
35439 if (excludeStatic && spStyle.position !== 'static') {
35440 excludeStatic = false;
35443 if (!excludeStatic && overflowRegex.test(spStyle.overflow + spStyle.overflowY + spStyle.overflowX)) {
35446 scrollParent = scrollParent.parentElement;
35449 return scrollParent;
35453 * Provides read-only equivalent of jQuery's position function:
35454 * http://api.jquery.com/position/ - distance to closest positioned
35455 * ancestor. Does not account for margins by default like jQuery position.
35457 * @param {element} elem - The element to caclulate the position on.
35458 * @param {boolean=} [includeMargins=false] - Should margins be accounted
35459 * for, default is false.
35461 * @returns {object} An object with the following properties:
35463 * <li>**width**: the width of the element</li>
35464 * <li>**height**: the height of the element</li>
35465 * <li>**top**: distance to top edge of offset parent</li>
35466 * <li>**left**: distance to left edge of offset parent</li>
35469 position: function(elem, includeMagins) {
35470 elem = this.getRawNode(elem);
35472 var elemOffset = this.offset(elem);
35473 if (includeMagins) {
35474 var elemStyle = $window.getComputedStyle(elem);
35475 elemOffset.top -= this.parseStyle(elemStyle.marginTop);
35476 elemOffset.left -= this.parseStyle(elemStyle.marginLeft);
35478 var parent = this.offsetParent(elem);
35479 var parentOffset = {top: 0, left: 0};
35481 if (parent !== $document[0].documentElement) {
35482 parentOffset = this.offset(parent);
35483 parentOffset.top += parent.clientTop - parent.scrollTop;
35484 parentOffset.left += parent.clientLeft - parent.scrollLeft;
35488 width: Math.round(angular.isNumber(elemOffset.width) ? elemOffset.width : elem.offsetWidth),
35489 height: Math.round(angular.isNumber(elemOffset.height) ? elemOffset.height : elem.offsetHeight),
35490 top: Math.round(elemOffset.top - parentOffset.top),
35491 left: Math.round(elemOffset.left - parentOffset.left)
35496 * Provides read-only equivalent of jQuery's offset function:
35497 * http://api.jquery.com/offset/ - distance to viewport. Does
35498 * not account for borders, margins, or padding on the body
35501 * @param {element} elem - The element to calculate the offset on.
35503 * @returns {object} An object with the following properties:
35505 * <li>**width**: the width of the element</li>
35506 * <li>**height**: the height of the element</li>
35507 * <li>**top**: distance to top edge of viewport</li>
35508 * <li>**right**: distance to bottom edge of viewport</li>
35511 offset: function(elem) {
35512 elem = this.getRawNode(elem);
35514 var elemBCR = elem.getBoundingClientRect();
35516 width: Math.round(angular.isNumber(elemBCR.width) ? elemBCR.width : elem.offsetWidth),
35517 height: Math.round(angular.isNumber(elemBCR.height) ? elemBCR.height : elem.offsetHeight),
35518 top: Math.round(elemBCR.top + ($window.pageYOffset || $document[0].documentElement.scrollTop)),
35519 left: Math.round(elemBCR.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft))
35524 * Provides offset distance to the closest scrollable ancestor
35525 * or viewport. Accounts for border and scrollbar width.
35527 * Right and bottom dimensions represent the distance to the
35528 * respective edge of the viewport element. If the element
35529 * edge extends beyond the viewport, a negative value will be
35532 * @param {element} elem - The element to get the viewport offset for.
35533 * @param {boolean=} [useDocument=false] - Should the viewport be the document element instead
35534 * of the first scrollable element, default is false.
35535 * @param {boolean=} [includePadding=true] - Should the padding on the offset parent element
35536 * be accounted for, default is true.
35538 * @returns {object} An object with the following properties:
35540 * <li>**top**: distance to the top content edge of viewport element</li>
35541 * <li>**bottom**: distance to the bottom content edge of viewport element</li>
35542 * <li>**left**: distance to the left content edge of viewport element</li>
35543 * <li>**right**: distance to the right content edge of viewport element</li>
35546 viewportOffset: function(elem, useDocument, includePadding) {
35547 elem = this.getRawNode(elem);
35548 includePadding = includePadding !== false ? true : false;
35550 var elemBCR = elem.getBoundingClientRect();
35551 var offsetBCR = {top: 0, left: 0, bottom: 0, right: 0};
35553 var offsetParent = useDocument ? $document[0].documentElement : this.scrollParent(elem);
35554 var offsetParentBCR = offsetParent.getBoundingClientRect();
35556 offsetBCR.top = offsetParentBCR.top + offsetParent.clientTop;
35557 offsetBCR.left = offsetParentBCR.left + offsetParent.clientLeft;
35558 if (offsetParent === $document[0].documentElement) {
35559 offsetBCR.top += $window.pageYOffset;
35560 offsetBCR.left += $window.pageXOffset;
35562 offsetBCR.bottom = offsetBCR.top + offsetParent.clientHeight;
35563 offsetBCR.right = offsetBCR.left + offsetParent.clientWidth;
35565 if (includePadding) {
35566 var offsetParentStyle = $window.getComputedStyle(offsetParent);
35567 offsetBCR.top += this.parseStyle(offsetParentStyle.paddingTop);
35568 offsetBCR.bottom -= this.parseStyle(offsetParentStyle.paddingBottom);
35569 offsetBCR.left += this.parseStyle(offsetParentStyle.paddingLeft);
35570 offsetBCR.right -= this.parseStyle(offsetParentStyle.paddingRight);
35574 top: Math.round(elemBCR.top - offsetBCR.top),
35575 bottom: Math.round(offsetBCR.bottom - elemBCR.bottom),
35576 left: Math.round(elemBCR.left - offsetBCR.left),
35577 right: Math.round(offsetBCR.right - elemBCR.right)
35582 * Provides an array of placement values parsed from a placement string.
35583 * Along with the 'auto' indicator, supported placement strings are:
35585 * <li>top: element on top, horizontally centered on host element.</li>
35586 * <li>top-left: element on top, left edge aligned with host element left edge.</li>
35587 * <li>top-right: element on top, lerightft edge aligned with host element right edge.</li>
35588 * <li>bottom: element on bottom, horizontally centered on host element.</li>
35589 * <li>bottom-left: element on bottom, left edge aligned with host element left edge.</li>
35590 * <li>bottom-right: element on bottom, right edge aligned with host element right edge.</li>
35591 * <li>left: element on left, vertically centered on host element.</li>
35592 * <li>left-top: element on left, top edge aligned with host element top edge.</li>
35593 * <li>left-bottom: element on left, bottom edge aligned with host element bottom edge.</li>
35594 * <li>right: element on right, vertically centered on host element.</li>
35595 * <li>right-top: element on right, top edge aligned with host element top edge.</li>
35596 * <li>right-bottom: element on right, bottom edge aligned with host element bottom edge.</li>
35598 * A placement string with an 'auto' indicator is expected to be
35599 * space separated from the placement, i.e: 'auto bottom-left' If
35600 * the primary and secondary placement values do not match 'top,
35601 * bottom, left, right' then 'top' will be the primary placement and
35602 * 'center' will be the secondary placement. If 'auto' is passed, true
35603 * will be returned as the 3rd value of the array.
35605 * @param {string} placement - The placement string to parse.
35607 * @returns {array} An array with the following values
35609 * <li>**[0]**: The primary placement.</li>
35610 * <li>**[1]**: The secondary placement.</li>
35611 * <li>**[2]**: If auto is passed: true, else undefined.</li>
35614 parsePlacement: function(placement) {
35615 var autoPlace = PLACEMENT_REGEX.auto.test(placement);
35617 placement = placement.replace(PLACEMENT_REGEX.auto, '');
35620 placement = placement.split('-');
35622 placement[0] = placement[0] || 'top';
35623 if (!PLACEMENT_REGEX.primary.test(placement[0])) {
35624 placement[0] = 'top';
35627 placement[1] = placement[1] || 'center';
35628 if (!PLACEMENT_REGEX.secondary.test(placement[1])) {
35629 placement[1] = 'center';
35633 placement[2] = true;
35635 placement[2] = false;
35642 * Provides coordinates for an element to be positioned relative to
35643 * another element. Passing 'auto' as part of the placement parameter
35644 * will enable smart placement - where the element fits. i.e:
35645 * 'auto left-top' will check to see if there is enough space to the left
35646 * of the hostElem to fit the targetElem, if not place right (same for secondary
35647 * top placement). Available space is calculated using the viewportOffset
35650 * @param {element} hostElem - The element to position against.
35651 * @param {element} targetElem - The element to position.
35652 * @param {string=} [placement=top] - The placement for the targetElem,
35653 * default is 'top'. 'center' is assumed as secondary placement for
35654 * 'top', 'left', 'right', and 'bottom' placements. Available placements are:
35657 * <li>top-right</li>
35658 * <li>top-left</li>
35660 * <li>bottom-left</li>
35661 * <li>bottom-right</li>
35663 * <li>left-top</li>
35664 * <li>left-bottom</li>
35666 * <li>right-top</li>
35667 * <li>right-bottom</li>
35669 * @param {boolean=} [appendToBody=false] - Should the top and left values returned
35670 * be calculated from the body element, default is false.
35672 * @returns {object} An object with the following properties:
35674 * <li>**top**: Value for targetElem top.</li>
35675 * <li>**left**: Value for targetElem left.</li>
35676 * <li>**placement**: The resolved placement.</li>
35679 positionElements: function(hostElem, targetElem, placement, appendToBody) {
35680 hostElem = this.getRawNode(hostElem);
35681 targetElem = this.getRawNode(targetElem);
35683 // need to read from prop to support tests.
35684 var targetWidth = angular.isDefined(targetElem.offsetWidth) ? targetElem.offsetWidth : targetElem.prop('offsetWidth');
35685 var targetHeight = angular.isDefined(targetElem.offsetHeight) ? targetElem.offsetHeight : targetElem.prop('offsetHeight');
35687 placement = this.parsePlacement(placement);
35689 var hostElemPos = appendToBody ? this.offset(hostElem) : this.position(hostElem);
35690 var targetElemPos = {top: 0, left: 0, placement: ''};
35692 if (placement[2]) {
35693 var viewportOffset = this.viewportOffset(hostElem);
35695 var targetElemStyle = $window.getComputedStyle(targetElem);
35696 var adjustedSize = {
35697 width: targetWidth + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginLeft) + this.parseStyle(targetElemStyle.marginRight))),
35698 height: targetHeight + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginTop) + this.parseStyle(targetElemStyle.marginBottom)))
35701 placement[0] = placement[0] === 'top' && adjustedSize.height > viewportOffset.top && adjustedSize.height <= viewportOffset.bottom ? 'bottom' :
35702 placement[0] === 'bottom' && adjustedSize.height > viewportOffset.bottom && adjustedSize.height <= viewportOffset.top ? 'top' :
35703 placement[0] === 'left' && adjustedSize.width > viewportOffset.left && adjustedSize.width <= viewportOffset.right ? 'right' :
35704 placement[0] === 'right' && adjustedSize.width > viewportOffset.right && adjustedSize.width <= viewportOffset.left ? 'left' :
35707 placement[1] = placement[1] === 'top' && adjustedSize.height - hostElemPos.height > viewportOffset.bottom && adjustedSize.height - hostElemPos.height <= viewportOffset.top ? 'bottom' :
35708 placement[1] === 'bottom' && adjustedSize.height - hostElemPos.height > viewportOffset.top && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom ? 'top' :
35709 placement[1] === 'left' && adjustedSize.width - hostElemPos.width > viewportOffset.right && adjustedSize.width - hostElemPos.width <= viewportOffset.left ? 'right' :
35710 placement[1] === 'right' && adjustedSize.width - hostElemPos.width > viewportOffset.left && adjustedSize.width - hostElemPos.width <= viewportOffset.right ? 'left' :
35713 if (placement[1] === 'center') {
35714 if (PLACEMENT_REGEX.vertical.test(placement[0])) {
35715 var xOverflow = hostElemPos.width / 2 - targetWidth / 2;
35716 if (viewportOffset.left + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.right) {
35717 placement[1] = 'left';
35718 } else if (viewportOffset.right + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.left) {
35719 placement[1] = 'right';
35722 var yOverflow = hostElemPos.height / 2 - adjustedSize.height / 2;
35723 if (viewportOffset.top + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom) {
35724 placement[1] = 'top';
35725 } else if (viewportOffset.bottom + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.top) {
35726 placement[1] = 'bottom';
35732 switch (placement[0]) {
35734 targetElemPos.top = hostElemPos.top - targetHeight;
35737 targetElemPos.top = hostElemPos.top + hostElemPos.height;
35740 targetElemPos.left = hostElemPos.left - targetWidth;
35743 targetElemPos.left = hostElemPos.left + hostElemPos.width;
35747 switch (placement[1]) {
35749 targetElemPos.top = hostElemPos.top;
35752 targetElemPos.top = hostElemPos.top + hostElemPos.height - targetHeight;
35755 targetElemPos.left = hostElemPos.left;
35758 targetElemPos.left = hostElemPos.left + hostElemPos.width - targetWidth;
35761 if (PLACEMENT_REGEX.vertical.test(placement[0])) {
35762 targetElemPos.left = hostElemPos.left + hostElemPos.width / 2 - targetWidth / 2;
35764 targetElemPos.top = hostElemPos.top + hostElemPos.height / 2 - targetHeight / 2;
35769 targetElemPos.top = Math.round(targetElemPos.top);
35770 targetElemPos.left = Math.round(targetElemPos.left);
35771 targetElemPos.placement = placement[1] === 'center' ? placement[0] : placement[0] + '-' + placement[1];
35773 return targetElemPos;
35777 * Provides a way for positioning tooltip & dropdown
35778 * arrows when using placement options beyond the standard
35779 * left, right, top, or bottom.
35781 * @param {element} elem - The tooltip/dropdown element.
35782 * @param {string} placement - The placement for the elem.
35784 positionArrow: function(elem, placement) {
35785 elem = this.getRawNode(elem);
35787 var isTooltip = true;
35789 var innerElem = elem.querySelector('.tooltip-inner');
35792 innerElem = elem.querySelector('.popover-inner');
35798 var arrowElem = isTooltip ? elem.querySelector('.tooltip-arrow') : elem.querySelector('.arrow');
35803 placement = this.parsePlacement(placement);
35804 if (placement[1] === 'center') {
35805 // no adjustment necessary - just reset styles
35806 angular.element(arrowElem).css({top: '', bottom: '', right: '', left: '', margin: ''});
35810 var borderProp = 'border-' + placement[0] + '-width';
35811 var borderWidth = $window.getComputedStyle(arrowElem)[borderProp];
35813 var borderRadiusProp = 'border-';
35814 if (PLACEMENT_REGEX.vertical.test(placement[0])) {
35815 borderRadiusProp += placement[0] + '-' + placement[1];
35817 borderRadiusProp += placement[1] + '-' + placement[0];
35819 borderRadiusProp += '-radius';
35820 var borderRadius = $window.getComputedStyle(isTooltip ? innerElem : elem)[borderRadiusProp];
35830 switch (placement[0]) {
35832 arrowCss.bottom = isTooltip ? '0' : '-' + borderWidth;
35835 arrowCss.top = isTooltip ? '0' : '-' + borderWidth;
35838 arrowCss.right = isTooltip ? '0' : '-' + borderWidth;
35841 arrowCss.left = isTooltip ? '0' : '-' + borderWidth;
35845 arrowCss[placement[1]] = borderRadius;
35847 angular.element(arrowElem).css(arrowCss);
35852 angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.isClass', 'ui.bootstrap.position'])
35854 .value('$datepickerSuppressError', false)
35856 .constant('uibDatepickerConfig', {
35858 formatMonth: 'MMMM',
35859 formatYear: 'yyyy',
35860 formatDayHeader: 'EEE',
35861 formatDayTitle: 'MMMM yyyy',
35862 formatMonthTitle: 'yyyy',
35863 datepickerMode: 'day',
35872 shortcutPropagation: false,
35876 .controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerSuppressError', 'uibDateParser',
35877 function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError, dateParser) {
35879 ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl;
35880 ngModelOptions = {};
35883 this.modes = ['day', 'month', 'year'];
35885 // Interpolated configuration attributes
35886 angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle'], function(key) {
35887 self[key] = angular.isDefined($attrs[key]) ? $interpolate($attrs[key])($scope.$parent) : datepickerConfig[key];
35890 // Evaled configuration attributes
35891 angular.forEach(['showWeeks', 'startingDay', 'yearRows', 'yearColumns', 'shortcutPropagation'], function(key) {
35892 self[key] = angular.isDefined($attrs[key]) ? $scope.$parent.$eval($attrs[key]) : datepickerConfig[key];
35895 // Watchable date attributes
35896 angular.forEach(['minDate', 'maxDate'], function(key) {
35898 $scope.$parent.$watch($attrs[key], function(value) {
35899 self[key] = value ? angular.isDate(value) ? dateParser.fromTimezone(new Date(value), ngModelOptions.timezone) : new Date(dateFilter(value, 'medium')) : null;
35900 self.refreshView();
35903 self[key] = datepickerConfig[key] ? dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) : null;
35907 angular.forEach(['minMode', 'maxMode'], function(key) {
35909 $scope.$parent.$watch($attrs[key], function(value) {
35910 self[key] = $scope[key] = angular.isDefined(value) ? value : $attrs[key];
35911 if (key === 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key]) ||
35912 key === 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key])) {
35913 $scope.datepickerMode = self[key];
35917 self[key] = $scope[key] = datepickerConfig[key] || null;
35921 $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode;
35922 $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
35924 if (angular.isDefined($attrs.initDate)) {
35925 this.activeDate = dateParser.fromTimezone($scope.$parent.$eval($attrs.initDate), ngModelOptions.timezone) || new Date();
35926 $scope.$parent.$watch($attrs.initDate, function(initDate) {
35927 if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
35928 self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone);
35929 self.refreshView();
35933 this.activeDate = new Date();
35936 $scope.disabled = angular.isDefined($attrs.disabled) || false;
35937 if (angular.isDefined($attrs.ngDisabled)) {
35938 $scope.$parent.$watch($attrs.ngDisabled, function(disabled) {
35939 $scope.disabled = disabled;
35940 self.refreshView();
35944 $scope.isActive = function(dateObject) {
35945 if (self.compare(dateObject.date, self.activeDate) === 0) {
35946 $scope.activeDateId = dateObject.uid;
35952 this.init = function(ngModelCtrl_) {
35953 ngModelCtrl = ngModelCtrl_;
35954 ngModelOptions = ngModelCtrl_.$options || datepickerConfig.ngModelOptions;
35956 if (ngModelCtrl.$modelValue) {
35957 this.activeDate = ngModelCtrl.$modelValue;
35960 ngModelCtrl.$render = function() {
35965 this.render = function() {
35966 if (ngModelCtrl.$viewValue) {
35967 var date = new Date(ngModelCtrl.$viewValue),
35968 isValid = !isNaN(date);
35971 this.activeDate = dateParser.fromTimezone(date, ngModelOptions.timezone);
35972 } else if (!$datepickerSuppressError) {
35973 $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.');
35976 this.refreshView();
35979 this.refreshView = function() {
35980 if (this.element) {
35981 $scope.selectedDt = null;
35982 this._refreshView();
35983 if ($scope.activeDt) {
35984 $scope.activeDateId = $scope.activeDt.uid;
35987 var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
35988 date = dateParser.fromTimezone(date, ngModelOptions.timezone);
35989 ngModelCtrl.$setValidity('dateDisabled', !date ||
35990 this.element && !this.isDisabled(date));
35994 this.createDateObject = function(date, format) {
35995 var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
35996 model = dateParser.fromTimezone(model, ngModelOptions.timezone);
35999 label: dateFilter(date, format),
36000 selected: model && this.compare(date, model) === 0,
36001 disabled: this.isDisabled(date),
36002 current: this.compare(date, new Date()) === 0,
36003 customClass: this.customClass(date) || null
36006 if (model && this.compare(date, model) === 0) {
36007 $scope.selectedDt = dt;
36010 if (self.activeDate && this.compare(dt.date, self.activeDate) === 0) {
36011 $scope.activeDt = dt;
36017 this.isDisabled = function(date) {
36018 return $scope.disabled ||
36019 this.minDate && this.compare(date, this.minDate) < 0 ||
36020 this.maxDate && this.compare(date, this.maxDate) > 0 ||
36021 $attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode});
36024 this.customClass = function(date) {
36025 return $scope.customClass({date: date, mode: $scope.datepickerMode});
36028 // Split array into smaller arrays
36029 this.split = function(arr, size) {
36031 while (arr.length > 0) {
36032 arrays.push(arr.splice(0, size));
36037 $scope.select = function(date) {
36038 if ($scope.datepickerMode === self.minMode) {
36039 var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.timezone) : new Date(0, 0, 0, 0, 0, 0, 0);
36040 dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
36041 dt = dateParser.toTimezone(dt, ngModelOptions.timezone);
36042 ngModelCtrl.$setViewValue(dt);
36043 ngModelCtrl.$render();
36045 self.activeDate = date;
36046 $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) - 1];
36050 $scope.move = function(direction) {
36051 var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
36052 month = self.activeDate.getMonth() + direction * (self.step.months || 0);
36053 self.activeDate.setFullYear(year, month, 1);
36054 self.refreshView();
36057 $scope.toggleMode = function(direction) {
36058 direction = direction || 1;
36060 if ($scope.datepickerMode === self.maxMode && direction === 1 ||
36061 $scope.datepickerMode === self.minMode && direction === -1) {
36065 $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) + direction];
36068 // Key event mapper
36069 $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' };
36071 var focusElement = function() {
36072 self.element[0].focus();
36075 // Listen for focus requests from popup directive
36076 $scope.$on('uib:datepicker.focus', focusElement);
36078 $scope.keydown = function(evt) {
36079 var key = $scope.keys[evt.which];
36081 if (!key || evt.shiftKey || evt.altKey || $scope.disabled) {
36085 evt.preventDefault();
36086 if (!self.shortcutPropagation) {
36087 evt.stopPropagation();
36090 if (key === 'enter' || key === 'space') {
36091 if (self.isDisabled(self.activeDate)) {
36092 return; // do nothing
36094 $scope.select(self.activeDate);
36095 } else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
36096 $scope.toggleMode(key === 'up' ? 1 : -1);
36098 self.handleKeyDown(key, evt);
36099 self.refreshView();
36104 .controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
36105 var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
36107 this.step = { months: 1 };
36108 this.element = $element;
36109 function getDaysInMonth(year, month) {
36110 return month === 1 && year % 4 === 0 &&
36111 (year % 100 !== 0 || year % 400 === 0) ? 29 : DAYS_IN_MONTH[month];
36114 this.init = function(ctrl) {
36115 angular.extend(ctrl, this);
36116 scope.showWeeks = ctrl.showWeeks;
36117 ctrl.refreshView();
36120 this.getDates = function(startDate, n) {
36121 var dates = new Array(n), current = new Date(startDate), i = 0, date;
36123 date = new Date(current);
36125 current.setDate(current.getDate() + 1);
36130 this._refreshView = function() {
36131 var year = this.activeDate.getFullYear(),
36132 month = this.activeDate.getMonth(),
36133 firstDayOfMonth = new Date(this.activeDate);
36135 firstDayOfMonth.setFullYear(year, month, 1);
36137 var difference = this.startingDay - firstDayOfMonth.getDay(),
36138 numDisplayedFromPreviousMonth = difference > 0 ?
36139 7 - difference : - difference,
36140 firstDate = new Date(firstDayOfMonth);
36142 if (numDisplayedFromPreviousMonth > 0) {
36143 firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
36146 // 42 is the number of days on a six-week calendar
36147 var days = this.getDates(firstDate, 42);
36148 for (var i = 0; i < 42; i ++) {
36149 days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), {
36150 secondary: days[i].getMonth() !== month,
36151 uid: scope.uniqueId + '-' + i
36155 scope.labels = new Array(7);
36156 for (var j = 0; j < 7; j++) {
36157 scope.labels[j] = {
36158 abbr: dateFilter(days[j].date, this.formatDayHeader),
36159 full: dateFilter(days[j].date, 'EEEE')
36163 scope.title = dateFilter(this.activeDate, this.formatDayTitle);
36164 scope.rows = this.split(days, 7);
36166 if (scope.showWeeks) {
36167 scope.weekNumbers = [];
36168 var thursdayIndex = (4 + 7 - this.startingDay) % 7,
36169 numWeeks = scope.rows.length;
36170 for (var curWeek = 0; curWeek < numWeeks; curWeek++) {
36171 scope.weekNumbers.push(
36172 getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date));
36177 this.compare = function(date1, date2) {
36178 var _date1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate());
36179 var _date2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
36180 _date1.setFullYear(date1.getFullYear());
36181 _date2.setFullYear(date2.getFullYear());
36182 return _date1 - _date2;
36185 function getISO8601WeekNumber(date) {
36186 var checkDate = new Date(date);
36187 checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
36188 var time = checkDate.getTime();
36189 checkDate.setMonth(0); // Compare with Jan 1
36190 checkDate.setDate(1);
36191 return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
36194 this.handleKeyDown = function(key, evt) {
36195 var date = this.activeDate.getDate();
36197 if (key === 'left') {
36199 } else if (key === 'up') {
36201 } else if (key === 'right') {
36203 } else if (key === 'down') {
36205 } else if (key === 'pageup' || key === 'pagedown') {
36206 var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1);
36207 this.activeDate.setMonth(month, 1);
36208 date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date);
36209 } else if (key === 'home') {
36211 } else if (key === 'end') {
36212 date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth());
36214 this.activeDate.setDate(date);
36218 .controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
36219 this.step = { years: 1 };
36220 this.element = $element;
36222 this.init = function(ctrl) {
36223 angular.extend(ctrl, this);
36224 ctrl.refreshView();
36227 this._refreshView = function() {
36228 var months = new Array(12),
36229 year = this.activeDate.getFullYear(),
36232 for (var i = 0; i < 12; i++) {
36233 date = new Date(this.activeDate);
36234 date.setFullYear(year, i, 1);
36235 months[i] = angular.extend(this.createDateObject(date, this.formatMonth), {
36236 uid: scope.uniqueId + '-' + i
36240 scope.title = dateFilter(this.activeDate, this.formatMonthTitle);
36241 scope.rows = this.split(months, 3);
36244 this.compare = function(date1, date2) {
36245 var _date1 = new Date(date1.getFullYear(), date1.getMonth());
36246 var _date2 = new Date(date2.getFullYear(), date2.getMonth());
36247 _date1.setFullYear(date1.getFullYear());
36248 _date2.setFullYear(date2.getFullYear());
36249 return _date1 - _date2;
36252 this.handleKeyDown = function(key, evt) {
36253 var date = this.activeDate.getMonth();
36255 if (key === 'left') {
36257 } else if (key === 'up') {
36259 } else if (key === 'right') {
36261 } else if (key === 'down') {
36263 } else if (key === 'pageup' || key === 'pagedown') {
36264 var year = this.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1);
36265 this.activeDate.setFullYear(year);
36266 } else if (key === 'home') {
36268 } else if (key === 'end') {
36271 this.activeDate.setMonth(date);
36275 .controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
36276 var columns, range;
36277 this.element = $element;
36279 function getStartingYear(year) {
36280 return parseInt((year - 1) / range, 10) * range + 1;
36283 this.yearpickerInit = function() {
36284 columns = this.yearColumns;
36285 range = this.yearRows * columns;
36286 this.step = { years: range };
36289 this._refreshView = function() {
36290 var years = new Array(range), date;
36292 for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) {
36293 date = new Date(this.activeDate);
36294 date.setFullYear(start + i, 0, 1);
36295 years[i] = angular.extend(this.createDateObject(date, this.formatYear), {
36296 uid: scope.uniqueId + '-' + i
36300 scope.title = [years[0].label, years[range - 1].label].join(' - ');
36301 scope.rows = this.split(years, columns);
36302 scope.columns = columns;
36305 this.compare = function(date1, date2) {
36306 return date1.getFullYear() - date2.getFullYear();
36309 this.handleKeyDown = function(key, evt) {
36310 var date = this.activeDate.getFullYear();
36312 if (key === 'left') {
36314 } else if (key === 'up') {
36315 date = date - columns;
36316 } else if (key === 'right') {
36318 } else if (key === 'down') {
36319 date = date + columns;
36320 } else if (key === 'pageup' || key === 'pagedown') {
36321 date += (key === 'pageup' ? - 1 : 1) * range;
36322 } else if (key === 'home') {
36323 date = getStartingYear(this.activeDate.getFullYear());
36324 } else if (key === 'end') {
36325 date = getStartingYear(this.activeDate.getFullYear()) + range - 1;
36327 this.activeDate.setFullYear(date);
36331 .directive('uibDatepicker', function() {
36334 templateUrl: function(element, attrs) {
36335 return attrs.templateUrl || 'uib/template/datepicker/datepicker.html';
36338 datepickerMode: '=?',
36341 shortcutPropagation: '&?'
36343 require: ['uibDatepicker', '^ngModel'],
36344 controller: 'UibDatepickerController',
36345 controllerAs: 'datepicker',
36346 link: function(scope, element, attrs, ctrls) {
36347 var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
36349 datepickerCtrl.init(ngModelCtrl);
36354 .directive('uibDaypicker', function() {
36357 templateUrl: function(element, attrs) {
36358 return attrs.templateUrl || 'uib/template/datepicker/day.html';
36360 require: ['^uibDatepicker', 'uibDaypicker'],
36361 controller: 'UibDaypickerController',
36362 link: function(scope, element, attrs, ctrls) {
36363 var datepickerCtrl = ctrls[0],
36364 daypickerCtrl = ctrls[1];
36366 daypickerCtrl.init(datepickerCtrl);
36371 .directive('uibMonthpicker', function() {
36374 templateUrl: function(element, attrs) {
36375 return attrs.templateUrl || 'uib/template/datepicker/month.html';
36377 require: ['^uibDatepicker', 'uibMonthpicker'],
36378 controller: 'UibMonthpickerController',
36379 link: function(scope, element, attrs, ctrls) {
36380 var datepickerCtrl = ctrls[0],
36381 monthpickerCtrl = ctrls[1];
36383 monthpickerCtrl.init(datepickerCtrl);
36388 .directive('uibYearpicker', function() {
36391 templateUrl: function(element, attrs) {
36392 return attrs.templateUrl || 'uib/template/datepicker/year.html';
36394 require: ['^uibDatepicker', 'uibYearpicker'],
36395 controller: 'UibYearpickerController',
36396 link: function(scope, element, attrs, ctrls) {
36397 var ctrl = ctrls[0];
36398 angular.extend(ctrl, ctrls[1]);
36399 ctrl.yearpickerInit();
36401 ctrl.refreshView();
36406 .constant('uibDatepickerPopupConfig', {
36407 datepickerPopup: 'yyyy-MM-dd',
36408 datepickerPopupTemplateUrl: 'uib/template/datepicker/popup.html',
36409 datepickerTemplateUrl: 'uib/template/datepicker/datepicker.html',
36411 date: 'yyyy-MM-dd',
36412 'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',
36415 currentText: 'Today',
36416 clearText: 'Clear',
36418 closeOnDateSelection: true,
36419 appendToBody: false,
36420 showButtonBar: true,
36422 altInputFormats: []
36425 .controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig',
36426 function(scope, element, attrs, $compile, $parse, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig) {
36429 isHtml5DateInput = false;
36430 var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
36431 datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl,
36432 ngModel, ngModelOptions, $popup, altInputFormats;
36434 scope.watchData = {};
36436 this.init = function(_ngModel_) {
36437 ngModel = _ngModel_;
36438 ngModelOptions = _ngModel_.$options || datepickerConfig.ngModelOptions;
36439 closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection;
36440 appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody;
36441 onOpenFocus = angular.isDefined(attrs.onOpenFocus) ? scope.$parent.$eval(attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus;
36442 datepickerPopupTemplateUrl = angular.isDefined(attrs.datepickerPopupTemplateUrl) ? attrs.datepickerPopupTemplateUrl : datepickerPopupConfig.datepickerPopupTemplateUrl;
36443 datepickerTemplateUrl = angular.isDefined(attrs.datepickerTemplateUrl) ? attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl;
36444 altInputFormats = angular.isDefined(attrs.altInputFormats) ? scope.$parent.$eval(attrs.altInputFormats) : datepickerPopupConfig.altInputFormats;
36446 scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar;
36448 if (datepickerPopupConfig.html5Types[attrs.type]) {
36449 dateFormat = datepickerPopupConfig.html5Types[attrs.type];
36450 isHtml5DateInput = true;
36452 dateFormat = attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup;
36453 attrs.$observe('uibDatepickerPopup', function(value, oldValue) {
36454 var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
36455 // Invalidate the $modelValue to ensure that formatters re-run
36456 // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
36457 if (newDateFormat !== dateFormat) {
36458 dateFormat = newDateFormat;
36459 ngModel.$modelValue = null;
36462 throw new Error('uibDatepickerPopup must have a date format specified.');
36469 throw new Error('uibDatepickerPopup must have a date format specified.');
36472 if (isHtml5DateInput && attrs.uibDatepickerPopup) {
36473 throw new Error('HTML5 date input types do not support custom formats.');
36476 // popup element used to display calendar
36477 popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>');
36478 scope.ngModelOptions = angular.copy(ngModelOptions);
36479 scope.ngModelOptions.timezone = null;
36481 'ng-model': 'date',
36482 'ng-model-options': 'ngModelOptions',
36483 'ng-change': 'dateSelection(date)',
36484 'template-url': datepickerPopupTemplateUrl
36487 // datepicker element
36488 datepickerEl = angular.element(popupEl.children()[0]);
36489 datepickerEl.attr('template-url', datepickerTemplateUrl);
36491 if (isHtml5DateInput) {
36492 if (attrs.type === 'month') {
36493 datepickerEl.attr('datepicker-mode', '"month"');
36494 datepickerEl.attr('min-mode', 'month');
36498 if (attrs.datepickerOptions) {
36499 var options = scope.$parent.$eval(attrs.datepickerOptions);
36500 if (options && options.initDate) {
36501 scope.initDate = dateParser.fromTimezone(options.initDate, ngModelOptions.timezone);
36502 datepickerEl.attr('init-date', 'initDate');
36503 delete options.initDate;
36505 angular.forEach(options, function(value, option) {
36506 datepickerEl.attr(cameltoDash(option), value);
36510 angular.forEach(['minMode', 'maxMode'], function(key) {
36512 scope.$parent.$watch(function() { return attrs[key]; }, function(value) {
36513 scope.watchData[key] = value;
36515 datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
36519 angular.forEach(['datepickerMode', 'shortcutPropagation'], function(key) {
36521 var getAttribute = $parse(attrs[key]);
36524 return getAttribute(scope.$parent);
36528 datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
36530 // Propagate changes from datepicker to outside
36531 if (key === 'datepickerMode') {
36532 var setAttribute = getAttribute.assign;
36533 propConfig.set = function(v) {
36534 setAttribute(scope.$parent, v);
36538 Object.defineProperty(scope.watchData, key, propConfig);
36542 angular.forEach(['minDate', 'maxDate', 'initDate'], function(key) {
36544 var getAttribute = $parse(attrs[key]);
36546 scope.$parent.$watch(getAttribute, function(value) {
36547 if (key === 'minDate' || key === 'maxDate') {
36548 cache[key] = angular.isDate(value) ? dateParser.fromTimezone(new Date(value), ngModelOptions.timezone) : new Date(dateFilter(value, 'medium'));
36551 scope.watchData[key] = cache[key] || dateParser.fromTimezone(new Date(value), ngModelOptions.timezone);
36554 datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
36558 if (attrs.dateDisabled) {
36559 datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })');
36562 angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle', 'showWeeks', 'startingDay', 'yearRows', 'yearColumns'], function(key) {
36563 if (angular.isDefined(attrs[key])) {
36564 datepickerEl.attr(cameltoDash(key), attrs[key]);
36568 if (attrs.customClass) {
36569 datepickerEl.attr('custom-class', 'customClass({ date: date, mode: mode })');
36572 if (!isHtml5DateInput) {
36573 // Internal API to maintain the correct ng-invalid-[key] class
36574 ngModel.$$parserName = 'date';
36575 ngModel.$validators.date = validator;
36576 ngModel.$parsers.unshift(parseDate);
36577 ngModel.$formatters.push(function(value) {
36578 if (ngModel.$isEmpty(value)) {
36579 scope.date = value;
36582 scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);
36583 return dateFilter(scope.date, dateFormat);
36586 ngModel.$formatters.push(function(value) {
36587 scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);
36592 // Detect changes in the view from the text box
36593 ngModel.$viewChangeListeners.push(function() {
36594 scope.date = parseDateString(ngModel.$viewValue);
36597 element.bind('keydown', inputKeydownBind);
36599 $popup = $compile(popupEl)(scope);
36600 // Prevent jQuery cache memory leak (template is now redundant after linking)
36603 if (appendToBody) {
36604 $document.find('body').append($popup);
36606 element.after($popup);
36609 scope.$on('$destroy', function() {
36610 if (scope.isOpen === true) {
36611 if (!$rootScope.$$phase) {
36612 scope.$apply(function() {
36613 scope.isOpen = false;
36619 element.unbind('keydown', inputKeydownBind);
36620 $document.unbind('click', documentClickBind);
36624 scope.getText = function(key) {
36625 return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
36628 scope.isDisabled = function(date) {
36629 if (date === 'today') {
36633 return scope.watchData.minDate && scope.compare(date, cache.minDate) < 0 ||
36634 scope.watchData.maxDate && scope.compare(date, cache.maxDate) > 0;
36637 scope.compare = function(date1, date2) {
36638 return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
36642 scope.dateSelection = function(dt) {
36643 if (angular.isDefined(dt)) {
36646 var date = scope.date ? dateFilter(scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function
36648 ngModel.$setViewValue(date);
36650 if (closeOnDateSelection) {
36651 scope.isOpen = false;
36652 element[0].focus();
36656 scope.keydown = function(evt) {
36657 if (evt.which === 27) {
36658 evt.stopPropagation();
36659 scope.isOpen = false;
36660 element[0].focus();
36664 scope.select = function(date) {
36665 if (date === 'today') {
36666 var today = new Date();
36667 if (angular.isDate(scope.date)) {
36668 date = new Date(scope.date);
36669 date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
36671 date = new Date(today.setHours(0, 0, 0, 0));
36674 scope.dateSelection(date);
36677 scope.close = function() {
36678 scope.isOpen = false;
36679 element[0].focus();
36682 scope.disabled = angular.isDefined(attrs.disabled) || false;
36683 if (attrs.ngDisabled) {
36684 scope.$parent.$watch($parse(attrs.ngDisabled), function(disabled) {
36685 scope.disabled = disabled;
36689 scope.$watch('isOpen', function(value) {
36691 if (!scope.disabled) {
36692 scope.position = appendToBody ? $position.offset(element) : $position.position(element);
36693 scope.position.top = scope.position.top + element.prop('offsetHeight');
36695 $timeout(function() {
36697 scope.$broadcast('uib:datepicker.focus');
36699 $document.bind('click', documentClickBind);
36702 scope.isOpen = false;
36705 $document.unbind('click', documentClickBind);
36709 function cameltoDash(string) {
36710 return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
36713 function parseDateString(viewValue) {
36714 var date = dateParser.parse(viewValue, dateFormat, scope.date);
36716 for (var i = 0; i < altInputFormats.length; i++) {
36717 date = dateParser.parse(viewValue, altInputFormats[i], scope.date);
36718 if (!isNaN(date)) {
36726 function parseDate(viewValue) {
36727 if (angular.isNumber(viewValue)) {
36728 // presumably timestamp to date object
36729 viewValue = new Date(viewValue);
36736 if (angular.isDate(viewValue) && !isNaN(viewValue)) {
36740 if (angular.isString(viewValue)) {
36741 var date = parseDateString(viewValue);
36742 if (!isNaN(date)) {
36743 return dateParser.toTimezone(date, ngModelOptions.timezone);
36747 return ngModel.$options && ngModel.$options.allowInvalid ? viewValue : undefined;
36750 function validator(modelValue, viewValue) {
36751 var value = modelValue || viewValue;
36753 if (!attrs.ngRequired && !value) {
36757 if (angular.isNumber(value)) {
36758 value = new Date(value);
36765 if (angular.isDate(value) && !isNaN(value)) {
36769 if (angular.isString(value)) {
36770 return !isNaN(parseDateString(viewValue));
36776 function documentClickBind(event) {
36777 if (!scope.isOpen && scope.disabled) {
36781 var popup = $popup[0];
36782 var dpContainsTarget = element[0].contains(event.target);
36783 // The popup node may not be an element node
36784 // In some browsers (IE) only element nodes have the 'contains' function
36785 var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target);
36786 if (scope.isOpen && !(dpContainsTarget || popupContainsTarget)) {
36787 scope.$apply(function() {
36788 scope.isOpen = false;
36793 function inputKeydownBind(evt) {
36794 if (evt.which === 27 && scope.isOpen) {
36795 evt.preventDefault();
36796 evt.stopPropagation();
36797 scope.$apply(function() {
36798 scope.isOpen = false;
36800 element[0].focus();
36801 } else if (evt.which === 40 && !scope.isOpen) {
36802 evt.preventDefault();
36803 evt.stopPropagation();
36804 scope.$apply(function() {
36805 scope.isOpen = true;
36811 .directive('uibDatepickerPopup', function() {
36813 require: ['ngModel', 'uibDatepickerPopup'],
36814 controller: 'UibDatepickerPopupController',
36823 link: function(scope, element, attrs, ctrls) {
36824 var ngModel = ctrls[0],
36827 ctrl.init(ngModel);
36832 .directive('uibDatepickerPopupWrap', function() {
36836 templateUrl: function(element, attrs) {
36837 return attrs.templateUrl || 'uib/template/datepicker/popup.html';
36842 angular.module('ui.bootstrap.debounce', [])
36844 * A helper, internal service that debounces a function
36846 .factory('$$debounce', ['$timeout', function($timeout) {
36847 return function(callback, debounceTime) {
36848 var timeoutPromise;
36850 return function() {
36852 var args = Array.prototype.slice.call(arguments);
36853 if (timeoutPromise) {
36854 $timeout.cancel(timeoutPromise);
36857 timeoutPromise = $timeout(function() {
36858 callback.apply(self, args);
36864 angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
36866 .constant('uibDropdownConfig', {
36867 appendToOpenClass: 'uib-dropdown-open',
36871 .service('uibDropdownService', ['$document', '$rootScope', function($document, $rootScope) {
36872 var openScope = null;
36874 this.open = function(dropdownScope) {
36876 $document.on('click', closeDropdown);
36877 $document.on('keydown', keybindFilter);
36880 if (openScope && openScope !== dropdownScope) {
36881 openScope.isOpen = false;
36884 openScope = dropdownScope;
36887 this.close = function(dropdownScope) {
36888 if (openScope === dropdownScope) {
36890 $document.off('click', closeDropdown);
36891 $document.off('keydown', keybindFilter);
36895 var closeDropdown = function(evt) {
36896 // This method may still be called during the same mouse event that
36897 // unbound this event handler. So check openScope before proceeding.
36898 if (!openScope) { return; }
36900 if (evt && openScope.getAutoClose() === 'disabled') { return; }
36902 if (evt && evt.which === 3) { return; }
36904 var toggleElement = openScope.getToggleElement();
36905 if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
36909 var dropdownElement = openScope.getDropdownElement();
36910 if (evt && openScope.getAutoClose() === 'outsideClick' &&
36911 dropdownElement && dropdownElement[0].contains(evt.target)) {
36915 openScope.isOpen = false;
36917 if (!$rootScope.$$phase) {
36918 openScope.$apply();
36922 var keybindFilter = function(evt) {
36923 if (evt.which === 27) {
36924 openScope.focusToggleElement();
36926 } else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen) {
36927 evt.preventDefault();
36928 evt.stopPropagation();
36929 openScope.focusDropdownEntry(evt.which);
36934 .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) {
36936 scope = $scope.$new(), // create a child scope so we are not polluting original one
36938 appendToOpenClass = dropdownConfig.appendToOpenClass,
36939 openClass = dropdownConfig.openClass,
36941 setIsOpen = angular.noop,
36942 toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
36943 appendToBody = false,
36945 keynavEnabled = false,
36946 selectedOption = null,
36947 body = $document.find('body');
36949 $element.addClass('dropdown');
36951 this.init = function() {
36952 if ($attrs.isOpen) {
36953 getIsOpen = $parse($attrs.isOpen);
36954 setIsOpen = getIsOpen.assign;
36956 $scope.$watch(getIsOpen, function(value) {
36957 scope.isOpen = !!value;
36961 if (angular.isDefined($attrs.dropdownAppendTo)) {
36962 var appendToEl = $parse($attrs.dropdownAppendTo)(scope);
36964 appendTo = angular.element(appendToEl);
36968 appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
36969 keynavEnabled = angular.isDefined($attrs.keyboardNav);
36971 if (appendToBody && !appendTo) {
36975 if (appendTo && self.dropdownMenu) {
36976 appendTo.append(self.dropdownMenu);
36977 $element.on('$destroy', function handleDestroyEvent() {
36978 self.dropdownMenu.remove();
36983 this.toggle = function(open) {
36984 return scope.isOpen = arguments.length ? !!open : !scope.isOpen;
36987 // Allow other directives to watch status
36988 this.isOpen = function() {
36989 return scope.isOpen;
36992 scope.getToggleElement = function() {
36993 return self.toggleElement;
36996 scope.getAutoClose = function() {
36997 return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled'
37000 scope.getElement = function() {
37004 scope.isKeynavEnabled = function() {
37005 return keynavEnabled;
37008 scope.focusDropdownEntry = function(keyCode) {
37009 var elems = self.dropdownMenu ? //If append to body is used.
37010 angular.element(self.dropdownMenu).find('a') :
37011 $element.find('ul').eq(0).find('a');
37015 if (!angular.isNumber(self.selectedOption)) {
37016 self.selectedOption = 0;
37018 self.selectedOption = self.selectedOption === elems.length - 1 ?
37019 self.selectedOption :
37020 self.selectedOption + 1;
37025 if (!angular.isNumber(self.selectedOption)) {
37026 self.selectedOption = elems.length - 1;
37028 self.selectedOption = self.selectedOption === 0 ?
37029 0 : self.selectedOption - 1;
37034 elems[self.selectedOption].focus();
37037 scope.getDropdownElement = function() {
37038 return self.dropdownMenu;
37041 scope.focusToggleElement = function() {
37042 if (self.toggleElement) {
37043 self.toggleElement[0].focus();
37047 scope.$watch('isOpen', function(isOpen, wasOpen) {
37048 if (appendTo && self.dropdownMenu) {
37049 var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true),
37054 top: pos.top + 'px',
37055 display: isOpen ? 'block' : 'none'
37058 rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
37060 css.left = pos.left + 'px';
37061 css.right = 'auto';
37064 css.right = window.innerWidth -
37065 (pos.left + $element.prop('offsetWidth')) + 'px';
37068 // Need to adjust our positioning to be relative to the appendTo container
37069 // if it's not the body element
37070 if (!appendToBody) {
37071 var appendOffset = $position.offset(appendTo);
37073 css.top = pos.top - appendOffset.top + 'px';
37076 css.left = pos.left - appendOffset.left + 'px';
37078 css.right = window.innerWidth -
37079 (pos.left - appendOffset.left + $element.prop('offsetWidth')) + 'px';
37083 self.dropdownMenu.css(css);
37086 var openContainer = appendTo ? appendTo : $element;
37088 $animate[isOpen ? 'addClass' : 'removeClass'](openContainer, appendTo ? appendToOpenClass : openClass).then(function() {
37089 if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
37090 toggleInvoker($scope, { open: !!isOpen });
37095 if (self.dropdownMenuTemplateUrl) {
37096 $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) {
37097 templateScope = scope.$new();
37098 $compile(tplContent.trim())(templateScope, function(dropdownElement) {
37099 var newEl = dropdownElement;
37100 self.dropdownMenu.replaceWith(newEl);
37101 self.dropdownMenu = newEl;
37106 scope.focusToggleElement();
37107 uibDropdownService.open(scope);
37109 if (self.dropdownMenuTemplateUrl) {
37110 if (templateScope) {
37111 templateScope.$destroy();
37113 var newEl = angular.element('<ul class="dropdown-menu"></ul>');
37114 self.dropdownMenu.replaceWith(newEl);
37115 self.dropdownMenu = newEl;
37118 uibDropdownService.close(scope);
37119 self.selectedOption = null;
37122 if (angular.isFunction(setIsOpen)) {
37123 setIsOpen($scope, isOpen);
37127 $scope.$on('$locationChangeSuccess', function() {
37128 if (scope.getAutoClose() !== 'disabled') {
37129 scope.isOpen = false;
37134 .directive('uibDropdown', function() {
37136 controller: 'UibDropdownController',
37137 link: function(scope, element, attrs, dropdownCtrl) {
37138 dropdownCtrl.init();
37143 .directive('uibDropdownMenu', function() {
37146 require: '?^uibDropdown',
37147 link: function(scope, element, attrs, dropdownCtrl) {
37148 if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {
37152 element.addClass('dropdown-menu');
37154 var tplUrl = attrs.templateUrl;
37156 dropdownCtrl.dropdownMenuTemplateUrl = tplUrl;
37159 if (!dropdownCtrl.dropdownMenu) {
37160 dropdownCtrl.dropdownMenu = element;
37166 .directive('uibDropdownToggle', function() {
37168 require: '?^uibDropdown',
37169 link: function(scope, element, attrs, dropdownCtrl) {
37170 if (!dropdownCtrl) {
37174 element.addClass('dropdown-toggle');
37176 dropdownCtrl.toggleElement = element;
37178 var toggleDropdown = function(event) {
37179 event.preventDefault();
37181 if (!element.hasClass('disabled') && !attrs.disabled) {
37182 scope.$apply(function() {
37183 dropdownCtrl.toggle();
37188 element.bind('click', toggleDropdown);
37191 element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
37192 scope.$watch(dropdownCtrl.isOpen, function(isOpen) {
37193 element.attr('aria-expanded', !!isOpen);
37196 scope.$on('$destroy', function() {
37197 element.unbind('click', toggleDropdown);
37203 angular.module('ui.bootstrap.stackedMap', [])
37205 * A helper, internal data structure that acts as a map but also allows getting / removing
37206 * elements in the LIFO order
37208 .factory('$$stackedMap', function() {
37210 createNew: function() {
37214 add: function(key, value) {
37220 get: function(key) {
37221 for (var i = 0; i < stack.length; i++) {
37222 if (key === stack[i].key) {
37229 for (var i = 0; i < stack.length; i++) {
37230 keys.push(stack[i].key);
37235 return stack[stack.length - 1];
37237 remove: function(key) {
37239 for (var i = 0; i < stack.length; i++) {
37240 if (key === stack[i].key) {
37245 return stack.splice(idx, 1)[0];
37247 removeTop: function() {
37248 return stack.splice(stack.length - 1, 1)[0];
37250 length: function() {
37251 return stack.length;
37257 angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
37259 * A helper, internal data structure that stores all references attached to key
37261 .factory('$$multiMap', function() {
37263 createNew: function() {
37267 entries: function() {
37268 return Object.keys(map).map(function(key) {
37275 get: function(key) {
37278 hasKey: function(key) {
37282 return Object.keys(map);
37284 put: function(key, value) {
37289 map[key].push(value);
37291 remove: function(key, value) {
37292 var values = map[key];
37298 var idx = values.indexOf(value);
37301 values.splice(idx, 1);
37304 if (!values.length) {
37314 * Pluggable resolve mechanism for the modal resolve resolution
37315 * Supports UI Router's $resolve service
37317 .provider('$uibResolve', function() {
37318 var resolve = this;
37319 this.resolver = null;
37321 this.setResolver = function(resolver) {
37322 this.resolver = resolver;
37325 this.$get = ['$injector', '$q', function($injector, $q) {
37326 var resolver = resolve.resolver ? $injector.get(resolve.resolver) : null;
37328 resolve: function(invocables, locals, parent, self) {
37330 return resolver.resolve(invocables, locals, parent, self);
37335 angular.forEach(invocables, function(value) {
37336 if (angular.isFunction(value) || angular.isArray(value)) {
37337 promises.push($q.resolve($injector.invoke(value)));
37338 } else if (angular.isString(value)) {
37339 promises.push($q.resolve($injector.get(value)));
37341 promises.push($q.resolve(value));
37345 return $q.all(promises).then(function(resolves) {
37346 var resolveObj = {};
37347 var resolveIter = 0;
37348 angular.forEach(invocables, function(value, key) {
37349 resolveObj[key] = resolves[resolveIter++];
37360 * A helper directive for the $modal service. It creates a backdrop element.
37362 .directive('uibModalBackdrop', ['$animateCss', '$injector', '$uibModalStack',
37363 function($animateCss, $injector, $modalStack) {
37366 templateUrl: 'uib/template/modal/backdrop.html',
37367 compile: function(tElement, tAttrs) {
37368 tElement.addClass(tAttrs.backdropClass);
37373 function linkFn(scope, element, attrs) {
37374 if (attrs.modalInClass) {
37375 $animateCss(element, {
37376 addClass: attrs.modalInClass
37379 scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
37380 var done = setIsAsync();
37381 if (scope.modalOptions.animation) {
37382 $animateCss(element, {
37383 removeClass: attrs.modalInClass
37384 }).start().then(done);
37393 .directive('uibModalWindow', ['$uibModalStack', '$q', '$animate', '$animateCss', '$document',
37394 function($modalStack, $q, $animate, $animateCss, $document) {
37401 templateUrl: function(tElement, tAttrs) {
37402 return tAttrs.templateUrl || 'uib/template/modal/window.html';
37404 link: function(scope, element, attrs) {
37405 element.addClass(attrs.windowClass || '');
37406 element.addClass(attrs.windowTopClass || '');
37407 scope.size = attrs.size;
37409 scope.close = function(evt) {
37410 var modal = $modalStack.getTop();
37411 if (modal && modal.value.backdrop &&
37412 modal.value.backdrop !== 'static' &&
37413 evt.target === evt.currentTarget) {
37414 evt.preventDefault();
37415 evt.stopPropagation();
37416 $modalStack.dismiss(modal.key, 'backdrop click');
37420 // moved from template to fix issue #2280
37421 element.on('click', scope.close);
37423 // This property is only added to the scope for the purpose of detecting when this directive is rendered.
37424 // We can detect that by using this property in the template associated with this directive and then use
37425 // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
37426 scope.$isRendered = true;
37428 // Deferred object that will be resolved when this modal is render.
37429 var modalRenderDeferObj = $q.defer();
37430 // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready.
37431 // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template.
37432 attrs.$observe('modalRender', function(value) {
37433 if (value === 'true') {
37434 modalRenderDeferObj.resolve();
37438 modalRenderDeferObj.promise.then(function() {
37439 var animationPromise = null;
37441 if (attrs.modalInClass) {
37442 animationPromise = $animateCss(element, {
37443 addClass: attrs.modalInClass
37446 scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
37447 var done = setIsAsync();
37449 $animateCss(element, {
37450 removeClass: attrs.modalInClass
37451 }).start().then(done);
37453 $animate.removeClass(element, attrs.modalInClass).then(done);
37459 $q.when(animationPromise).then(function() {
37461 * If something within the freshly-opened modal already has focus (perhaps via a
37462 * directive that causes focus). then no need to try and focus anything.
37464 if (!($document[0].activeElement && element[0].contains($document[0].activeElement))) {
37465 var inputWithAutofocus = element[0].querySelector('[autofocus]');
37467 * Auto-focusing of a freshly-opened modal element causes any child elements
37468 * with the autofocus attribute to lose focus. This is an issue on touch
37469 * based devices which will show and then hide the onscreen keyboard.
37470 * Attempts to refocus the autofocus element via JavaScript will not reopen
37471 * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
37472 * the modal element if the modal does not contain an autofocus element.
37474 if (inputWithAutofocus) {
37475 inputWithAutofocus.focus();
37477 element[0].focus();
37482 // Notify {@link $modalStack} that modal is rendered.
37483 var modal = $modalStack.getTop();
37485 $modalStack.modalRendered(modal.key);
37492 .directive('uibModalAnimationClass', function() {
37494 compile: function(tElement, tAttrs) {
37495 if (tAttrs.modalAnimation) {
37496 tElement.addClass(tAttrs.uibModalAnimationClass);
37502 .directive('uibModalTransclude', function() {
37504 link: function(scope, element, attrs, controller, transclude) {
37505 transclude(scope.$parent, function(clone) {
37507 element.append(clone);
37513 .factory('$uibModalStack', ['$animate', '$animateCss', '$document',
37514 '$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap',
37515 function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap) {
37516 var OPENED_MODAL_CLASS = 'modal-open';
37518 var backdropDomEl, backdropScope;
37519 var openedWindows = $$stackedMap.createNew();
37520 var openedClasses = $$multiMap.createNew();
37521 var $modalStack = {
37522 NOW_CLOSING_EVENT: 'modal.stack.now-closing'
37525 //Modal focus behavior
37526 var focusableElementList;
37527 var focusIndex = 0;
37528 var tababbleSelector = 'a[href], area[href], input:not([disabled]), ' +
37529 'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' +
37530 'iframe, object, embed, *[tabindex], *[contenteditable=true]';
37532 function backdropIndex() {
37533 var topBackdropIndex = -1;
37534 var opened = openedWindows.keys();
37535 for (var i = 0; i < opened.length; i++) {
37536 if (openedWindows.get(opened[i]).value.backdrop) {
37537 topBackdropIndex = i;
37540 return topBackdropIndex;
37543 $rootScope.$watch(backdropIndex, function(newBackdropIndex) {
37544 if (backdropScope) {
37545 backdropScope.index = newBackdropIndex;
37549 function removeModalWindow(modalInstance, elementToReceiveFocus) {
37550 var modalWindow = openedWindows.get(modalInstance).value;
37551 var appendToElement = modalWindow.appendTo;
37553 //clean up the stack
37554 openedWindows.remove(modalInstance);
37556 removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() {
37557 var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS;
37558 openedClasses.remove(modalBodyClass, modalInstance);
37559 appendToElement.toggleClass(modalBodyClass, openedClasses.hasKey(modalBodyClass));
37560 toggleTopWindowClass(true);
37562 checkRemoveBackdrop();
37564 //move focus to specified element if available, or else to body
37565 if (elementToReceiveFocus && elementToReceiveFocus.focus) {
37566 elementToReceiveFocus.focus();
37567 } else if (appendToElement.focus) {
37568 appendToElement.focus();
37572 // Add or remove "windowTopClass" from the top window in the stack
37573 function toggleTopWindowClass(toggleSwitch) {
37576 if (openedWindows.length() > 0) {
37577 modalWindow = openedWindows.top().value;
37578 modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch);
37582 function checkRemoveBackdrop() {
37583 //remove backdrop if no longer needed
37584 if (backdropDomEl && backdropIndex() === -1) {
37585 var backdropScopeRef = backdropScope;
37586 removeAfterAnimate(backdropDomEl, backdropScope, function() {
37587 backdropScopeRef = null;
37589 backdropDomEl = undefined;
37590 backdropScope = undefined;
37594 function removeAfterAnimate(domEl, scope, done, closedDeferred) {
37596 var asyncPromise = null;
37597 var setIsAsync = function() {
37598 if (!asyncDeferred) {
37599 asyncDeferred = $q.defer();
37600 asyncPromise = asyncDeferred.promise;
37603 return function asyncDone() {
37604 asyncDeferred.resolve();
37607 scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync);
37609 // Note that it's intentional that asyncPromise might be null.
37610 // That's when setIsAsync has not been called during the
37611 // NOW_CLOSING_EVENT broadcast.
37612 return $q.when(asyncPromise).then(afterAnimating);
37614 function afterAnimating() {
37615 if (afterAnimating.done) {
37618 afterAnimating.done = true;
37620 $animateCss(domEl, {
37622 }).start().then(function() {
37624 if (closedDeferred) {
37625 closedDeferred.resolve();
37636 $document.on('keydown', keydownListener);
37638 $rootScope.$on('$destroy', function() {
37639 $document.off('keydown', keydownListener);
37642 function keydownListener(evt) {
37643 if (evt.isDefaultPrevented()) {
37647 var modal = openedWindows.top();
37649 switch (evt.which) {
37651 if (modal.value.keyboard) {
37652 evt.preventDefault();
37653 $rootScope.$apply(function() {
37654 $modalStack.dismiss(modal.key, 'escape key press');
37660 $modalStack.loadFocusElementList(modal);
37661 var focusChanged = false;
37662 if (evt.shiftKey) {
37663 if ($modalStack.isFocusInFirstItem(evt)) {
37664 focusChanged = $modalStack.focusLastFocusableElement();
37667 if ($modalStack.isFocusInLastItem(evt)) {
37668 focusChanged = $modalStack.focusFirstFocusableElement();
37672 if (focusChanged) {
37673 evt.preventDefault();
37674 evt.stopPropagation();
37682 $modalStack.open = function(modalInstance, modal) {
37683 var modalOpener = $document[0].activeElement,
37684 modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS;
37686 toggleTopWindowClass(false);
37688 openedWindows.add(modalInstance, {
37689 deferred: modal.deferred,
37690 renderDeferred: modal.renderDeferred,
37691 closedDeferred: modal.closedDeferred,
37692 modalScope: modal.scope,
37693 backdrop: modal.backdrop,
37694 keyboard: modal.keyboard,
37695 openedClass: modal.openedClass,
37696 windowTopClass: modal.windowTopClass,
37697 animation: modal.animation,
37698 appendTo: modal.appendTo
37701 openedClasses.put(modalBodyClass, modalInstance);
37703 var appendToElement = modal.appendTo,
37704 currBackdropIndex = backdropIndex();
37706 if (!appendToElement.length) {
37707 throw new Error('appendTo element not found. Make sure that the element passed is in DOM.');
37710 if (currBackdropIndex >= 0 && !backdropDomEl) {
37711 backdropScope = $rootScope.$new(true);
37712 backdropScope.modalOptions = modal;
37713 backdropScope.index = currBackdropIndex;
37714 backdropDomEl = angular.element('<div uib-modal-backdrop="modal-backdrop"></div>');
37715 backdropDomEl.attr('backdrop-class', modal.backdropClass);
37716 if (modal.animation) {
37717 backdropDomEl.attr('modal-animation', 'true');
37719 $compile(backdropDomEl)(backdropScope);
37720 $animate.enter(backdropDomEl, appendToElement);
37723 var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>');
37724 angularDomEl.attr({
37725 'template-url': modal.windowTemplateUrl,
37726 'window-class': modal.windowClass,
37727 'window-top-class': modal.windowTopClass,
37728 'size': modal.size,
37729 'index': openedWindows.length() - 1,
37730 'animate': 'animate'
37731 }).html(modal.content);
37732 if (modal.animation) {
37733 angularDomEl.attr('modal-animation', 'true');
37736 $animate.enter(angularDomEl, appendToElement)
37738 $compile(angularDomEl)(modal.scope);
37739 $animate.addClass(appendToElement, modalBodyClass);
37742 openedWindows.top().value.modalDomEl = angularDomEl;
37743 openedWindows.top().value.modalOpener = modalOpener;
37745 $modalStack.clearFocusListCache();
37748 function broadcastClosing(modalWindow, resultOrReason, closing) {
37749 return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented;
37752 $modalStack.close = function(modalInstance, result) {
37753 var modalWindow = openedWindows.get(modalInstance);
37754 if (modalWindow && broadcastClosing(modalWindow, result, true)) {
37755 modalWindow.value.modalScope.$$uibDestructionScheduled = true;
37756 modalWindow.value.deferred.resolve(result);
37757 removeModalWindow(modalInstance, modalWindow.value.modalOpener);
37760 return !modalWindow;
37763 $modalStack.dismiss = function(modalInstance, reason) {
37764 var modalWindow = openedWindows.get(modalInstance);
37765 if (modalWindow && broadcastClosing(modalWindow, reason, false)) {
37766 modalWindow.value.modalScope.$$uibDestructionScheduled = true;
37767 modalWindow.value.deferred.reject(reason);
37768 removeModalWindow(modalInstance, modalWindow.value.modalOpener);
37771 return !modalWindow;
37774 $modalStack.dismissAll = function(reason) {
37775 var topModal = this.getTop();
37776 while (topModal && this.dismiss(topModal.key, reason)) {
37777 topModal = this.getTop();
37781 $modalStack.getTop = function() {
37782 return openedWindows.top();
37785 $modalStack.modalRendered = function(modalInstance) {
37786 var modalWindow = openedWindows.get(modalInstance);
37788 modalWindow.value.renderDeferred.resolve();
37792 $modalStack.focusFirstFocusableElement = function() {
37793 if (focusableElementList.length > 0) {
37794 focusableElementList[0].focus();
37799 $modalStack.focusLastFocusableElement = function() {
37800 if (focusableElementList.length > 0) {
37801 focusableElementList[focusableElementList.length - 1].focus();
37807 $modalStack.isFocusInFirstItem = function(evt) {
37808 if (focusableElementList.length > 0) {
37809 return (evt.target || evt.srcElement) === focusableElementList[0];
37814 $modalStack.isFocusInLastItem = function(evt) {
37815 if (focusableElementList.length > 0) {
37816 return (evt.target || evt.srcElement) === focusableElementList[focusableElementList.length - 1];
37821 $modalStack.clearFocusListCache = function() {
37822 focusableElementList = [];
37826 $modalStack.loadFocusElementList = function(modalWindow) {
37827 if (focusableElementList === undefined || !focusableElementList.length) {
37829 var modalDomE1 = modalWindow.value.modalDomEl;
37830 if (modalDomE1 && modalDomE1.length) {
37831 focusableElementList = modalDomE1[0].querySelectorAll(tababbleSelector);
37837 return $modalStack;
37840 .provider('$uibModal', function() {
37841 var $modalProvider = {
37844 backdrop: true, //can also be false or 'static'
37847 $get: ['$rootScope', '$q', '$document', '$templateRequest', '$controller', '$uibResolve', '$uibModalStack',
37848 function ($rootScope, $q, $document, $templateRequest, $controller, $uibResolve, $modalStack) {
37851 function getTemplatePromise(options) {
37852 return options.template ? $q.when(options.template) :
37853 $templateRequest(angular.isFunction(options.templateUrl) ?
37854 options.templateUrl() : options.templateUrl);
37857 var promiseChain = null;
37858 $modal.getPromiseChain = function() {
37859 return promiseChain;
37862 $modal.open = function(modalOptions) {
37863 var modalResultDeferred = $q.defer();
37864 var modalOpenedDeferred = $q.defer();
37865 var modalClosedDeferred = $q.defer();
37866 var modalRenderDeferred = $q.defer();
37868 //prepare an instance of a modal to be injected into controllers and returned to a caller
37869 var modalInstance = {
37870 result: modalResultDeferred.promise,
37871 opened: modalOpenedDeferred.promise,
37872 closed: modalClosedDeferred.promise,
37873 rendered: modalRenderDeferred.promise,
37874 close: function (result) {
37875 return $modalStack.close(modalInstance, result);
37877 dismiss: function (reason) {
37878 return $modalStack.dismiss(modalInstance, reason);
37882 //merge and clean up options
37883 modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
37884 modalOptions.resolve = modalOptions.resolve || {};
37885 modalOptions.appendTo = modalOptions.appendTo || $document.find('body').eq(0);
37888 if (!modalOptions.template && !modalOptions.templateUrl) {
37889 throw new Error('One of template or templateUrl options is required.');
37892 var templateAndResolvePromise =
37893 $q.all([getTemplatePromise(modalOptions), $uibResolve.resolve(modalOptions.resolve, {}, null, null)]);
37895 function resolveWithTemplate() {
37896 return templateAndResolvePromise;
37899 // Wait for the resolution of the existing promise chain.
37900 // Then switch to our own combined promise dependency (regardless of how the previous modal fared).
37901 // Then add to $modalStack and resolve opened.
37902 // Finally clean up the chain variable if no subsequent modal has overwritten it.
37904 samePromise = promiseChain = $q.all([promiseChain])
37905 .then(resolveWithTemplate, resolveWithTemplate)
37906 .then(function resolveSuccess(tplAndVars) {
37907 var providedScope = modalOptions.scope || $rootScope;
37909 var modalScope = providedScope.$new();
37910 modalScope.$close = modalInstance.close;
37911 modalScope.$dismiss = modalInstance.dismiss;
37913 modalScope.$on('$destroy', function() {
37914 if (!modalScope.$$uibDestructionScheduled) {
37915 modalScope.$dismiss('$uibUnscheduledDestruction');
37919 var ctrlInstance, ctrlLocals = {};
37922 if (modalOptions.controller) {
37923 ctrlLocals.$scope = modalScope;
37924 ctrlLocals.$uibModalInstance = modalInstance;
37925 angular.forEach(tplAndVars[1], function(value, key) {
37926 ctrlLocals[key] = value;
37929 ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
37930 if (modalOptions.controllerAs) {
37931 if (modalOptions.bindToController) {
37932 ctrlInstance.$close = modalScope.$close;
37933 ctrlInstance.$dismiss = modalScope.$dismiss;
37934 angular.extend(ctrlInstance, providedScope);
37937 modalScope[modalOptions.controllerAs] = ctrlInstance;
37941 $modalStack.open(modalInstance, {
37943 deferred: modalResultDeferred,
37944 renderDeferred: modalRenderDeferred,
37945 closedDeferred: modalClosedDeferred,
37946 content: tplAndVars[0],
37947 animation: modalOptions.animation,
37948 backdrop: modalOptions.backdrop,
37949 keyboard: modalOptions.keyboard,
37950 backdropClass: modalOptions.backdropClass,
37951 windowTopClass: modalOptions.windowTopClass,
37952 windowClass: modalOptions.windowClass,
37953 windowTemplateUrl: modalOptions.windowTemplateUrl,
37954 size: modalOptions.size,
37955 openedClass: modalOptions.openedClass,
37956 appendTo: modalOptions.appendTo
37958 modalOpenedDeferred.resolve(true);
37960 }, function resolveError(reason) {
37961 modalOpenedDeferred.reject(reason);
37962 modalResultDeferred.reject(reason);
37963 })['finally'](function() {
37964 if (promiseChain === samePromise) {
37965 promiseChain = null;
37969 return modalInstance;
37977 return $modalProvider;
37980 angular.module('ui.bootstrap.paging', [])
37982 * Helper internal service for generating common controller code between the
37983 * pager and pagination components
37985 .factory('uibPaging', ['$parse', function($parse) {
37987 create: function(ctrl, $scope, $attrs) {
37988 ctrl.setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
37989 ctrl.ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl
37991 ctrl.init = function(ngModelCtrl, config) {
37992 ctrl.ngModelCtrl = ngModelCtrl;
37993 ctrl.config = config;
37995 ngModelCtrl.$render = function() {
37999 if ($attrs.itemsPerPage) {
38000 $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) {
38001 ctrl.itemsPerPage = parseInt(value, 10);
38002 $scope.totalPages = ctrl.calculateTotalPages();
38006 ctrl.itemsPerPage = config.itemsPerPage;
38009 $scope.$watch('totalItems', function(newTotal, oldTotal) {
38010 if (angular.isDefined(newTotal) || newTotal !== oldTotal) {
38011 $scope.totalPages = ctrl.calculateTotalPages();
38017 ctrl.calculateTotalPages = function() {
38018 var totalPages = ctrl.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / ctrl.itemsPerPage);
38019 return Math.max(totalPages || 0, 1);
38022 ctrl.render = function() {
38023 $scope.page = parseInt(ctrl.ngModelCtrl.$viewValue, 10) || 1;
38026 $scope.selectPage = function(page, evt) {
38028 evt.preventDefault();
38031 var clickAllowed = !$scope.ngDisabled || !evt;
38032 if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) {
38033 if (evt && evt.target) {
38036 ctrl.ngModelCtrl.$setViewValue(page);
38037 ctrl.ngModelCtrl.$render();
38041 $scope.getText = function(key) {
38042 return $scope[key + 'Text'] || ctrl.config[key + 'Text'];
38045 $scope.noPrevious = function() {
38046 return $scope.page === 1;
38049 $scope.noNext = function() {
38050 return $scope.page === $scope.totalPages;
38053 ctrl.updatePage = function() {
38054 ctrl.setNumPages($scope.$parent, $scope.totalPages); // Readonly variable
38056 if ($scope.page > $scope.totalPages) {
38057 $scope.selectPage($scope.totalPages);
38059 ctrl.ngModelCtrl.$render();
38066 angular.module('ui.bootstrap.pager', ['ui.bootstrap.paging'])
38068 .controller('UibPagerController', ['$scope', '$attrs', 'uibPaging', 'uibPagerConfig', function($scope, $attrs, uibPaging, uibPagerConfig) {
38069 $scope.align = angular.isDefined($attrs.align) ? $scope.$parent.$eval($attrs.align) : uibPagerConfig.align;
38071 uibPaging.create(this, $scope, $attrs);
38074 .constant('uibPagerConfig', {
38076 previousText: '« Previous',
38077 nextText: 'Next »',
38081 .directive('uibPager', ['uibPagerConfig', function(uibPagerConfig) {
38089 require: ['uibPager', '?ngModel'],
38090 controller: 'UibPagerController',
38091 controllerAs: 'pager',
38092 templateUrl: function(element, attrs) {
38093 return attrs.templateUrl || 'uib/template/pager/pager.html';
38096 link: function(scope, element, attrs, ctrls) {
38097 var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
38099 if (!ngModelCtrl) {
38100 return; // do nothing if no ng-model
38103 paginationCtrl.init(ngModelCtrl, uibPagerConfig);
38108 angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging'])
38109 .controller('UibPaginationController', ['$scope', '$attrs', '$parse', 'uibPaging', 'uibPaginationConfig', function($scope, $attrs, $parse, uibPaging, uibPaginationConfig) {
38111 // Setup configuration parameters
38112 var maxSize = angular.isDefined($attrs.maxSize) ? $scope.$parent.$eval($attrs.maxSize) : uibPaginationConfig.maxSize,
38113 rotate = angular.isDefined($attrs.rotate) ? $scope.$parent.$eval($attrs.rotate) : uibPaginationConfig.rotate,
38114 forceEllipses = angular.isDefined($attrs.forceEllipses) ? $scope.$parent.$eval($attrs.forceEllipses) : uibPaginationConfig.forceEllipses,
38115 boundaryLinkNumbers = angular.isDefined($attrs.boundaryLinkNumbers) ? $scope.$parent.$eval($attrs.boundaryLinkNumbers) : uibPaginationConfig.boundaryLinkNumbers;
38116 $scope.boundaryLinks = angular.isDefined($attrs.boundaryLinks) ? $scope.$parent.$eval($attrs.boundaryLinks) : uibPaginationConfig.boundaryLinks;
38117 $scope.directionLinks = angular.isDefined($attrs.directionLinks) ? $scope.$parent.$eval($attrs.directionLinks) : uibPaginationConfig.directionLinks;
38119 uibPaging.create(this, $scope, $attrs);
38121 if ($attrs.maxSize) {
38122 $scope.$parent.$watch($parse($attrs.maxSize), function(value) {
38123 maxSize = parseInt(value, 10);
38128 // Create page object used in template
38129 function makePage(number, text, isActive) {
38137 function getPages(currentPage, totalPages) {
38140 // Default page limits
38141 var startPage = 1, endPage = totalPages;
38142 var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages;
38144 // recompute if maxSize
38147 // Current page is displayed in the middle of the visible ones
38148 startPage = Math.max(currentPage - Math.floor(maxSize / 2), 1);
38149 endPage = startPage + maxSize - 1;
38151 // Adjust if limit is exceeded
38152 if (endPage > totalPages) {
38153 endPage = totalPages;
38154 startPage = endPage - maxSize + 1;
38157 // Visible pages are paginated with maxSize
38158 startPage = (Math.ceil(currentPage / maxSize) - 1) * maxSize + 1;
38160 // Adjust last page if limit is exceeded
38161 endPage = Math.min(startPage + maxSize - 1, totalPages);
38165 // Add page number links
38166 for (var number = startPage; number <= endPage; number++) {
38167 var page = makePage(number, number, number === currentPage);
38171 // Add links to move between page sets
38172 if (isMaxSized && maxSize > 0 && (!rotate || forceEllipses || boundaryLinkNumbers)) {
38173 if (startPage > 1) {
38174 if (!boundaryLinkNumbers || startPage > 3) { //need ellipsis for all options unless range is too close to beginning
38175 var previousPageSet = makePage(startPage - 1, '...', false);
38176 pages.unshift(previousPageSet);
38178 if (boundaryLinkNumbers) {
38179 if (startPage === 3) { //need to replace ellipsis when the buttons would be sequential
38180 var secondPageLink = makePage(2, '2', false);
38181 pages.unshift(secondPageLink);
38183 //add the first page
38184 var firstPageLink = makePage(1, '1', false);
38185 pages.unshift(firstPageLink);
38189 if (endPage < totalPages) {
38190 if (!boundaryLinkNumbers || endPage < totalPages - 2) { //need ellipsis for all options unless range is too close to end
38191 var nextPageSet = makePage(endPage + 1, '...', false);
38192 pages.push(nextPageSet);
38194 if (boundaryLinkNumbers) {
38195 if (endPage === totalPages - 2) { //need to replace ellipsis when the buttons would be sequential
38196 var secondToLastPageLink = makePage(totalPages - 1, totalPages - 1, false);
38197 pages.push(secondToLastPageLink);
38199 //add the last page
38200 var lastPageLink = makePage(totalPages, totalPages, false);
38201 pages.push(lastPageLink);
38208 var originalRender = this.render;
38209 this.render = function() {
38211 if ($scope.page > 0 && $scope.page <= $scope.totalPages) {
38212 $scope.pages = getPages($scope.page, $scope.totalPages);
38217 .constant('uibPaginationConfig', {
38219 boundaryLinks: false,
38220 boundaryLinkNumbers: false,
38221 directionLinks: true,
38222 firstText: 'First',
38223 previousText: 'Previous',
38227 forceEllipses: false
38230 .directive('uibPagination', ['$parse', 'uibPaginationConfig', function($parse, uibPaginationConfig) {
38240 require: ['uibPagination', '?ngModel'],
38241 controller: 'UibPaginationController',
38242 controllerAs: 'pagination',
38243 templateUrl: function(element, attrs) {
38244 return attrs.templateUrl || 'uib/template/pagination/pagination.html';
38247 link: function(scope, element, attrs, ctrls) {
38248 var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
38250 if (!ngModelCtrl) {
38251 return; // do nothing if no ng-model
38254 paginationCtrl.init(ngModelCtrl, uibPaginationConfig);
38260 * The following features are still outstanding: animation as a
38261 * function, placement as a function, inside, support for more triggers than
38262 * just mouse enter/leave, html tooltips, and selector delegation.
38264 angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.stackedMap'])
38267 * The $tooltip service creates tooltip- and popover-like directives as well as
38268 * houses global options for them.
38270 .provider('$uibTooltip', function() {
38271 // The default options tooltip and popover.
38272 var defaultOptions = {
38274 placementClassPrefix: '',
38277 popupCloseDelay: 0,
38278 useContentExp: false
38281 // Default hide triggers for each show trigger
38283 'mouseenter': 'mouseleave',
38285 'outsideClick': 'outsideClick',
38290 // The options specified to the provider globally.
38291 var globalOptions = {};
38294 * `options({})` allows global configuration of all tooltips in the
38297 * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
38298 * // place tooltips left instead of top by default
38299 * $tooltipProvider.options( { placement: 'left' } );
38302 this.options = function(value) {
38303 angular.extend(globalOptions, value);
38307 * This allows you to extend the set of trigger mappings available. E.g.:
38309 * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
38311 this.setTriggers = function setTriggers(triggers) {
38312 angular.extend(triggerMap, triggers);
38316 * This is a helper function for translating camel-case to snake_case.
38318 function snake_case(name) {
38319 var regexp = /[A-Z]/g;
38320 var separator = '-';
38321 return name.replace(regexp, function(letter, pos) {
38322 return (pos ? separator : '') + letter.toLowerCase();
38327 * Returns the actual instance of the $tooltip service.
38328 * TODO support multiple triggers
38330 this.$get = ['$window', '$compile', '$timeout', '$document', '$uibPosition', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) {
38331 var openedTooltips = $$stackedMap.createNew();
38332 $document.on('keypress', keypressListener);
38334 $rootScope.$on('$destroy', function() {
38335 $document.off('keypress', keypressListener);
38338 function keypressListener(e) {
38339 if (e.which === 27) {
38340 var last = openedTooltips.top();
38342 last.value.close();
38343 openedTooltips.removeTop();
38349 return function $tooltip(ttType, prefix, defaultTriggerShow, options) {
38350 options = angular.extend({}, defaultOptions, globalOptions, options);
38353 * Returns an object of show and hide triggers.
38355 * If a trigger is supplied,
38356 * it is used to show the tooltip; otherwise, it will use the `trigger`
38357 * option passed to the `$tooltipProvider.options` method; else it will
38358 * default to the trigger supplied to this directive factory.
38360 * The hide trigger is based on the show trigger. If the `trigger` option
38361 * was passed to the `$tooltipProvider.options` method, it will use the
38362 * mapped trigger from `triggerMap` or the passed trigger if the map is
38363 * undefined; otherwise, it uses the `triggerMap` value of the show
38364 * trigger; else it will just use the show trigger.
38366 function getTriggers(trigger) {
38367 var show = (trigger || options.trigger || defaultTriggerShow).split(' ');
38368 var hide = show.map(function(trigger) {
38369 return triggerMap[trigger] || trigger;
38377 var directiveName = snake_case(ttType);
38379 var startSym = $interpolate.startSymbol();
38380 var endSym = $interpolate.endSymbol();
38382 '<div '+ directiveName + '-popup '+
38383 'title="' + startSym + 'title' + endSym + '" '+
38384 (options.useContentExp ?
38385 'content-exp="contentExp()" ' :
38386 'content="' + startSym + 'content' + endSym + '" ') +
38387 'placement="' + startSym + 'placement' + endSym + '" '+
38388 'popup-class="' + startSym + 'popupClass' + endSym + '" '+
38389 'animation="animation" ' +
38390 'is-open="isOpen"' +
38391 'origin-scope="origScope" ' +
38392 'style="visibility: hidden; display: block; top: -9999px; left: -9999px;"' +
38397 compile: function(tElem, tAttrs) {
38398 var tooltipLinker = $compile(template);
38400 return function link(scope, element, attrs, tooltipCtrl) {
38402 var tooltipLinkedScope;
38403 var transitionTimeout;
38406 var positionTimeout;
38407 var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false;
38408 var triggers = getTriggers(undefined);
38409 var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']);
38410 var ttScope = scope.$new(true);
38411 var repositionScheduled = false;
38412 var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false;
38413 var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false;
38414 var observers = [];
38416 var positionTooltip = function() {
38417 // check if tooltip exists and is not empty
38418 if (!tooltip || !tooltip.html()) { return; }
38420 if (!positionTimeout) {
38421 positionTimeout = $timeout(function() {
38422 // Reset the positioning.
38423 tooltip.css({ top: 0, left: 0 });
38425 // Now set the calculated positioning.
38426 var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);
38427 tooltip.css({ top: ttPosition.top + 'px', left: ttPosition.left + 'px', visibility: 'visible' });
38429 // If the placement class is prefixed, still need
38430 // to remove the TWBS standard class.
38431 if (options.placementClassPrefix) {
38432 tooltip.removeClass('top bottom left right');
38435 tooltip.removeClass(
38436 options.placementClassPrefix + 'top ' +
38437 options.placementClassPrefix + 'top-left ' +
38438 options.placementClassPrefix + 'top-right ' +
38439 options.placementClassPrefix + 'bottom ' +
38440 options.placementClassPrefix + 'bottom-left ' +
38441 options.placementClassPrefix + 'bottom-right ' +
38442 options.placementClassPrefix + 'left ' +
38443 options.placementClassPrefix + 'left-top ' +
38444 options.placementClassPrefix + 'left-bottom ' +
38445 options.placementClassPrefix + 'right ' +
38446 options.placementClassPrefix + 'right-top ' +
38447 options.placementClassPrefix + 'right-bottom');
38449 var placement = ttPosition.placement.split('-');
38450 tooltip.addClass(placement[0], options.placementClassPrefix + ttPosition.placement);
38451 $position.positionArrow(tooltip, ttPosition.placement);
38453 positionTimeout = null;
38458 // Set up the correct scope to allow transclusion later
38459 ttScope.origScope = scope;
38461 // By default, the tooltip is not open.
38462 // TODO add ability to start tooltip opened
38463 ttScope.isOpen = false;
38464 openedTooltips.add(ttScope, {
38468 function toggleTooltipBind() {
38469 if (!ttScope.isOpen) {
38476 // Show the tooltip with delay if specified, otherwise show it immediately
38477 function showTooltipBind() {
38478 if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) {
38485 if (ttScope.popupDelay) {
38486 // Do nothing if the tooltip was already scheduled to pop-up.
38487 // This happens if show is triggered multiple times before any hide is triggered.
38488 if (!showTimeout) {
38489 showTimeout = $timeout(show, ttScope.popupDelay, false);
38496 function hideTooltipBind() {
38499 if (ttScope.popupCloseDelay) {
38500 if (!hideTimeout) {
38501 hideTimeout = $timeout(hide, ttScope.popupCloseDelay, false);
38508 // Show the tooltip popup element.
38513 // Don't show empty tooltips.
38514 if (!ttScope.content) {
38515 return angular.noop;
38520 // And show the tooltip.
38521 ttScope.$evalAsync(function() {
38522 ttScope.isOpen = true;
38523 assignIsOpen(true);
38528 function cancelShow() {
38530 $timeout.cancel(showTimeout);
38531 showTimeout = null;
38534 if (positionTimeout) {
38535 $timeout.cancel(positionTimeout);
38536 positionTimeout = null;
38540 // Hide the tooltip popup element.
38546 // First things first: we don't show it anymore.
38547 ttScope.$evalAsync(function() {
38548 ttScope.isOpen = false;
38549 assignIsOpen(false);
38550 // And now we remove it from the DOM. However, if we have animation, we
38551 // need to wait for it to expire beforehand.
38552 // FIXME: this is a placeholder for a port of the transitions library.
38553 // The fade transition in TWBS is 150ms.
38554 if (ttScope.animation) {
38555 if (!transitionTimeout) {
38556 transitionTimeout = $timeout(removeTooltip, 150, false);
38564 function cancelHide() {
38566 $timeout.cancel(hideTimeout);
38567 hideTimeout = null;
38569 if (transitionTimeout) {
38570 $timeout.cancel(transitionTimeout);
38571 transitionTimeout = null;
38575 function createTooltip() {
38576 // There can only be one tooltip element per directive shown at once.
38581 tooltipLinkedScope = ttScope.$new();
38582 tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) {
38583 if (appendToBody) {
38584 $document.find('body').append(tooltip);
38586 element.after(tooltip);
38593 function removeTooltip() {
38596 unregisterObservers();
38602 if (tooltipLinkedScope) {
38603 tooltipLinkedScope.$destroy();
38604 tooltipLinkedScope = null;
38609 * Set the initial scope values. Once
38610 * the tooltip is created, the observers
38611 * will be added to keep things in sync.
38613 function prepareTooltip() {
38614 ttScope.title = attrs[prefix + 'Title'];
38615 if (contentParse) {
38616 ttScope.content = contentParse(scope);
38618 ttScope.content = attrs[ttType];
38621 ttScope.popupClass = attrs[prefix + 'Class'];
38622 ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement;
38624 var delay = parseInt(attrs[prefix + 'PopupDelay'], 10);
38625 var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10);
38626 ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay;
38627 ttScope.popupCloseDelay = !isNaN(closeDelay) ? closeDelay : options.popupCloseDelay;
38630 function assignIsOpen(isOpen) {
38631 if (isOpenParse && angular.isFunction(isOpenParse.assign)) {
38632 isOpenParse.assign(scope, isOpen);
38636 ttScope.contentExp = function() {
38637 return ttScope.content;
38641 * Observe the relevant attributes.
38643 attrs.$observe('disabled', function(val) {
38648 if (val && ttScope.isOpen) {
38654 scope.$watch(isOpenParse, function(val) {
38655 if (ttScope && !val === ttScope.isOpen) {
38656 toggleTooltipBind();
38661 function prepObservers() {
38662 observers.length = 0;
38664 if (contentParse) {
38666 scope.$watch(contentParse, function(val) {
38667 ttScope.content = val;
38668 if (!val && ttScope.isOpen) {
38675 tooltipLinkedScope.$watch(function() {
38676 if (!repositionScheduled) {
38677 repositionScheduled = true;
38678 tooltipLinkedScope.$$postDigest(function() {
38679 repositionScheduled = false;
38680 if (ttScope && ttScope.isOpen) {
38689 attrs.$observe(ttType, function(val) {
38690 ttScope.content = val;
38691 if (!val && ttScope.isOpen) {
38701 attrs.$observe(prefix + 'Title', function(val) {
38702 ttScope.title = val;
38703 if (ttScope.isOpen) {
38710 attrs.$observe(prefix + 'Placement', function(val) {
38711 ttScope.placement = val ? val : options.placement;
38712 if (ttScope.isOpen) {
38719 function unregisterObservers() {
38720 if (observers.length) {
38721 angular.forEach(observers, function(observer) {
38724 observers.length = 0;
38728 // hide tooltips/popovers for outsideClick trigger
38729 function bodyHideTooltipBind(e) {
38730 if (!ttScope || !ttScope.isOpen || !tooltip) {
38733 // make sure the tooltip/popover link or tool tooltip/popover itself were not clicked
38734 if (!element[0].contains(e.target) && !tooltip[0].contains(e.target)) {
38739 var unregisterTriggers = function() {
38740 triggers.show.forEach(function(trigger) {
38741 if (trigger === 'outsideClick') {
38742 element.off('click', toggleTooltipBind);
38744 element.off(trigger, showTooltipBind);
38745 element.off(trigger, toggleTooltipBind);
38748 triggers.hide.forEach(function(trigger) {
38749 if (trigger === 'outsideClick') {
38750 $document.off('click', bodyHideTooltipBind);
38752 element.off(trigger, hideTooltipBind);
38757 function prepTriggers() {
38758 var val = attrs[prefix + 'Trigger'];
38759 unregisterTriggers();
38761 triggers = getTriggers(val);
38763 if (triggers.show !== 'none') {
38764 triggers.show.forEach(function(trigger, idx) {
38765 if (trigger === 'outsideClick') {
38766 element.on('click', toggleTooltipBind);
38767 $document.on('click', bodyHideTooltipBind);
38768 } else if (trigger === triggers.hide[idx]) {
38769 element.on(trigger, toggleTooltipBind);
38770 } else if (trigger) {
38771 element.on(trigger, showTooltipBind);
38772 element.on(triggers.hide[idx], hideTooltipBind);
38775 element.on('keypress', function(e) {
38776 if (e.which === 27) {
38786 var animation = scope.$eval(attrs[prefix + 'Animation']);
38787 ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation;
38789 var appendToBodyVal;
38790 var appendKey = prefix + 'AppendToBody';
38791 if (appendKey in attrs && attrs[appendKey] === undefined) {
38792 appendToBodyVal = true;
38794 appendToBodyVal = scope.$eval(attrs[appendKey]);
38797 appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;
38799 // if a tooltip is attached to <body> we need to remove it on
38800 // location change as its parent scope will probably not be destroyed
38802 if (appendToBody) {
38803 scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess() {
38804 if (ttScope.isOpen) {
38810 // Make sure tooltip is destroyed and removed.
38811 scope.$on('$destroy', function onDestroyTooltip() {
38812 unregisterTriggers();
38814 openedTooltips.remove(ttScope);
38824 // This is mostly ngInclude code but with a custom scope
38825 .directive('uibTooltipTemplateTransclude', [
38826 '$animate', '$sce', '$compile', '$templateRequest',
38827 function ($animate, $sce, $compile, $templateRequest) {
38829 link: function(scope, elem, attrs) {
38830 var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope);
38832 var changeCounter = 0,
38837 var cleanupLastIncludeContent = function() {
38838 if (previousElement) {
38839 previousElement.remove();
38840 previousElement = null;
38843 if (currentScope) {
38844 currentScope.$destroy();
38845 currentScope = null;
38848 if (currentElement) {
38849 $animate.leave(currentElement).then(function() {
38850 previousElement = null;
38852 previousElement = currentElement;
38853 currentElement = null;
38857 scope.$watch($sce.parseAsResourceUrl(attrs.uibTooltipTemplateTransclude), function(src) {
38858 var thisChangeId = ++changeCounter;
38861 //set the 2nd param to true to ignore the template request error so that the inner
38862 //contents and scope can be cleaned up.
38863 $templateRequest(src, true).then(function(response) {
38864 if (thisChangeId !== changeCounter) { return; }
38865 var newScope = origScope.$new();
38866 var template = response;
38868 var clone = $compile(template)(newScope, function(clone) {
38869 cleanupLastIncludeContent();
38870 $animate.enter(clone, elem);
38873 currentScope = newScope;
38874 currentElement = clone;
38876 currentScope.$emit('$includeContentLoaded', src);
38878 if (thisChangeId === changeCounter) {
38879 cleanupLastIncludeContent();
38880 scope.$emit('$includeContentError', src);
38883 scope.$emit('$includeContentRequested', src);
38885 cleanupLastIncludeContent();
38889 scope.$on('$destroy', cleanupLastIncludeContent);
38895 * Note that it's intentional that these classes are *not* applied through $animate.
38896 * They must not be animated as they're expected to be present on the tooltip on
38899 .directive('uibTooltipClasses', ['$uibPosition', function($uibPosition) {
38902 link: function(scope, element, attrs) {
38903 // need to set the primary position so the
38904 // arrow has space during position measure.
38905 // tooltip.positionTooltip()
38906 if (scope.placement) {
38907 // // There are no top-left etc... classes
38908 // // in TWBS, so we need the primary position.
38909 var position = $uibPosition.parsePlacement(scope.placement);
38910 element.addClass(position[0]);
38912 element.addClass('top');
38915 if (scope.popupClass) {
38916 element.addClass(scope.popupClass);
38919 if (scope.animation()) {
38920 element.addClass(attrs.tooltipAnimationClass);
38926 .directive('uibTooltipPopup', function() {
38929 scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
38930 templateUrl: 'uib/template/tooltip/tooltip-popup.html'
38934 .directive('uibTooltip', [ '$uibTooltip', function($uibTooltip) {
38935 return $uibTooltip('uibTooltip', 'tooltip', 'mouseenter');
38938 .directive('uibTooltipTemplatePopup', function() {
38941 scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
38942 originScope: '&' },
38943 templateUrl: 'uib/template/tooltip/tooltip-template-popup.html'
38947 .directive('uibTooltipTemplate', ['$uibTooltip', function($uibTooltip) {
38948 return $uibTooltip('uibTooltipTemplate', 'tooltip', 'mouseenter', {
38949 useContentExp: true
38953 .directive('uibTooltipHtmlPopup', function() {
38956 scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
38957 templateUrl: 'uib/template/tooltip/tooltip-html-popup.html'
38961 .directive('uibTooltipHtml', ['$uibTooltip', function($uibTooltip) {
38962 return $uibTooltip('uibTooltipHtml', 'tooltip', 'mouseenter', {
38963 useContentExp: true
38968 * The following features are still outstanding: popup delay, animation as a
38969 * function, placement as a function, inside, support for more triggers than
38970 * just mouse enter/leave, and selector delegatation.
38972 angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip'])
38974 .directive('uibPopoverTemplatePopup', function() {
38977 scope: { title: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
38978 originScope: '&' },
38979 templateUrl: 'uib/template/popover/popover-template.html'
38983 .directive('uibPopoverTemplate', ['$uibTooltip', function($uibTooltip) {
38984 return $uibTooltip('uibPopoverTemplate', 'popover', 'click', {
38985 useContentExp: true
38989 .directive('uibPopoverHtmlPopup', function() {
38992 scope: { contentExp: '&', title: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
38993 templateUrl: 'uib/template/popover/popover-html.html'
38997 .directive('uibPopoverHtml', ['$uibTooltip', function($uibTooltip) {
38998 return $uibTooltip('uibPopoverHtml', 'popover', 'click', {
38999 useContentExp: true
39003 .directive('uibPopoverPopup', function() {
39006 scope: { title: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
39007 templateUrl: 'uib/template/popover/popover.html'
39011 .directive('uibPopover', ['$uibTooltip', function($uibTooltip) {
39012 return $uibTooltip('uibPopover', 'popover', 'click');
39015 angular.module('ui.bootstrap.progressbar', [])
39017 .constant('uibProgressConfig', {
39022 .controller('UibProgressController', ['$scope', '$attrs', 'uibProgressConfig', function($scope, $attrs, progressConfig) {
39024 animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
39027 $scope.max = angular.isDefined($scope.max) ? $scope.max : progressConfig.max;
39029 this.addBar = function(bar, element, attrs) {
39031 element.css({'transition': 'none'});
39034 this.bars.push(bar);
39036 bar.max = $scope.max;
39037 bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar';
39039 bar.$watch('value', function(value) {
39040 bar.recalculatePercentage();
39043 bar.recalculatePercentage = function() {
39044 var totalPercentage = self.bars.reduce(function(total, bar) {
39045 bar.percent = +(100 * bar.value / bar.max).toFixed(2);
39046 return total + bar.percent;
39049 if (totalPercentage > 100) {
39050 bar.percent -= totalPercentage - 100;
39054 bar.$on('$destroy', function() {
39056 self.removeBar(bar);
39060 this.removeBar = function(bar) {
39061 this.bars.splice(this.bars.indexOf(bar), 1);
39062 this.bars.forEach(function (bar) {
39063 bar.recalculatePercentage();
39067 $scope.$watch('max', function(max) {
39068 self.bars.forEach(function(bar) {
39069 bar.max = $scope.max;
39070 bar.recalculatePercentage();
39075 .directive('uibProgress', function() {
39079 controller: 'UibProgressController',
39080 require: 'uibProgress',
39084 templateUrl: 'uib/template/progressbar/progress.html'
39088 .directive('uibBar', function() {
39092 require: '^uibProgress',
39097 templateUrl: 'uib/template/progressbar/bar.html',
39098 link: function(scope, element, attrs, progressCtrl) {
39099 progressCtrl.addBar(scope, element, attrs);
39104 .directive('uibProgressbar', function() {
39108 controller: 'UibProgressController',
39114 templateUrl: 'uib/template/progressbar/progressbar.html',
39115 link: function(scope, element, attrs, progressCtrl) {
39116 progressCtrl.addBar(scope, angular.element(element.children()[0]), {title: attrs.title});
39121 angular.module('ui.bootstrap.rating', [])
39123 .constant('uibRatingConfig', {
39127 titles : ['one', 'two', 'three', 'four', 'five']
39130 .controller('UibRatingController', ['$scope', '$attrs', 'uibRatingConfig', function($scope, $attrs, ratingConfig) {
39131 var ngModelCtrl = { $setViewValue: angular.noop };
39133 this.init = function(ngModelCtrl_) {
39134 ngModelCtrl = ngModelCtrl_;
39135 ngModelCtrl.$render = this.render;
39137 ngModelCtrl.$formatters.push(function(value) {
39138 if (angular.isNumber(value) && value << 0 !== value) {
39139 value = Math.round(value);
39145 this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
39146 this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
39147 var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles ;
39148 this.titles = angular.isArray(tmpTitles) && tmpTitles.length > 0 ?
39149 tmpTitles : ratingConfig.titles;
39151 var ratingStates = angular.isDefined($attrs.ratingStates) ?
39152 $scope.$parent.$eval($attrs.ratingStates) :
39153 new Array(angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max);
39154 $scope.range = this.buildTemplateObjects(ratingStates);
39157 this.buildTemplateObjects = function(states) {
39158 for (var i = 0, n = states.length; i < n; i++) {
39159 states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff, title: this.getTitle(i) }, states[i]);
39164 this.getTitle = function(index) {
39165 if (index >= this.titles.length) {
39169 return this.titles[index];
39172 $scope.rate = function(value) {
39173 if (!$scope.readonly && value >= 0 && value <= $scope.range.length) {
39174 ngModelCtrl.$setViewValue(ngModelCtrl.$viewValue === value ? 0 : value);
39175 ngModelCtrl.$render();
39179 $scope.enter = function(value) {
39180 if (!$scope.readonly) {
39181 $scope.value = value;
39183 $scope.onHover({value: value});
39186 $scope.reset = function() {
39187 $scope.value = ngModelCtrl.$viewValue;
39191 $scope.onKeydown = function(evt) {
39192 if (/(37|38|39|40)/.test(evt.which)) {
39193 evt.preventDefault();
39194 evt.stopPropagation();
39195 $scope.rate($scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1));
39199 this.render = function() {
39200 $scope.value = ngModelCtrl.$viewValue;
39204 .directive('uibRating', function() {
39206 require: ['uibRating', 'ngModel'],
39212 controller: 'UibRatingController',
39213 templateUrl: 'uib/template/rating/rating.html',
39215 link: function(scope, element, attrs, ctrls) {
39216 var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];
39217 ratingCtrl.init(ngModelCtrl);
39222 angular.module('ui.bootstrap.tabs', [])
39224 .controller('UibTabsetController', ['$scope', function ($scope) {
39226 tabs = ctrl.tabs = $scope.tabs = [];
39228 ctrl.select = function(selectedTab) {
39229 angular.forEach(tabs, function(tab) {
39230 if (tab.active && tab !== selectedTab) {
39231 tab.active = false;
39233 selectedTab.selectCalled = false;
39236 selectedTab.active = true;
39237 // only call select if it has not already been called
39238 if (!selectedTab.selectCalled) {
39239 selectedTab.onSelect();
39240 selectedTab.selectCalled = true;
39244 ctrl.addTab = function addTab(tab) {
39246 // we can't run the select function on the first tab
39247 // since that would select it twice
39248 if (tabs.length === 1 && tab.active !== false) {
39250 } else if (tab.active) {
39253 tab.active = false;
39257 ctrl.removeTab = function removeTab(tab) {
39258 var index = tabs.indexOf(tab);
39259 //Select a new tab if the tab to be removed is selected and not destroyed
39260 if (tab.active && tabs.length > 1 && !destroyed) {
39261 //If this is the last tab, select the previous tab. else, the next tab.
39262 var newActiveIndex = index === tabs.length - 1 ? index - 1 : index + 1;
39263 ctrl.select(tabs[newActiveIndex]);
39265 tabs.splice(index, 1);
39269 $scope.$on('$destroy', function() {
39274 .directive('uibTabset', function() {
39281 controller: 'UibTabsetController',
39282 templateUrl: 'uib/template/tabs/tabset.html',
39283 link: function(scope, element, attrs) {
39284 scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
39285 scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false;
39290 .directive('uibTab', ['$parse', function($parse) {
39292 require: '^uibTabset',
39294 templateUrl: 'uib/template/tabs/tab.html',
39299 onSelect: '&select', //This callback is called in contentHeadingTransclude
39300 //once it inserts the tab's content into the dom
39301 onDeselect: '&deselect'
39303 controller: function() {
39304 //Empty controller so other directives can require being 'under' a tab
39306 controllerAs: 'tab',
39307 link: function(scope, elm, attrs, tabsetCtrl, transclude) {
39308 scope.$watch('active', function(active) {
39310 tabsetCtrl.select(scope);
39314 scope.disabled = false;
39315 if (attrs.disable) {
39316 scope.$parent.$watch($parse(attrs.disable), function(value) {
39317 scope.disabled = !! value;
39321 scope.select = function() {
39322 if (!scope.disabled) {
39323 scope.active = true;
39327 tabsetCtrl.addTab(scope);
39328 scope.$on('$destroy', function() {
39329 tabsetCtrl.removeTab(scope);
39332 //We need to transclude later, once the content container is ready.
39333 //when this link happens, we're inside a tab heading.
39334 scope.$transcludeFn = transclude;
39339 .directive('uibTabHeadingTransclude', function() {
39342 require: '^uibTab',
39343 link: function(scope, elm) {
39344 scope.$watch('headingElement', function updateHeadingElement(heading) {
39347 elm.append(heading);
39354 .directive('uibTabContentTransclude', function() {
39357 require: '^uibTabset',
39358 link: function(scope, elm, attrs) {
39359 var tab = scope.$eval(attrs.uibTabContentTransclude);
39361 //Now our tab is ready to be transcluded: both the tab heading area
39362 //and the tab content area are loaded. Transclude 'em both.
39363 tab.$transcludeFn(tab.$parent, function(contents) {
39364 angular.forEach(contents, function(node) {
39365 if (isTabHeading(node)) {
39366 //Let tabHeadingTransclude know.
39367 tab.headingElement = node;
39376 function isTabHeading(node) {
39377 return node.tagName && (
39378 node.hasAttribute('uib-tab-heading') ||
39379 node.hasAttribute('data-uib-tab-heading') ||
39380 node.hasAttribute('x-uib-tab-heading') ||
39381 node.tagName.toLowerCase() === 'uib-tab-heading' ||
39382 node.tagName.toLowerCase() === 'data-uib-tab-heading' ||
39383 node.tagName.toLowerCase() === 'x-uib-tab-heading'
39388 angular.module('ui.bootstrap.timepicker', [])
39390 .constant('uibTimepickerConfig', {
39394 showMeridian: true,
39395 showSeconds: false,
39397 readonlyInput: false,
39403 .controller('UibTimepickerController', ['$scope', '$element', '$attrs', '$parse', '$log', '$locale', 'uibTimepickerConfig', function($scope, $element, $attrs, $parse, $log, $locale, timepickerConfig) {
39404 var selected = new Date(),
39405 ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
39406 meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS;
39408 $scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0;
39409 $element.removeAttr('tabindex');
39411 this.init = function(ngModelCtrl_, inputs) {
39412 ngModelCtrl = ngModelCtrl_;
39413 ngModelCtrl.$render = this.render;
39415 ngModelCtrl.$formatters.unshift(function(modelValue) {
39416 return modelValue ? new Date(modelValue) : null;
39419 var hoursInputEl = inputs.eq(0),
39420 minutesInputEl = inputs.eq(1),
39421 secondsInputEl = inputs.eq(2);
39423 var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel;
39426 this.setupMousewheelEvents(hoursInputEl, minutesInputEl, secondsInputEl);
39429 var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys;
39431 this.setupArrowkeyEvents(hoursInputEl, minutesInputEl, secondsInputEl);
39434 $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput;
39435 this.setupInputEvents(hoursInputEl, minutesInputEl, secondsInputEl);
39438 var hourStep = timepickerConfig.hourStep;
39439 if ($attrs.hourStep) {
39440 $scope.$parent.$watch($parse($attrs.hourStep), function(value) {
39441 hourStep = parseInt(value, 10);
39445 var minuteStep = timepickerConfig.minuteStep;
39446 if ($attrs.minuteStep) {
39447 $scope.$parent.$watch($parse($attrs.minuteStep), function(value) {
39448 minuteStep = parseInt(value, 10);
39453 $scope.$parent.$watch($parse($attrs.min), function(value) {
39454 var dt = new Date(value);
39455 min = isNaN(dt) ? undefined : dt;
39459 $scope.$parent.$watch($parse($attrs.max), function(value) {
39460 var dt = new Date(value);
39461 max = isNaN(dt) ? undefined : dt;
39464 var disabled = false;
39465 if ($attrs.ngDisabled) {
39466 $scope.$parent.$watch($parse($attrs.ngDisabled), function(value) {
39471 $scope.noIncrementHours = function() {
39472 var incrementedSelected = addMinutes(selected, hourStep * 60);
39473 return disabled || incrementedSelected > max ||
39474 incrementedSelected < selected && incrementedSelected < min;
39477 $scope.noDecrementHours = function() {
39478 var decrementedSelected = addMinutes(selected, -hourStep * 60);
39479 return disabled || decrementedSelected < min ||
39480 decrementedSelected > selected && decrementedSelected > max;
39483 $scope.noIncrementMinutes = function() {
39484 var incrementedSelected = addMinutes(selected, minuteStep);
39485 return disabled || incrementedSelected > max ||
39486 incrementedSelected < selected && incrementedSelected < min;
39489 $scope.noDecrementMinutes = function() {
39490 var decrementedSelected = addMinutes(selected, -minuteStep);
39491 return disabled || decrementedSelected < min ||
39492 decrementedSelected > selected && decrementedSelected > max;
39495 $scope.noIncrementSeconds = function() {
39496 var incrementedSelected = addSeconds(selected, secondStep);
39497 return disabled || incrementedSelected > max ||
39498 incrementedSelected < selected && incrementedSelected < min;
39501 $scope.noDecrementSeconds = function() {
39502 var decrementedSelected = addSeconds(selected, -secondStep);
39503 return disabled || decrementedSelected < min ||
39504 decrementedSelected > selected && decrementedSelected > max;
39507 $scope.noToggleMeridian = function() {
39508 if (selected.getHours() < 12) {
39509 return disabled || addMinutes(selected, 12 * 60) > max;
39512 return disabled || addMinutes(selected, -12 * 60) < min;
39515 var secondStep = timepickerConfig.secondStep;
39516 if ($attrs.secondStep) {
39517 $scope.$parent.$watch($parse($attrs.secondStep), function(value) {
39518 secondStep = parseInt(value, 10);
39522 $scope.showSeconds = timepickerConfig.showSeconds;
39523 if ($attrs.showSeconds) {
39524 $scope.$parent.$watch($parse($attrs.showSeconds), function(value) {
39525 $scope.showSeconds = !!value;
39530 $scope.showMeridian = timepickerConfig.showMeridian;
39531 if ($attrs.showMeridian) {
39532 $scope.$parent.$watch($parse($attrs.showMeridian), function(value) {
39533 $scope.showMeridian = !!value;
39535 if (ngModelCtrl.$error.time) {
39536 // Evaluate from template
39537 var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();
39538 if (angular.isDefined(hours) && angular.isDefined(minutes)) {
39539 selected.setHours(hours);
39548 // Get $scope.hours in 24H mode if valid
39549 function getHoursFromTemplate() {
39550 var hours = parseInt($scope.hours, 10);
39551 var valid = $scope.showMeridian ? hours > 0 && hours < 13 :
39552 hours >= 0 && hours < 24;
39557 if ($scope.showMeridian) {
39558 if (hours === 12) {
39561 if ($scope.meridian === meridians[1]) {
39562 hours = hours + 12;
39568 function getMinutesFromTemplate() {
39569 var minutes = parseInt($scope.minutes, 10);
39570 return minutes >= 0 && minutes < 60 ? minutes : undefined;
39573 function getSecondsFromTemplate() {
39574 var seconds = parseInt($scope.seconds, 10);
39575 return seconds >= 0 && seconds < 60 ? seconds : undefined;
39578 function pad(value) {
39579 if (value === null) {
39583 return angular.isDefined(value) && value.toString().length < 2 ?
39584 '0' + value : value.toString();
39587 // Respond on mousewheel spin
39588 this.setupMousewheelEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
39589 var isScrollingUp = function(e) {
39590 if (e.originalEvent) {
39591 e = e.originalEvent;
39593 //pick correct delta variable depending on event
39594 var delta = e.wheelDelta ? e.wheelDelta : -e.deltaY;
39595 return e.detail || delta > 0;
39598 hoursInputEl.bind('mousewheel wheel', function(e) {
39600 $scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours());
39602 e.preventDefault();
39605 minutesInputEl.bind('mousewheel wheel', function(e) {
39607 $scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes());
39609 e.preventDefault();
39612 secondsInputEl.bind('mousewheel wheel', function(e) {
39614 $scope.$apply(isScrollingUp(e) ? $scope.incrementSeconds() : $scope.decrementSeconds());
39616 e.preventDefault();
39620 // Respond on up/down arrowkeys
39621 this.setupArrowkeyEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
39622 hoursInputEl.bind('keydown', function(e) {
39624 if (e.which === 38) { // up
39625 e.preventDefault();
39626 $scope.incrementHours();
39628 } else if (e.which === 40) { // down
39629 e.preventDefault();
39630 $scope.decrementHours();
39636 minutesInputEl.bind('keydown', function(e) {
39638 if (e.which === 38) { // up
39639 e.preventDefault();
39640 $scope.incrementMinutes();
39642 } else if (e.which === 40) { // down
39643 e.preventDefault();
39644 $scope.decrementMinutes();
39650 secondsInputEl.bind('keydown', function(e) {
39652 if (e.which === 38) { // up
39653 e.preventDefault();
39654 $scope.incrementSeconds();
39656 } else if (e.which === 40) { // down
39657 e.preventDefault();
39658 $scope.decrementSeconds();
39665 this.setupInputEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
39666 if ($scope.readonlyInput) {
39667 $scope.updateHours = angular.noop;
39668 $scope.updateMinutes = angular.noop;
39669 $scope.updateSeconds = angular.noop;
39673 var invalidate = function(invalidHours, invalidMinutes, invalidSeconds) {
39674 ngModelCtrl.$setViewValue(null);
39675 ngModelCtrl.$setValidity('time', false);
39676 if (angular.isDefined(invalidHours)) {
39677 $scope.invalidHours = invalidHours;
39680 if (angular.isDefined(invalidMinutes)) {
39681 $scope.invalidMinutes = invalidMinutes;
39684 if (angular.isDefined(invalidSeconds)) {
39685 $scope.invalidSeconds = invalidSeconds;
39689 $scope.updateHours = function() {
39690 var hours = getHoursFromTemplate(),
39691 minutes = getMinutesFromTemplate();
39693 ngModelCtrl.$setDirty();
39695 if (angular.isDefined(hours) && angular.isDefined(minutes)) {
39696 selected.setHours(hours);
39697 selected.setMinutes(minutes);
39698 if (selected < min || selected > max) {
39708 hoursInputEl.bind('blur', function(e) {
39709 ngModelCtrl.$setTouched();
39710 if ($scope.hours === null || $scope.hours === '') {
39712 } else if (!$scope.invalidHours && $scope.hours < 10) {
39713 $scope.$apply(function() {
39714 $scope.hours = pad($scope.hours);
39719 $scope.updateMinutes = function() {
39720 var minutes = getMinutesFromTemplate(),
39721 hours = getHoursFromTemplate();
39723 ngModelCtrl.$setDirty();
39725 if (angular.isDefined(minutes) && angular.isDefined(hours)) {
39726 selected.setHours(hours);
39727 selected.setMinutes(minutes);
39728 if (selected < min || selected > max) {
39729 invalidate(undefined, true);
39734 invalidate(undefined, true);
39738 minutesInputEl.bind('blur', function(e) {
39739 ngModelCtrl.$setTouched();
39740 if ($scope.minutes === null) {
39741 invalidate(undefined, true);
39742 } else if (!$scope.invalidMinutes && $scope.minutes < 10) {
39743 $scope.$apply(function() {
39744 $scope.minutes = pad($scope.minutes);
39749 $scope.updateSeconds = function() {
39750 var seconds = getSecondsFromTemplate();
39752 ngModelCtrl.$setDirty();
39754 if (angular.isDefined(seconds)) {
39755 selected.setSeconds(seconds);
39758 invalidate(undefined, undefined, true);
39762 secondsInputEl.bind('blur', function(e) {
39763 if (!$scope.invalidSeconds && $scope.seconds < 10) {
39764 $scope.$apply( function() {
39765 $scope.seconds = pad($scope.seconds);
39772 this.render = function() {
39773 var date = ngModelCtrl.$viewValue;
39776 ngModelCtrl.$setValidity('time', false);
39777 $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.');
39783 if (selected < min || selected > max) {
39784 ngModelCtrl.$setValidity('time', false);
39785 $scope.invalidHours = true;
39786 $scope.invalidMinutes = true;
39794 // Call internally when we know that model is valid.
39795 function refresh(keyboardChange) {
39797 ngModelCtrl.$setViewValue(new Date(selected));
39798 updateTemplate(keyboardChange);
39801 function makeValid() {
39802 ngModelCtrl.$setValidity('time', true);
39803 $scope.invalidHours = false;
39804 $scope.invalidMinutes = false;
39805 $scope.invalidSeconds = false;
39808 function updateTemplate(keyboardChange) {
39809 if (!ngModelCtrl.$modelValue) {
39810 $scope.hours = null;
39811 $scope.minutes = null;
39812 $scope.seconds = null;
39813 $scope.meridian = meridians[0];
39815 var hours = selected.getHours(),
39816 minutes = selected.getMinutes(),
39817 seconds = selected.getSeconds();
39819 if ($scope.showMeridian) {
39820 hours = hours === 0 || hours === 12 ? 12 : hours % 12; // Convert 24 to 12 hour system
39823 $scope.hours = keyboardChange === 'h' ? hours : pad(hours);
39824 if (keyboardChange !== 'm') {
39825 $scope.minutes = pad(minutes);
39827 $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
39829 if (keyboardChange !== 's') {
39830 $scope.seconds = pad(seconds);
39832 $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
39836 function addSecondsToSelected(seconds) {
39837 selected = addSeconds(selected, seconds);
39841 function addMinutes(selected, minutes) {
39842 return addSeconds(selected, minutes*60);
39845 function addSeconds(date, seconds) {
39846 var dt = new Date(date.getTime() + seconds * 1000);
39847 var newDate = new Date(date);
39848 newDate.setHours(dt.getHours(), dt.getMinutes(), dt.getSeconds());
39852 $scope.showSpinners = angular.isDefined($attrs.showSpinners) ?
39853 $scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners;
39855 $scope.incrementHours = function() {
39856 if (!$scope.noIncrementHours()) {
39857 addSecondsToSelected(hourStep * 60 * 60);
39861 $scope.decrementHours = function() {
39862 if (!$scope.noDecrementHours()) {
39863 addSecondsToSelected(-hourStep * 60 * 60);
39867 $scope.incrementMinutes = function() {
39868 if (!$scope.noIncrementMinutes()) {
39869 addSecondsToSelected(minuteStep * 60);
39873 $scope.decrementMinutes = function() {
39874 if (!$scope.noDecrementMinutes()) {
39875 addSecondsToSelected(-minuteStep * 60);
39879 $scope.incrementSeconds = function() {
39880 if (!$scope.noIncrementSeconds()) {
39881 addSecondsToSelected(secondStep);
39885 $scope.decrementSeconds = function() {
39886 if (!$scope.noDecrementSeconds()) {
39887 addSecondsToSelected(-secondStep);
39891 $scope.toggleMeridian = function() {
39892 var minutes = getMinutesFromTemplate(),
39893 hours = getHoursFromTemplate();
39895 if (!$scope.noToggleMeridian()) {
39896 if (angular.isDefined(minutes) && angular.isDefined(hours)) {
39897 addSecondsToSelected(12 * 60 * (selected.getHours() < 12 ? 60 : -60));
39899 $scope.meridian = $scope.meridian === meridians[0] ? meridians[1] : meridians[0];
39904 $scope.blur = function() {
39905 ngModelCtrl.$setTouched();
39909 .directive('uibTimepicker', function() {
39911 require: ['uibTimepicker', '?^ngModel'],
39912 controller: 'UibTimepickerController',
39913 controllerAs: 'timepicker',
39916 templateUrl: function(element, attrs) {
39917 return attrs.templateUrl || 'uib/template/timepicker/timepicker.html';
39919 link: function(scope, element, attrs, ctrls) {
39920 var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
39923 timepickerCtrl.init(ngModelCtrl, element.find('input'));
39929 angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap.position'])
39932 * A helper service that can parse typeahead's syntax (string provided by users)
39933 * Extracted to a separate service for ease of unit testing
39935 .factory('uibTypeaheadParser', ['$parse', function($parse) {
39936 // 00000111000000000000022200000000000000003333333333333330000000000044000
39937 var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
39939 parse: function(input) {
39940 var match = input.match(TYPEAHEAD_REGEXP);
39943 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
39944 ' but got "' + input + '".');
39948 itemName: match[3],
39949 source: $parse(match[4]),
39950 viewMapper: $parse(match[2] || match[1]),
39951 modelMapper: $parse(match[1])
39957 .controller('UibTypeaheadController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$$debounce', '$uibPosition', 'uibTypeaheadParser',
39958 function(originalScope, element, attrs, $compile, $parse, $q, $timeout, $document, $window, $rootScope, $$debounce, $position, typeaheadParser) {
39959 var HOT_KEYS = [9, 13, 27, 38, 40];
39960 var eventDebounceTime = 200;
39961 var modelCtrl, ngModelOptions;
39962 //SUPPORTED ATTRIBUTES (OPTIONS)
39964 //minimal no of characters that needs to be entered before typeahead kicks-in
39965 var minLength = originalScope.$eval(attrs.typeaheadMinLength);
39966 if (!minLength && minLength !== 0) {
39970 //minimal wait time after last character typed before typeahead kicks-in
39971 var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
39973 //should it restrict model values to the ones selected from the popup only?
39974 var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
39975 originalScope.$watch(attrs.typeaheadEditable, function (newVal) {
39976 isEditable = newVal !== false;
39979 //binding to a variable that indicates if matches are being retrieved asynchronously
39980 var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
39982 //a callback executed when a match is selected
39983 var onSelectCallback = $parse(attrs.typeaheadOnSelect);
39985 //should it select highlighted popup value when losing focus?
39986 var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false;
39988 //binding to a variable that indicates if there were no results after the query is completed
39989 var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop;
39991 var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
39993 var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;
39995 var appendTo = attrs.typeaheadAppendTo ?
39996 originalScope.$eval(attrs.typeaheadAppendTo) : null;
39998 var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;
40000 //If input matches an item of the list exactly, select it automatically
40001 var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false;
40003 //binding to a variable that indicates if dropdown is open
40004 var isOpenSetter = $parse(attrs.typeaheadIsOpen).assign || angular.noop;
40006 var showHint = originalScope.$eval(attrs.typeaheadShowHint) || false;
40008 //INTERNAL VARIABLES
40010 //model setter executed upon match selection
40011 var parsedModel = $parse(attrs.ngModel);
40012 var invokeModelSetter = $parse(attrs.ngModel + '($$$p)');
40013 var $setModelValue = function(scope, newValue) {
40014 if (angular.isFunction(parsedModel(originalScope)) &&
40015 ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) {
40016 return invokeModelSetter(scope, {$$$p: newValue});
40019 return parsedModel.assign(scope, newValue);
40022 //expressions used by typeahead
40023 var parserResult = typeaheadParser.parse(attrs.uibTypeahead);
40027 //Used to avoid bug in iOS webview where iOS keyboard does not fire
40028 //mousedown & mouseup events
40032 //create a child scope for the typeahead directive so we are not polluting original scope
40033 //with typeahead-specific data (matches, query etc.)
40034 var scope = originalScope.$new();
40035 var offDestroy = originalScope.$on('$destroy', function() {
40038 scope.$on('$destroy', offDestroy);
40041 var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
40043 'aria-autocomplete': 'list',
40044 'aria-expanded': false,
40045 'aria-owns': popupId
40048 var inputsContainer, hintInputElem;
40049 //add read-only input to show hint
40051 inputsContainer = angular.element('<div></div>');
40052 inputsContainer.css('position', 'relative');
40053 element.after(inputsContainer);
40054 hintInputElem = element.clone();
40055 hintInputElem.attr('placeholder', '');
40056 hintInputElem.val('');
40057 hintInputElem.css({
40058 'position': 'absolute',
40061 'border-color': 'transparent',
40062 'box-shadow': 'none',
40064 'background': 'none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)',
40068 'position': 'relative',
40069 'vertical-align': 'top',
40070 'background-color': 'transparent'
40072 inputsContainer.append(hintInputElem);
40073 hintInputElem.after(element);
40076 //pop-up element used to display matches
40077 var popUpEl = angular.element('<div uib-typeahead-popup></div>');
40080 matches: 'matches',
40081 active: 'activeIdx',
40082 select: 'select(activeIdx, evt)',
40083 'move-in-progress': 'moveInProgress',
40085 position: 'position',
40086 'assign-is-open': 'assignIsOpen(isOpen)',
40087 debounce: 'debounceUpdate'
40089 //custom item template
40090 if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
40091 popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
40094 if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) {
40095 popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl);
40098 var resetHint = function() {
40100 hintInputElem.val('');
40104 var resetMatches = function() {
40105 scope.matches = [];
40106 scope.activeIdx = -1;
40107 element.attr('aria-expanded', false);
40111 var getMatchId = function(index) {
40112 return popupId + '-option-' + index;
40115 // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
40116 // This attribute is added or removed automatically when the `activeIdx` changes.
40117 scope.$watch('activeIdx', function(index) {
40119 element.removeAttr('aria-activedescendant');
40121 element.attr('aria-activedescendant', getMatchId(index));
40125 var inputIsExactMatch = function(inputValue, index) {
40126 if (scope.matches.length > index && inputValue) {
40127 return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase();
40133 var getMatchesAsync = function(inputValue, evt) {
40134 var locals = {$viewValue: inputValue};
40135 isLoadingSetter(originalScope, true);
40136 isNoResultsSetter(originalScope, false);
40137 $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
40138 //it might happen that several async queries were in progress if a user were typing fast
40139 //but we are interested only in responses that correspond to the current view value
40140 var onCurrentRequest = inputValue === modelCtrl.$viewValue;
40141 if (onCurrentRequest && hasFocus) {
40142 if (matches && matches.length > 0) {
40143 scope.activeIdx = focusFirst ? 0 : -1;
40144 isNoResultsSetter(originalScope, false);
40145 scope.matches.length = 0;
40148 for (var i = 0; i < matches.length; i++) {
40149 locals[parserResult.itemName] = matches[i];
40150 scope.matches.push({
40152 label: parserResult.viewMapper(scope, locals),
40157 scope.query = inputValue;
40158 //position pop-up with matches - we need to re-calculate its position each time we are opening a window
40159 //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
40160 //due to other elements being rendered
40161 recalculatePosition();
40163 element.attr('aria-expanded', true);
40165 //Select the single remaining option if user input matches
40166 if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) {
40167 if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
40168 $$debounce(function() {
40169 scope.select(0, evt);
40170 }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
40172 scope.select(0, evt);
40177 var firstLabel = scope.matches[0].label;
40178 if (inputValue.length > 0 && firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) {
40179 hintInputElem.val(inputValue + firstLabel.slice(inputValue.length));
40182 hintInputElem.val('');
40187 isNoResultsSetter(originalScope, true);
40190 if (onCurrentRequest) {
40191 isLoadingSetter(originalScope, false);
40195 isLoadingSetter(originalScope, false);
40196 isNoResultsSetter(originalScope, true);
40200 // bind events only if appendToBody params exist - performance feature
40201 if (appendToBody) {
40202 angular.element($window).on('resize', fireRecalculating);
40203 $document.find('body').on('scroll', fireRecalculating);
40206 // Declare the debounced function outside recalculating for
40207 // proper debouncing
40208 var debouncedRecalculate = $$debounce(function() {
40209 // if popup is visible
40210 if (scope.matches.length) {
40211 recalculatePosition();
40214 scope.moveInProgress = false;
40215 }, eventDebounceTime);
40217 // Default progress type
40218 scope.moveInProgress = false;
40220 function fireRecalculating() {
40221 if (!scope.moveInProgress) {
40222 scope.moveInProgress = true;
40226 debouncedRecalculate();
40229 // recalculate actual position and set new values to scope
40230 // after digest loop is popup in right position
40231 function recalculatePosition() {
40232 scope.position = appendToBody ? $position.offset(element) : $position.position(element);
40233 scope.position.top += element.prop('offsetHeight');
40236 //we need to propagate user's query so we can higlight matches
40237 scope.query = undefined;
40239 //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
40240 var timeoutPromise;
40242 var scheduleSearchWithTimeout = function(inputValue) {
40243 timeoutPromise = $timeout(function() {
40244 getMatchesAsync(inputValue);
40248 var cancelPreviousTimeout = function() {
40249 if (timeoutPromise) {
40250 $timeout.cancel(timeoutPromise);
40256 scope.assignIsOpen = function (isOpen) {
40257 isOpenSetter(originalScope, isOpen);
40260 scope.select = function(activeIdx, evt) {
40261 //called from within the $digest() cycle
40266 locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
40267 model = parserResult.modelMapper(originalScope, locals);
40268 $setModelValue(originalScope, model);
40269 modelCtrl.$setValidity('editable', true);
40270 modelCtrl.$setValidity('parse', true);
40272 onSelectCallback(originalScope, {
40275 $label: parserResult.viewMapper(originalScope, locals),
40281 //return focus to the input element if a match was selected via a mouse click event
40282 // use timeout to avoid $rootScope:inprog error
40283 if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) {
40284 $timeout(function() { element[0].focus(); }, 0, false);
40288 //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
40289 element.on('keydown', function(evt) {
40290 //typeahead is open and an "interesting" key was pressed
40291 if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
40295 // if there's nothing selected (i.e. focusFirst) and enter or tab is hit, clear the results
40296 if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13)) {
40302 evt.preventDefault();
40304 switch (evt.which) {
40307 scope.$apply(function () {
40308 if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
40309 $$debounce(function() {
40310 scope.select(scope.activeIdx, evt);
40311 }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
40313 scope.select(scope.activeIdx, evt);
40318 evt.stopPropagation();
40324 scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
40326 popUpEl.find('li')[scope.activeIdx].scrollIntoView(false);
40329 scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
40331 popUpEl.find('li')[scope.activeIdx].scrollIntoView(false);
40336 element.bind('focus', function (evt) {
40338 if (minLength === 0 && !modelCtrl.$viewValue) {
40339 $timeout(function() {
40340 getMatchesAsync(modelCtrl.$viewValue, evt);
40345 element.bind('blur', function(evt) {
40346 if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) {
40348 scope.$apply(function() {
40349 if (angular.isObject(scope.debounceUpdate) && angular.isNumber(scope.debounceUpdate.blur)) {
40350 $$debounce(function() {
40351 scope.select(scope.activeIdx, evt);
40352 }, scope.debounceUpdate.blur);
40354 scope.select(scope.activeIdx, evt);
40358 if (!isEditable && modelCtrl.$error.editable) {
40359 modelCtrl.$viewValue = '';
40366 // Keep reference to click handler to unbind it.
40367 var dismissClickHandler = function(evt) {
40369 // Firefox treats right click as a click on document
40370 if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) {
40372 if (!$rootScope.$$phase) {
40378 $document.on('click', dismissClickHandler);
40380 originalScope.$on('$destroy', function() {
40381 $document.off('click', dismissClickHandler);
40382 if (appendToBody || appendTo) {
40386 if (appendToBody) {
40387 angular.element($window).off('resize', fireRecalculating);
40388 $document.find('body').off('scroll', fireRecalculating);
40390 // Prevent jQuery cache memory leak
40394 inputsContainer.remove();
40398 var $popup = $compile(popUpEl)(scope);
40400 if (appendToBody) {
40401 $document.find('body').append($popup);
40402 } else if (appendTo) {
40403 angular.element(appendTo).eq(0).append($popup);
40405 element.after($popup);
40408 this.init = function(_modelCtrl, _ngModelOptions) {
40409 modelCtrl = _modelCtrl;
40410 ngModelOptions = _ngModelOptions;
40412 scope.debounceUpdate = modelCtrl.$options && $parse(modelCtrl.$options.debounce)(originalScope);
40414 //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
40415 //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
40416 modelCtrl.$parsers.unshift(function(inputValue) {
40419 if (minLength === 0 || inputValue && inputValue.length >= minLength) {
40420 if (waitTime > 0) {
40421 cancelPreviousTimeout();
40422 scheduleSearchWithTimeout(inputValue);
40424 getMatchesAsync(inputValue);
40427 isLoadingSetter(originalScope, false);
40428 cancelPreviousTimeout();
40437 // Reset in case user had typed something previously.
40438 modelCtrl.$setValidity('editable', true);
40442 modelCtrl.$setValidity('editable', false);
40446 modelCtrl.$formatters.push(function(modelValue) {
40447 var candidateViewValue, emptyViewValue;
40450 // The validity may be set to false via $parsers (see above) if
40451 // the model is restricted to selected values. If the model
40452 // is set manually it is considered to be valid.
40454 modelCtrl.$setValidity('editable', true);
40457 if (inputFormatter) {
40458 locals.$model = modelValue;
40459 return inputFormatter(originalScope, locals);
40462 //it might happen that we don't have enough info to properly render input value
40463 //we need to check for this situation and simply return model value if we can't apply custom formatting
40464 locals[parserResult.itemName] = modelValue;
40465 candidateViewValue = parserResult.viewMapper(originalScope, locals);
40466 locals[parserResult.itemName] = undefined;
40467 emptyViewValue = parserResult.viewMapper(originalScope, locals);
40469 return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;
40474 .directive('uibTypeahead', function() {
40476 controller: 'UibTypeaheadController',
40477 require: ['ngModel', '^?ngModelOptions', 'uibTypeahead'],
40478 link: function(originalScope, element, attrs, ctrls) {
40479 ctrls[2].init(ctrls[0], ctrls[1]);
40484 .directive('uibTypeaheadPopup', ['$$debounce', function($$debounce) {
40491 moveInProgress: '=',
40497 templateUrl: function(element, attrs) {
40498 return attrs.popupTemplateUrl || 'uib/template/typeahead/typeahead-popup.html';
40500 link: function(scope, element, attrs) {
40501 scope.templateUrl = attrs.templateUrl;
40503 scope.isOpen = function() {
40504 var isDropdownOpen = scope.matches.length > 0;
40505 scope.assignIsOpen({ isOpen: isDropdownOpen });
40506 return isDropdownOpen;
40509 scope.isActive = function(matchIdx) {
40510 return scope.active === matchIdx;
40513 scope.selectActive = function(matchIdx) {
40514 scope.active = matchIdx;
40517 scope.selectMatch = function(activeIdx, evt) {
40518 var debounce = scope.debounce();
40519 if (angular.isNumber(debounce) || angular.isObject(debounce)) {
40520 $$debounce(function() {
40521 scope.select({activeIdx: activeIdx, evt: evt});
40522 }, angular.isNumber(debounce) ? debounce : debounce['default']);
40524 scope.select({activeIdx: activeIdx, evt: evt});
40531 .directive('uibTypeaheadMatch', ['$templateRequest', '$compile', '$parse', function($templateRequest, $compile, $parse) {
40538 link: function(scope, element, attrs) {
40539 var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'uib/template/typeahead/typeahead-match.html';
40540 $templateRequest(tplUrl).then(function(tplContent) {
40541 var tplEl = angular.element(tplContent.trim());
40542 element.replaceWith(tplEl);
40543 $compile(tplEl)(scope);
40549 .filter('uibTypeaheadHighlight', ['$sce', '$injector', '$log', function($sce, $injector, $log) {
40550 var isSanitizePresent;
40551 isSanitizePresent = $injector.has('$sanitize');
40553 function escapeRegexp(queryToEscape) {
40554 // Regex: capture the whole query string and replace it with the string that will be used to match
40555 // the results, for example if the capture is "a" the result will be \a
40556 return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
40559 function containsHtml(matchItem) {
40560 return /<.*>/g.test(matchItem);
40563 return function(matchItem, query) {
40564 if (!isSanitizePresent && containsHtml(matchItem)) {
40565 $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger
40567 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
40568 if (!isSanitizePresent) {
40569 matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive
40575 angular.module("uib/template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) {
40576 $templateCache.put("uib/template/accordion/accordion-group.html",
40577 "<div class=\"panel\" ng-class=\"panelClass || 'panel-default'\">\n" +
40578 " <div class=\"panel-heading\" ng-keypress=\"toggleOpen($event)\">\n" +
40579 " <h4 class=\"panel-title\">\n" +
40580 " <div tabindex=\"0\" class=\"accordion-toggle\" ng-click=\"toggleOpen()\" uib-accordion-transclude=\"heading\"><span ng-class=\"{'text-muted': isDisabled}\">{{heading}}</span></div>\n" +
40583 " <div class=\"panel-collapse collapse\" uib-collapse=\"!isOpen\">\n" +
40584 " <div class=\"panel-body\" ng-transclude></div>\n" +
40590 angular.module("uib/template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) {
40591 $templateCache.put("uib/template/accordion/accordion.html",
40592 "<div class=\"panel-group\" ng-transclude></div>");
40595 angular.module("uib/template/alert/alert.html", []).run(["$templateCache", function($templateCache) {
40596 $templateCache.put("uib/template/alert/alert.html",
40597 "<div class=\"alert\" ng-class=\"['alert-' + (type || 'warning'), closeable ? 'alert-dismissible' : null]\" role=\"alert\">\n" +
40598 " <button ng-show=\"closeable\" type=\"button\" class=\"close\" ng-click=\"close({$event: $event})\">\n" +
40599 " <span aria-hidden=\"true\">×</span>\n" +
40600 " <span class=\"sr-only\">Close</span>\n" +
40602 " <div ng-transclude></div>\n" +
40607 angular.module("uib/template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) {
40608 $templateCache.put("uib/template/carousel/carousel.html",
40609 "<div ng-mouseenter=\"pause()\" ng-mouseleave=\"play()\" class=\"carousel\" ng-swipe-right=\"prev()\" ng-swipe-left=\"next()\">\n" +
40610 " <div class=\"carousel-inner\" ng-transclude></div>\n" +
40611 " <a role=\"button\" href class=\"left carousel-control\" ng-click=\"prev()\" ng-show=\"slides.length > 1\">\n" +
40612 " <span aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-left\"></span>\n" +
40613 " <span class=\"sr-only\">previous</span>\n" +
40615 " <a role=\"button\" href class=\"right carousel-control\" ng-click=\"next()\" ng-show=\"slides.length > 1\">\n" +
40616 " <span aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-right\"></span>\n" +
40617 " <span class=\"sr-only\">next</span>\n" +
40619 " <ol class=\"carousel-indicators\" ng-show=\"slides.length > 1\">\n" +
40620 " <li ng-repeat=\"slide in slides | orderBy:indexOfSlide track by $index\" ng-class=\"{ active: isActive(slide) }\" ng-click=\"select(slide)\">\n" +
40621 " <span class=\"sr-only\">slide {{ $index + 1 }} of {{ slides.length }}<span ng-if=\"isActive(slide)\">, currently active</span></span>\n" +
40627 angular.module("uib/template/carousel/slide.html", []).run(["$templateCache", function($templateCache) {
40628 $templateCache.put("uib/template/carousel/slide.html",
40629 "<div ng-class=\"{\n" +
40630 " 'active': active\n" +
40631 " }\" class=\"item text-center\" ng-transclude></div>\n" +
40635 angular.module("uib/template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) {
40636 $templateCache.put("uib/template/datepicker/datepicker.html",
40637 "<div class=\"uib-datepicker\" ng-switch=\"datepickerMode\" role=\"application\" ng-keydown=\"keydown($event)\">\n" +
40638 " <uib-daypicker ng-switch-when=\"day\" tabindex=\"0\"></uib-daypicker>\n" +
40639 " <uib-monthpicker ng-switch-when=\"month\" tabindex=\"0\"></uib-monthpicker>\n" +
40640 " <uib-yearpicker ng-switch-when=\"year\" tabindex=\"0\"></uib-yearpicker>\n" +
40644 angular.module("uib/template/datepicker/day.html", []).run(["$templateCache", function($templateCache) {
40645 $templateCache.put("uib/template/datepicker/day.html",
40646 "<table class=\"uib-daypicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
40649 " <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" +
40650 " <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" +
40651 " <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" +
40654 " <th ng-if=\"showWeeks\" class=\"text-center\"></th>\n" +
40655 " <th ng-repeat=\"label in ::labels track by $index\" class=\"text-center\"><small aria-label=\"{{::label.full}}\">{{::label.abbr}}</small></th>\n" +
40659 " <tr class=\"uib-weeks\" ng-repeat=\"row in rows track by $index\">\n" +
40660 " <td ng-if=\"showWeeks\" class=\"text-center h6\"><em>{{ weekNumbers[$index] }}</em></td>\n" +
40661 " <td ng-repeat=\"dt in row\" class=\"uib-day text-center\" role=\"gridcell\"\n" +
40662 " id=\"{{::dt.uid}}\"\n" +
40663 " ng-class=\"::dt.customClass\">\n" +
40664 " <button type=\"button\" style=\"min-width:100%;\" class=\"btn btn-default btn-sm\"\n" +
40665 " uib-is-class=\"\n" +
40666 " 'btn-info' for selectedDt,\n" +
40667 " 'active' for activeDt\n" +
40669 " ng-click=\"select(dt.date)\"\n" +
40670 " ng-disabled=\"::dt.disabled\"\n" +
40671 " tabindex=\"-1\"><span ng-class=\"::{'text-muted': dt.secondary, 'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
40679 angular.module("uib/template/datepicker/month.html", []).run(["$templateCache", function($templateCache) {
40680 $templateCache.put("uib/template/datepicker/month.html",
40681 "<table class=\"uib-monthpicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
40684 " <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" +
40685 " <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" +
40686 " <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" +
40690 " <tr class=\"uib-months\" ng-repeat=\"row in rows track by $index\">\n" +
40691 " <td ng-repeat=\"dt in row\" class=\"uib-month text-center\" role=\"gridcell\"\n" +
40692 " id=\"{{::dt.uid}}\"\n" +
40693 " ng-class=\"::dt.customClass\">\n" +
40694 " <button type=\"button\" style=\"min-width:100%;\" class=\"btn btn-default\"\n" +
40695 " uib-is-class=\"\n" +
40696 " 'btn-info' for selectedDt,\n" +
40697 " 'active' for activeDt\n" +
40699 " ng-click=\"select(dt.date)\"\n" +
40700 " ng-disabled=\"::dt.disabled\"\n" +
40701 " tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
40709 angular.module("uib/template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) {
40710 $templateCache.put("uib/template/datepicker/popup.html",
40711 "<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" +
40712 " <li ng-transclude></li>\n" +
40713 " <li ng-if=\"showButtonBar\" style=\"padding:10px 9px 2px\" class=\"uib-button-bar\">\n" +
40714 " <span class=\"btn-group pull-left\">\n" +
40715 " <button type=\"button\" class=\"btn btn-sm btn-info uib-datepicker-current\" ng-click=\"select('today')\" ng-disabled=\"isDisabled('today')\">{{ getText('current') }}</button>\n" +
40716 " <button type=\"button\" class=\"btn btn-sm btn-danger uib-clear\" ng-click=\"select(null)\">{{ getText('clear') }}</button>\n" +
40718 " <button type=\"button\" class=\"btn btn-sm btn-success pull-right uib-close\" ng-click=\"close()\">{{ getText('close') }}</button>\n" +
40724 angular.module("uib/template/datepicker/year.html", []).run(["$templateCache", function($templateCache) {
40725 $templateCache.put("uib/template/datepicker/year.html",
40726 "<table class=\"uib-yearpicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
40729 " <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" +
40730 " <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" +
40731 " <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" +
40735 " <tr class=\"uib-years\" ng-repeat=\"row in rows track by $index\">\n" +
40736 " <td ng-repeat=\"dt in row\" class=\"uib-year text-center\" role=\"gridcell\"\n" +
40737 " id=\"{{::dt.uid}}\"\n" +
40738 " ng-class=\"::dt.customClass\">\n" +
40739 " <button type=\"button\" style=\"min-width:100%;\" class=\"btn btn-default\"\n" +
40740 " uib-is-class=\"\n" +
40741 " 'btn-info' for selectedDt,\n" +
40742 " 'active' for activeDt\n" +
40744 " ng-click=\"select(dt.date)\"\n" +
40745 " ng-disabled=\"::dt.disabled\"\n" +
40746 " tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
40754 angular.module("uib/template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) {
40755 $templateCache.put("uib/template/modal/backdrop.html",
40756 "<div class=\"modal-backdrop\"\n" +
40757 " uib-modal-animation-class=\"fade\"\n" +
40758 " modal-in-class=\"in\"\n" +
40759 " ng-style=\"{'z-index': 1040 + (index && 1 || 0) + index*10}\"\n" +
40764 angular.module("uib/template/modal/window.html", []).run(["$templateCache", function($templateCache) {
40765 $templateCache.put("uib/template/modal/window.html",
40766 "<div modal-render=\"{{$isRendered}}\" tabindex=\"-1\" role=\"dialog\" class=\"modal\"\n" +
40767 " uib-modal-animation-class=\"fade\"\n" +
40768 " modal-in-class=\"in\"\n" +
40769 " ng-style=\"{'z-index': 1050 + index*10, display: 'block'}\">\n" +
40770 " <div class=\"modal-dialog\" ng-class=\"size ? 'modal-' + size : ''\"><div class=\"modal-content\" uib-modal-transclude></div></div>\n" +
40775 angular.module("uib/template/pager/pager.html", []).run(["$templateCache", function($templateCache) {
40776 $templateCache.put("uib/template/pager/pager.html",
40777 "<ul class=\"pager\">\n" +
40778 " <li ng-class=\"{disabled: noPrevious()||ngDisabled, previous: align}\"><a href ng-click=\"selectPage(page - 1, $event)\">{{::getText('previous')}}</a></li>\n" +
40779 " <li ng-class=\"{disabled: noNext()||ngDisabled, next: align}\"><a href ng-click=\"selectPage(page + 1, $event)\">{{::getText('next')}}</a></li>\n" +
40784 angular.module("uib/template/pagination/pager.html", []).run(["$templateCache", function($templateCache) {
40785 $templateCache.put("uib/template/pagination/pager.html",
40786 "<ul class=\"pager\">\n" +
40787 " <li ng-class=\"{disabled: noPrevious()||ngDisabled, previous: align}\"><a href ng-click=\"selectPage(page - 1, $event)\">{{::getText('previous')}}</a></li>\n" +
40788 " <li ng-class=\"{disabled: noNext()||ngDisabled, next: align}\"><a href ng-click=\"selectPage(page + 1, $event)\">{{::getText('next')}}</a></li>\n" +
40793 angular.module("uib/template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) {
40794 $templateCache.put("uib/template/pagination/pagination.html",
40795 "<ul class=\"pagination\">\n" +
40796 " <li ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noPrevious()||ngDisabled}\" class=\"pagination-first\"><a href ng-click=\"selectPage(1, $event)\">{{::getText('first')}}</a></li>\n" +
40797 " <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" +
40798 " <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" +
40799 " <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" +
40800 " <li ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noNext()||ngDisabled}\" class=\"pagination-last\"><a href ng-click=\"selectPage(totalPages, $event)\">{{::getText('last')}}</a></li>\n" +
40805 angular.module("uib/template/tooltip/tooltip-html-popup.html", []).run(["$templateCache", function($templateCache) {
40806 $templateCache.put("uib/template/tooltip/tooltip-html-popup.html",
40807 "<div class=\"tooltip\"\n" +
40808 " tooltip-animation-class=\"fade\"\n" +
40809 " uib-tooltip-classes\n" +
40810 " ng-class=\"{ in: isOpen() }\">\n" +
40811 " <div class=\"tooltip-arrow\"></div>\n" +
40812 " <div class=\"tooltip-inner\" ng-bind-html=\"contentExp()\"></div>\n" +
40817 angular.module("uib/template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) {
40818 $templateCache.put("uib/template/tooltip/tooltip-popup.html",
40819 "<div class=\"tooltip\"\n" +
40820 " tooltip-animation-class=\"fade\"\n" +
40821 " uib-tooltip-classes\n" +
40822 " ng-class=\"{ in: isOpen() }\">\n" +
40823 " <div class=\"tooltip-arrow\"></div>\n" +
40824 " <div class=\"tooltip-inner\" ng-bind=\"content\"></div>\n" +
40829 angular.module("uib/template/tooltip/tooltip-template-popup.html", []).run(["$templateCache", function($templateCache) {
40830 $templateCache.put("uib/template/tooltip/tooltip-template-popup.html",
40831 "<div class=\"tooltip\"\n" +
40832 " tooltip-animation-class=\"fade\"\n" +
40833 " uib-tooltip-classes\n" +
40834 " ng-class=\"{ in: isOpen() }\">\n" +
40835 " <div class=\"tooltip-arrow\"></div>\n" +
40836 " <div class=\"tooltip-inner\"\n" +
40837 " uib-tooltip-template-transclude=\"contentExp()\"\n" +
40838 " tooltip-template-transclude-scope=\"originScope()\"></div>\n" +
40843 angular.module("uib/template/popover/popover-html.html", []).run(["$templateCache", function($templateCache) {
40844 $templateCache.put("uib/template/popover/popover-html.html",
40845 "<div class=\"popover\"\n" +
40846 " tooltip-animation-class=\"fade\"\n" +
40847 " uib-tooltip-classes\n" +
40848 " ng-class=\"{ in: isOpen() }\">\n" +
40849 " <div class=\"arrow\"></div>\n" +
40851 " <div class=\"popover-inner\">\n" +
40852 " <h3 class=\"popover-title\" ng-bind=\"title\" ng-if=\"title\"></h3>\n" +
40853 " <div class=\"popover-content\" ng-bind-html=\"contentExp()\"></div>\n" +
40859 angular.module("uib/template/popover/popover-template.html", []).run(["$templateCache", function($templateCache) {
40860 $templateCache.put("uib/template/popover/popover-template.html",
40861 "<div class=\"popover\"\n" +
40862 " tooltip-animation-class=\"fade\"\n" +
40863 " uib-tooltip-classes\n" +
40864 " ng-class=\"{ in: isOpen() }\">\n" +
40865 " <div class=\"arrow\"></div>\n" +
40867 " <div class=\"popover-inner\">\n" +
40868 " <h3 class=\"popover-title\" ng-bind=\"title\" ng-if=\"title\"></h3>\n" +
40869 " <div class=\"popover-content\"\n" +
40870 " uib-tooltip-template-transclude=\"contentExp()\"\n" +
40871 " tooltip-template-transclude-scope=\"originScope()\"></div>\n" +
40877 angular.module("uib/template/popover/popover.html", []).run(["$templateCache", function($templateCache) {
40878 $templateCache.put("uib/template/popover/popover.html",
40879 "<div class=\"popover\"\n" +
40880 " tooltip-animation-class=\"fade\"\n" +
40881 " uib-tooltip-classes\n" +
40882 " ng-class=\"{ in: isOpen() }\">\n" +
40883 " <div class=\"arrow\"></div>\n" +
40885 " <div class=\"popover-inner\">\n" +
40886 " <h3 class=\"popover-title\" ng-bind=\"title\" ng-if=\"title\"></h3>\n" +
40887 " <div class=\"popover-content\" ng-bind=\"content\"></div>\n" +
40893 angular.module("uib/template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) {
40894 $templateCache.put("uib/template/progressbar/bar.html",
40895 "<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" +
40899 angular.module("uib/template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) {
40900 $templateCache.put("uib/template/progressbar/progress.html",
40901 "<div class=\"progress\" ng-transclude aria-labelledby=\"{{::title}}\"></div>");
40904 angular.module("uib/template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) {
40905 $templateCache.put("uib/template/progressbar/progressbar.html",
40906 "<div class=\"progress\">\n" +
40907 " <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" +
40912 angular.module("uib/template/rating/rating.html", []).run(["$templateCache", function($templateCache) {
40913 $templateCache.put("uib/template/rating/rating.html",
40914 "<span ng-mouseleave=\"reset()\" ng-keydown=\"onKeydown($event)\" tabindex=\"0\" role=\"slider\" aria-valuemin=\"0\" aria-valuemax=\"{{range.length}}\" aria-valuenow=\"{{value}}\">\n" +
40915 " <span ng-repeat-start=\"r in range track by $index\" class=\"sr-only\">({{ $index < value ? '*' : ' ' }})</span>\n" +
40916 " <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" +
40921 angular.module("uib/template/tabs/tab.html", []).run(["$templateCache", function($templateCache) {
40922 $templateCache.put("uib/template/tabs/tab.html",
40923 "<li ng-class=\"{active: active, disabled: disabled}\" class=\"uib-tab\">\n" +
40924 " <div ng-click=\"select()\" uib-tab-heading-transclude>{{heading}}</div>\n" +
40929 angular.module("uib/template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) {
40930 $templateCache.put("uib/template/tabs/tabset.html",
40932 " <ul class=\"nav nav-{{type || 'tabs'}}\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" +
40933 " <div class=\"tab-content\">\n" +
40934 " <div class=\"tab-pane\" \n" +
40935 " ng-repeat=\"tab in tabs\" \n" +
40936 " ng-class=\"{active: tab.active}\"\n" +
40937 " uib-tab-content-transclude=\"tab\">\n" +
40944 angular.module("uib/template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) {
40945 $templateCache.put("uib/template/timepicker/timepicker.html",
40946 "<table class=\"uib-timepicker\">\n" +
40948 " <tr class=\"text-center\" ng-show=\"::showSpinners\">\n" +
40949 " <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" +
40950 " <td> </td>\n" +
40951 " <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" +
40952 " <td ng-show=\"showSeconds\"> </td>\n" +
40953 " <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" +
40954 " <td ng-show=\"showMeridian\"></td>\n" +
40957 " <td class=\"form-group uib-time hours\" ng-class=\"{'has-error': invalidHours}\">\n" +
40958 " <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" +
40960 " <td class=\"uib-separator\">:</td>\n" +
40961 " <td class=\"form-group uib-time minutes\" ng-class=\"{'has-error': invalidMinutes}\">\n" +
40962 " <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" +
40964 " <td ng-show=\"showSeconds\" class=\"uib-separator\">:</td>\n" +
40965 " <td class=\"form-group uib-time seconds\" ng-class=\"{'has-error': invalidSeconds}\" ng-show=\"showSeconds\">\n" +
40966 " <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" +
40968 " <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" +
40970 " <tr class=\"text-center\" ng-show=\"::showSpinners\">\n" +
40971 " <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" +
40972 " <td> </td>\n" +
40973 " <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" +
40974 " <td ng-show=\"showSeconds\"> </td>\n" +
40975 " <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" +
40976 " <td ng-show=\"showMeridian\"></td>\n" +
40983 angular.module("uib/template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) {
40984 $templateCache.put("uib/template/typeahead/typeahead-match.html",
40985 "<a href tabindex=\"-1\" ng-bind-html=\"match.label | uibTypeaheadHighlight:query\"></a>\n" +
40989 angular.module("uib/template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) {
40990 $templateCache.put("uib/template/typeahead/typeahead-popup.html",
40991 "<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" +
40992 " <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" +
40993 " <div uib-typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>\n" +
40998 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>'); })
41002 /***/ function(module, exports) {
41007 (function (declares) {
41008 var CommandInfo = (function () {
41009 function CommandInfo(name) {
41012 return CommandInfo;
41014 declares.CommandInfo = CommandInfo;
41015 })(declares = app.declares || (app.declares = {}));
41016 })(app || (app = {}));
41020 (function (services) {
41021 var APIEndPoint = (function () {
41022 function APIEndPoint($resource, $http) {
41023 this.$resource = $resource;
41024 this.$http = $http;
41026 APIEndPoint.prototype.resource = function (endPoint, data) {
41027 var customAction = {
41033 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
41035 return this.$resource(endPoint, {}, { execute: execute });
41037 APIEndPoint.prototype.getOptionControlFile = function (command) {
41038 var endPoint = '/api/v1/optionControlFile/' + command;
41039 return this.resource(endPoint, {}).get();
41041 APIEndPoint.prototype.getFiles = function (fileId) {
41042 var endPoint = '/api/v1/workspace';
41044 endPoint += '/' + fileId;
41046 return this.resource(endPoint, {}).get();
41048 APIEndPoint.prototype.getDirectories = function () {
41049 var endPoint = '/api/v1/all/workspace/directory';
41050 return this.resource(endPoint, {}).get();
41052 APIEndPoint.prototype.getTags = function () {
41053 var endPoint = '/api/v1/tagList';
41054 return this.resource(endPoint, {}).get();
41056 APIEndPoint.prototype.getCommands = function () {
41057 var endPoint = '/api/v1/commandList';
41058 return this.resource(endPoint, {}).get();
41060 APIEndPoint.prototype.execute = function (data) {
41061 var endPoint = '/api/v1/execution';
41062 var fd = new FormData();
41063 fd.append('data', data);
41064 return this.$http.post(endPoint, fd, {
41065 headers: { 'Content-Type': undefined },
41066 transformRequest: angular.identity
41069 APIEndPoint.prototype.debug = function () {
41070 var endPoint = '/api/v1/debug';
41071 return this.$http.get(endPoint);
41073 APIEndPoint.prototype.help = function (command) {
41074 var endPoint = '/api/v1/help/' + command;
41075 return this.$http.get(endPoint);
41077 return APIEndPoint;
41079 services.APIEndPoint = APIEndPoint;
41080 })(services = app.services || (app.services = {}));
41081 })(app || (app = {}));
41085 (function (services) {
41086 var MyModal = (function () {
41087 function MyModal($uibModal) {
41088 this.$uibModal = $uibModal;
41089 this.modalOption = {
41096 MyModal.prototype.open = function (modalName) {
41097 if (modalName === 'SelectCommand') {
41098 this.modalOption.templateUrl = 'templates/select-command.html';
41099 this.modalOption.size = 'lg';
41101 return this.$uibModal.open(this.modalOption);
41103 MyModal.prototype.selectCommand = function () {
41104 this.modalOption.templateUrl = 'templates/select-command.html';
41105 this.modalOption.controller = 'selectCommandController';
41106 this.modalOption.controllerAs = 'c';
41107 this.modalOption.size = 'lg';
41108 return this.$uibModal.open(this.modalOption);
41110 MyModal.prototype.preview = function () {
41111 this.modalOption.templateUrl = 'templates/preview.html';
41112 this.modalOption.controller = 'previewController';
41113 this.modalOption.controllerAs = 'c';
41114 this.modalOption.size = 'lg';
41115 return this.$uibModal.open(this.modalOption);
41117 MyModal.$inject = ['$uibModal'];
41120 services.MyModal = MyModal;
41121 })(services = app.services || (app.services = {}));
41122 })(app || (app = {}));
41126 (function (services) {
41127 var WebSocket = (function () {
41128 function WebSocket($rootScope) {
41129 this.$rootScope = $rootScope;
41130 this.socket = io.connect();
41132 WebSocket.prototype.on = function (eventName, callback) {
41133 var socket = this.socket;
41134 var rootScope = this.$rootScope;
41135 socket.on(eventName, function () {
41136 var args = arguments;
41137 rootScope.$apply(function () {
41138 callback.apply(socket, args);
41142 WebSocket.prototype.emit = function (eventName, data, callback) {
41143 var socket = this.socket;
41144 var rootScope = this.$rootScope;
41145 this.socket.emit(eventName, data, function () {
41146 var args = arguments;
41147 rootScope.$apply(function () {
41149 callback.apply(socket, args);
41155 services.WebSocket = WebSocket;
41156 })(services = app.services || (app.services = {}));
41157 })(app || (app = {}));
41161 (function (directives) {
41162 var Command = (function () {
41163 function Command() {
41164 this.restrict = 'E';
41165 this.replace = true;
41167 this.controller = 'commandController';
41168 this.controllerAs = 'ctrl';
41169 this.bindToController = {
41175 this.templateUrl = 'templates/command.html';
41177 Command.Factory = function () {
41178 var directive = function () {
41179 return new Command();
41181 directive.$inject = [];
41186 directives.Command = Command;
41187 var CommandController = (function () {
41188 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
41189 this.APIEndPoint = APIEndPoint;
41190 this.$scope = $scope;
41191 this.MyModal = MyModal;
41192 this.WebSocket = WebSocket;
41193 var controller = this;
41195 .getOptionControlFile(this.name)
41197 .then(function (result) {
41198 controller.options = result.info;
41203 .then(function (result) {
41204 controller.dirs = result.info;
41206 this.heading = "[" + this.index + "]: dcdFilePrint";
41207 this.isOpen = true;
41208 this.$scope.$on('close', function () {
41209 controller.isOpen = false;
41211 this.WebSocket.on('console', function (msg) {
41212 controller.messages = msg.split('\n');
41215 CommandController.prototype.submit = function () {
41217 angular.forEach(this.options, function (option) {
41219 name: option.option,
41222 angular.forEach(option.arg, function (arg) {
41224 if (typeof arg.input === 'object') {
41225 obj.arguments.push(arg.input.name);
41228 obj.arguments.push(arg.input);
41232 if (obj.arguments.length > 0) {
41237 command: this.name,
41238 workspace: this.workspace.fileId,
41242 .execute(JSON.stringify(execObj))
41243 .then(function (result) {
41244 console.log(result);
41247 CommandController.prototype.removeMySelf = function (index) {
41248 this.remove()(index, this.list);
41250 CommandController.prototype.reloadFiles = function () {
41252 var fileId = this.workspace.fileId;
41256 .then(function (result) {
41257 var status = result.status;
41258 if (status === 'success') {
41259 _this.files = result.info;
41262 console.log(result.message);
41266 CommandController.prototype.debug = function () {
41269 .then(function (result) {
41270 console.log(result);
41273 CommandController.prototype.help = function () {
41276 .then(function (result) {
41277 console.log(result);
41280 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
41281 return CommandController;
41283 directives.CommandController = CommandController;
41284 })(directives = app.directives || (app.directives = {}));
41285 })(app || (app = {}));
41289 (function (directives) {
41290 var HeaderMenu = (function () {
41291 function HeaderMenu() {
41292 this.restrict = 'E';
41293 this.replace = true;
41294 this.templateUrl = 'templates/header-menu.html';
41296 HeaderMenu.Factory = function () {
41297 var directive = function () {
41298 return new HeaderMenu();
41304 directives.HeaderMenu = HeaderMenu;
41305 })(directives = app.directives || (app.directives = {}));
41306 })(app || (app = {}));
41310 (function (directives) {
41311 var Option = (function () {
41312 function Option() {
41313 this.restrict = 'E';
41314 this.replace = true;
41315 this.controller = 'optionController';
41316 this.bindToController = {
41321 this.templateUrl = 'templates/option.html';
41322 this.controllerAs = 'ctrl';
41324 Option.Factory = function () {
41325 var directive = function () {
41326 return new Option();
41328 directive.$inject = [];
41333 directives.Option = Option;
41334 var OptionController = (function () {
41335 function OptionController() {
41336 var controller = this;
41337 angular.forEach(controller.info.arg, function (arg) {
41338 if (arg.initialValue) {
41339 if (arg.formType === 'number') {
41340 arg.input = parseInt(arg.initialValue);
41343 arg.input = arg.initialValue;
41348 OptionController.$inject = [];
41349 return OptionController;
41351 directives.OptionController = OptionController;
41352 })(directives = app.directives || (app.directives = {}));
41353 })(app || (app = {}));
41357 (function (directives) {
41358 var Directory = (function () {
41359 function Directory() {
41360 this.restrict = 'E';
41361 this.replace = true;
41362 this.controller = 'directoryController';
41363 this.controllerAs = 'ctrl';
41364 this.bindToController = {
41370 this.templateUrl = 'templates/directory.html';
41372 Directory.Factory = function () {
41373 var directive = function () {
41374 return new Directory();
41380 directives.Directory = Directory;
41381 var DirectoryController = (function () {
41382 function DirectoryController(APIEndPoint, $scope) {
41383 this.APIEndPoint = APIEndPoint;
41384 this.$scope = $scope;
41385 var controller = this;
41387 .getFiles(this.info.fileId)
41389 .then(function (result) {
41390 if (result.status === 'success') {
41391 controller.files = result.info;
41392 angular.forEach(result.info, function (file) {
41393 if (file.fileType === '0') {
41395 if (controller.info.path === '/') {
41396 o.path = '/' + file.name;
41399 o.path = controller.info.path + '/' + file.name;
41401 controller.add()(o, controller.list);
41408 DirectoryController.$inject = ['APIEndPoint', '$scope'];
41409 return DirectoryController;
41411 directives.DirectoryController = DirectoryController;
41412 })(directives = app.directives || (app.directives = {}));
41413 })(app || (app = {}));
41417 (function (controllers) {
41418 var Execution = (function () {
41419 function Execution(MyModal, $scope) {
41420 this.MyModal = MyModal;
41421 this.$scope = $scope;
41422 this.commandInfoList = [];
41425 Execution.prototype.add = function () {
41426 this.$scope.$broadcast('close');
41427 var commandInfoList = this.commandInfoList;
41428 var commandInstance = this.MyModal.selectCommand();
41431 .then(function (command) {
41432 commandInfoList.push(new app.declares.CommandInfo(command));
41435 Execution.prototype.open = function () {
41436 var result = this.MyModal.open('SelectCommand');
41437 console.log(result);
41439 Execution.prototype.remove = function (index, list) {
41440 list.splice(index, 1);
41442 Execution.prototype.close = function () {
41443 console.log("close");
41445 Execution.$inject = ['MyModal', '$scope'];
41448 controllers.Execution = Execution;
41449 })(controllers = app.controllers || (app.controllers = {}));
41450 })(app || (app = {}));
41454 (function (controllers) {
41455 var Workspace = (function () {
41456 function Workspace($scope, APIEndPoint, MyModal) {
41457 this.$scope = $scope;
41458 this.APIEndPoint = APIEndPoint;
41459 this.MyModal = MyModal;
41460 this.directoryList = [];
41461 var controller = this;
41462 var directoryList = this.directoryList;
41464 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
41472 directoryList.push(o);
41474 Workspace.prototype.addDirectory = function (info, directoryList) {
41475 directoryList.push(info);
41477 Workspace.prototype.debug = function () {
41478 this.MyModal.preview();
41480 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
41483 controllers.Workspace = Workspace;
41484 })(controllers = app.controllers || (app.controllers = {}));
41485 })(app || (app = {}));
41489 (function (controllers) {
41490 var History = (function () {
41491 function History($scope) {
41492 this.page = "History";
41494 History.$inject = ['$scope'];
41497 controllers.History = History;
41498 })(controllers = app.controllers || (app.controllers = {}));
41499 })(app || (app = {}));
41503 (function (controllers) {
41504 var SelectCommand = (function () {
41505 function SelectCommand($scope, APIEndPoint, $modalInstance) {
41506 this.APIEndPoint = APIEndPoint;
41507 this.$modalInstance = $modalInstance;
41508 var controller = this;
41511 .$promise.then(function (result) {
41512 controller.tags = result.info;
41516 .$promise.then(function (result) {
41517 controller.commands = result.info;
41519 this.currentTag = 'all';
41521 SelectCommand.prototype.changeTag = function (tag) {
41522 this.currentTag = tag;
41524 SelectCommand.prototype.selectCommand = function (command) {
41525 this.$modalInstance.close(command);
41527 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
41528 return SelectCommand;
41530 controllers.SelectCommand = SelectCommand;
41531 })(controllers = app.controllers || (app.controllers = {}));
41532 })(app || (app = {}));
41536 (function (controllers) {
41537 var Preview = (function () {
41538 function Preview($scope, APIEndPoint, $modalInstance) {
41539 this.APIEndPoint = APIEndPoint;
41540 this.$modalInstance = $modalInstance;
41541 var controller = this;
41542 console.log('preview');
41544 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
41547 controllers.Preview = Preview;
41548 })(controllers = app.controllers || (app.controllers = {}));
41549 })(app || (app = {}));
41551 (function (filters) {
41553 return function (commands, tag) {
41555 angular.forEach(commands, function (command) {
41557 angular.forEach(command.tags, function (value) {
41562 result.push(command);
41568 })(filters || (filters = {}));
41572 var appName = 'zephyr';
41573 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
41574 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
41575 $urlRouterProvider.otherwise('/execution');
41576 $locationProvider.html5Mode({
41581 .state('execution', {
41583 templateUrl: 'templates/execution.html',
41584 controller: 'executionController',
41587 .state('workspace', {
41589 templateUrl: 'templates/workspace.html',
41590 controller: 'workspaceController',
41593 .state('history', {
41595 templateUrl: 'templates/history.html',
41596 controller: 'historyController',
41600 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
41601 app.zephyr.service('MyModal', app.services.MyModal);
41602 app.zephyr.service('WebSocket', app.services.WebSocket);
41603 app.zephyr.filter('Tag', filters.Tag);
41604 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
41605 app.zephyr.controller('previewController', app.controllers.Preview);
41606 app.zephyr.controller('executionController', app.controllers.Execution);
41607 app.zephyr.controller('workspaceController', app.controllers.Workspace);
41608 app.zephyr.controller('historyController', app.controllers.History);
41609 app.zephyr.controller('commandController', app.directives.CommandController);
41610 app.zephyr.controller('optionController', app.directives.OptionController);
41611 app.zephyr.controller('directoryController', app.directives.DirectoryController);
41612 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
41613 app.zephyr.directive('command', app.directives.Command.Factory());
41614 app.zephyr.directive('option', app.directives.Option.Factory());
41615 app.zephyr.directive('directory', app.directives.Directory.Factory());
41616 })(app || (app = {}));
41621 /***/ function(module, exports) {
41626 (function (declares) {
41627 var CommandInfo = (function () {
41628 function CommandInfo(name) {
41631 return CommandInfo;
41633 declares.CommandInfo = CommandInfo;
41634 })(declares = app.declares || (app.declares = {}));
41635 })(app || (app = {}));
41639 (function (services) {
41640 var APIEndPoint = (function () {
41641 function APIEndPoint($resource, $http) {
41642 this.$resource = $resource;
41643 this.$http = $http;
41645 APIEndPoint.prototype.resource = function (endPoint, data) {
41646 var customAction = {
41652 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
41654 return this.$resource(endPoint, {}, { execute: execute });
41656 APIEndPoint.prototype.getOptionControlFile = function (command) {
41657 var endPoint = '/api/v1/optionControlFile/' + command;
41658 return this.resource(endPoint, {}).get();
41660 APIEndPoint.prototype.getFiles = function (fileId) {
41661 var endPoint = '/api/v1/workspace';
41663 endPoint += '/' + fileId;
41665 return this.resource(endPoint, {}).get();
41667 APIEndPoint.prototype.getDirectories = function () {
41668 var endPoint = '/api/v1/all/workspace/directory';
41669 return this.resource(endPoint, {}).get();
41671 APIEndPoint.prototype.getTags = function () {
41672 var endPoint = '/api/v1/tagList';
41673 return this.resource(endPoint, {}).get();
41675 APIEndPoint.prototype.getCommands = function () {
41676 var endPoint = '/api/v1/commandList';
41677 return this.resource(endPoint, {}).get();
41679 APIEndPoint.prototype.execute = function (data) {
41680 var endPoint = '/api/v1/execution';
41681 var fd = new FormData();
41682 fd.append('data', data);
41683 return this.$http.post(endPoint, fd, {
41684 headers: { 'Content-Type': undefined },
41685 transformRequest: angular.identity
41688 APIEndPoint.prototype.debug = function () {
41689 var endPoint = '/api/v1/debug';
41690 return this.$http.get(endPoint);
41692 APIEndPoint.prototype.help = function (command) {
41693 var endPoint = '/api/v1/help/' + command;
41694 return this.$http.get(endPoint);
41696 return APIEndPoint;
41698 services.APIEndPoint = APIEndPoint;
41699 })(services = app.services || (app.services = {}));
41700 })(app || (app = {}));
41704 (function (services) {
41705 var MyModal = (function () {
41706 function MyModal($uibModal) {
41707 this.$uibModal = $uibModal;
41708 this.modalOption = {
41715 MyModal.prototype.open = function (modalName) {
41716 if (modalName === 'SelectCommand') {
41717 this.modalOption.templateUrl = 'templates/select-command.html';
41718 this.modalOption.size = 'lg';
41720 return this.$uibModal.open(this.modalOption);
41722 MyModal.prototype.selectCommand = function () {
41723 this.modalOption.templateUrl = 'templates/select-command.html';
41724 this.modalOption.controller = 'selectCommandController';
41725 this.modalOption.controllerAs = 'c';
41726 this.modalOption.size = 'lg';
41727 return this.$uibModal.open(this.modalOption);
41729 MyModal.prototype.preview = function () {
41730 this.modalOption.templateUrl = 'templates/preview.html';
41731 this.modalOption.controller = 'previewController';
41732 this.modalOption.controllerAs = 'c';
41733 this.modalOption.size = 'lg';
41734 return this.$uibModal.open(this.modalOption);
41736 MyModal.$inject = ['$uibModal'];
41739 services.MyModal = MyModal;
41740 })(services = app.services || (app.services = {}));
41741 })(app || (app = {}));
41745 (function (services) {
41746 var WebSocket = (function () {
41747 function WebSocket($rootScope) {
41748 this.$rootScope = $rootScope;
41749 this.socket = io.connect();
41751 WebSocket.prototype.on = function (eventName, callback) {
41752 var socket = this.socket;
41753 var rootScope = this.$rootScope;
41754 socket.on(eventName, function () {
41755 var args = arguments;
41756 rootScope.$apply(function () {
41757 callback.apply(socket, args);
41761 WebSocket.prototype.emit = function (eventName, data, callback) {
41762 var socket = this.socket;
41763 var rootScope = this.$rootScope;
41764 this.socket.emit(eventName, data, function () {
41765 var args = arguments;
41766 rootScope.$apply(function () {
41768 callback.apply(socket, args);
41774 services.WebSocket = WebSocket;
41775 })(services = app.services || (app.services = {}));
41776 })(app || (app = {}));
41780 (function (directives) {
41781 var Command = (function () {
41782 function Command() {
41783 this.restrict = 'E';
41784 this.replace = true;
41786 this.controller = 'commandController';
41787 this.controllerAs = 'ctrl';
41788 this.bindToController = {
41794 this.templateUrl = 'templates/command.html';
41796 Command.Factory = function () {
41797 var directive = function () {
41798 return new Command();
41800 directive.$inject = [];
41805 directives.Command = Command;
41806 var CommandController = (function () {
41807 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
41808 this.APIEndPoint = APIEndPoint;
41809 this.$scope = $scope;
41810 this.MyModal = MyModal;
41811 this.WebSocket = WebSocket;
41812 var controller = this;
41814 .getOptionControlFile(this.name)
41816 .then(function (result) {
41817 controller.options = result.info;
41822 .then(function (result) {
41823 controller.dirs = result.info;
41825 this.heading = "[" + this.index + "]: dcdFilePrint";
41826 this.isOpen = true;
41827 this.$scope.$on('close', function () {
41828 controller.isOpen = false;
41830 this.WebSocket.on('console', function (msg) {
41831 controller.messages = msg.split('\n');
41834 CommandController.prototype.submit = function () {
41836 angular.forEach(this.options, function (option) {
41838 name: option.option,
41841 angular.forEach(option.arg, function (arg) {
41843 if (typeof arg.input === 'object') {
41844 obj.arguments.push(arg.input.name);
41847 obj.arguments.push(arg.input);
41851 if (obj.arguments.length > 0) {
41856 command: this.name,
41857 workspace: this.workspace.fileId,
41861 .execute(JSON.stringify(execObj))
41862 .then(function (result) {
41863 console.log(result);
41866 CommandController.prototype.removeMySelf = function (index) {
41867 this.remove()(index, this.list);
41869 CommandController.prototype.reloadFiles = function () {
41871 var fileId = this.workspace.fileId;
41875 .then(function (result) {
41876 var status = result.status;
41877 if (status === 'success') {
41878 _this.files = result.info;
41881 console.log(result.message);
41885 CommandController.prototype.debug = function () {
41888 .then(function (result) {
41889 console.log(result);
41892 CommandController.prototype.help = function () {
41895 .then(function (result) {
41896 console.log(result);
41899 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
41900 return CommandController;
41902 directives.CommandController = CommandController;
41903 })(directives = app.directives || (app.directives = {}));
41904 })(app || (app = {}));
41908 (function (directives) {
41909 var HeaderMenu = (function () {
41910 function HeaderMenu() {
41911 this.restrict = 'E';
41912 this.replace = true;
41913 this.templateUrl = 'templates/header-menu.html';
41915 HeaderMenu.Factory = function () {
41916 var directive = function () {
41917 return new HeaderMenu();
41923 directives.HeaderMenu = HeaderMenu;
41924 })(directives = app.directives || (app.directives = {}));
41925 })(app || (app = {}));
41929 (function (directives) {
41930 var Option = (function () {
41931 function Option() {
41932 this.restrict = 'E';
41933 this.replace = true;
41934 this.controller = 'optionController';
41935 this.bindToController = {
41940 this.templateUrl = 'templates/option.html';
41941 this.controllerAs = 'ctrl';
41943 Option.Factory = function () {
41944 var directive = function () {
41945 return new Option();
41947 directive.$inject = [];
41952 directives.Option = Option;
41953 var OptionController = (function () {
41954 function OptionController() {
41955 var controller = this;
41956 angular.forEach(controller.info.arg, function (arg) {
41957 if (arg.initialValue) {
41958 if (arg.formType === 'number') {
41959 arg.input = parseInt(arg.initialValue);
41962 arg.input = arg.initialValue;
41967 OptionController.$inject = [];
41968 return OptionController;
41970 directives.OptionController = OptionController;
41971 })(directives = app.directives || (app.directives = {}));
41972 })(app || (app = {}));
41976 (function (directives) {
41977 var Directory = (function () {
41978 function Directory() {
41979 this.restrict = 'E';
41980 this.replace = true;
41981 this.controller = 'directoryController';
41982 this.controllerAs = 'ctrl';
41983 this.bindToController = {
41989 this.templateUrl = 'templates/directory.html';
41991 Directory.Factory = function () {
41992 var directive = function () {
41993 return new Directory();
41999 directives.Directory = Directory;
42000 var DirectoryController = (function () {
42001 function DirectoryController(APIEndPoint, $scope) {
42002 this.APIEndPoint = APIEndPoint;
42003 this.$scope = $scope;
42004 var controller = this;
42006 .getFiles(this.info.fileId)
42008 .then(function (result) {
42009 if (result.status === 'success') {
42010 controller.files = result.info;
42011 angular.forEach(result.info, function (file) {
42012 if (file.fileType === '0') {
42014 if (controller.info.path === '/') {
42015 o.path = '/' + file.name;
42018 o.path = controller.info.path + '/' + file.name;
42020 controller.add()(o, controller.list);
42027 DirectoryController.$inject = ['APIEndPoint', '$scope'];
42028 return DirectoryController;
42030 directives.DirectoryController = DirectoryController;
42031 })(directives = app.directives || (app.directives = {}));
42032 })(app || (app = {}));
42036 (function (controllers) {
42037 var Execution = (function () {
42038 function Execution(MyModal, $scope) {
42039 this.MyModal = MyModal;
42040 this.$scope = $scope;
42041 this.commandInfoList = [];
42044 Execution.prototype.add = function () {
42045 this.$scope.$broadcast('close');
42046 var commandInfoList = this.commandInfoList;
42047 var commandInstance = this.MyModal.selectCommand();
42050 .then(function (command) {
42051 commandInfoList.push(new app.declares.CommandInfo(command));
42054 Execution.prototype.open = function () {
42055 var result = this.MyModal.open('SelectCommand');
42056 console.log(result);
42058 Execution.prototype.remove = function (index, list) {
42059 list.splice(index, 1);
42061 Execution.prototype.close = function () {
42062 console.log("close");
42064 Execution.$inject = ['MyModal', '$scope'];
42067 controllers.Execution = Execution;
42068 })(controllers = app.controllers || (app.controllers = {}));
42069 })(app || (app = {}));
42073 (function (controllers) {
42074 var Workspace = (function () {
42075 function Workspace($scope, APIEndPoint, MyModal) {
42076 this.$scope = $scope;
42077 this.APIEndPoint = APIEndPoint;
42078 this.MyModal = MyModal;
42079 this.directoryList = [];
42080 var controller = this;
42081 var directoryList = this.directoryList;
42083 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
42091 directoryList.push(o);
42093 Workspace.prototype.addDirectory = function (info, directoryList) {
42094 directoryList.push(info);
42096 Workspace.prototype.debug = function () {
42097 this.MyModal.preview();
42099 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
42102 controllers.Workspace = Workspace;
42103 })(controllers = app.controllers || (app.controllers = {}));
42104 })(app || (app = {}));
42108 (function (controllers) {
42109 var History = (function () {
42110 function History($scope) {
42111 this.page = "History";
42113 History.$inject = ['$scope'];
42116 controllers.History = History;
42117 })(controllers = app.controllers || (app.controllers = {}));
42118 })(app || (app = {}));
42122 (function (controllers) {
42123 var SelectCommand = (function () {
42124 function SelectCommand($scope, APIEndPoint, $modalInstance) {
42125 this.APIEndPoint = APIEndPoint;
42126 this.$modalInstance = $modalInstance;
42127 var controller = this;
42130 .$promise.then(function (result) {
42131 controller.tags = result.info;
42135 .$promise.then(function (result) {
42136 controller.commands = result.info;
42138 this.currentTag = 'all';
42140 SelectCommand.prototype.changeTag = function (tag) {
42141 this.currentTag = tag;
42143 SelectCommand.prototype.selectCommand = function (command) {
42144 this.$modalInstance.close(command);
42146 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
42147 return SelectCommand;
42149 controllers.SelectCommand = SelectCommand;
42150 })(controllers = app.controllers || (app.controllers = {}));
42151 })(app || (app = {}));
42155 (function (controllers) {
42156 var Preview = (function () {
42157 function Preview($scope, APIEndPoint, $modalInstance) {
42158 this.APIEndPoint = APIEndPoint;
42159 this.$modalInstance = $modalInstance;
42160 var controller = this;
42161 console.log('preview');
42163 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
42166 controllers.Preview = Preview;
42167 })(controllers = app.controllers || (app.controllers = {}));
42168 })(app || (app = {}));
42170 (function (filters) {
42172 return function (commands, tag) {
42174 angular.forEach(commands, function (command) {
42176 angular.forEach(command.tags, function (value) {
42181 result.push(command);
42187 })(filters || (filters = {}));
42191 var appName = 'zephyr';
42192 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
42193 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
42194 $urlRouterProvider.otherwise('/execution');
42195 $locationProvider.html5Mode({
42200 .state('execution', {
42202 templateUrl: 'templates/execution.html',
42203 controller: 'executionController',
42206 .state('workspace', {
42208 templateUrl: 'templates/workspace.html',
42209 controller: 'workspaceController',
42212 .state('history', {
42214 templateUrl: 'templates/history.html',
42215 controller: 'historyController',
42219 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
42220 app.zephyr.service('MyModal', app.services.MyModal);
42221 app.zephyr.service('WebSocket', app.services.WebSocket);
42222 app.zephyr.filter('Tag', filters.Tag);
42223 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
42224 app.zephyr.controller('previewController', app.controllers.Preview);
42225 app.zephyr.controller('executionController', app.controllers.Execution);
42226 app.zephyr.controller('workspaceController', app.controllers.Workspace);
42227 app.zephyr.controller('historyController', app.controllers.History);
42228 app.zephyr.controller('commandController', app.directives.CommandController);
42229 app.zephyr.controller('optionController', app.directives.OptionController);
42230 app.zephyr.controller('directoryController', app.directives.DirectoryController);
42231 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
42232 app.zephyr.directive('command', app.directives.Command.Factory());
42233 app.zephyr.directive('option', app.directives.Option.Factory());
42234 app.zephyr.directive('directory', app.directives.Directory.Factory());
42235 })(app || (app = {}));
42240 /***/ function(module, exports) {
42245 (function (declares) {
42246 var CommandInfo = (function () {
42247 function CommandInfo(name) {
42250 return CommandInfo;
42252 declares.CommandInfo = CommandInfo;
42253 })(declares = app.declares || (app.declares = {}));
42254 })(app || (app = {}));
42258 (function (services) {
42259 var APIEndPoint = (function () {
42260 function APIEndPoint($resource, $http) {
42261 this.$resource = $resource;
42262 this.$http = $http;
42264 APIEndPoint.prototype.resource = function (endPoint, data) {
42265 var customAction = {
42271 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
42273 return this.$resource(endPoint, {}, { execute: execute });
42275 APIEndPoint.prototype.getOptionControlFile = function (command) {
42276 var endPoint = '/api/v1/optionControlFile/' + command;
42277 return this.resource(endPoint, {}).get();
42279 APIEndPoint.prototype.getFiles = function (fileId) {
42280 var endPoint = '/api/v1/workspace';
42282 endPoint += '/' + fileId;
42284 return this.resource(endPoint, {}).get();
42286 APIEndPoint.prototype.getDirectories = function () {
42287 var endPoint = '/api/v1/all/workspace/directory';
42288 return this.resource(endPoint, {}).get();
42290 APIEndPoint.prototype.getTags = function () {
42291 var endPoint = '/api/v1/tagList';
42292 return this.resource(endPoint, {}).get();
42294 APIEndPoint.prototype.getCommands = function () {
42295 var endPoint = '/api/v1/commandList';
42296 return this.resource(endPoint, {}).get();
42298 APIEndPoint.prototype.execute = function (data) {
42299 var endPoint = '/api/v1/execution';
42300 var fd = new FormData();
42301 fd.append('data', data);
42302 return this.$http.post(endPoint, fd, {
42303 headers: { 'Content-Type': undefined },
42304 transformRequest: angular.identity
42307 APIEndPoint.prototype.debug = function () {
42308 var endPoint = '/api/v1/debug';
42309 return this.$http.get(endPoint);
42311 APIEndPoint.prototype.help = function (command) {
42312 var endPoint = '/api/v1/help/' + command;
42313 return this.$http.get(endPoint);
42315 return APIEndPoint;
42317 services.APIEndPoint = APIEndPoint;
42318 })(services = app.services || (app.services = {}));
42319 })(app || (app = {}));
42323 (function (services) {
42324 var MyModal = (function () {
42325 function MyModal($uibModal) {
42326 this.$uibModal = $uibModal;
42327 this.modalOption = {
42334 MyModal.prototype.open = function (modalName) {
42335 if (modalName === 'SelectCommand') {
42336 this.modalOption.templateUrl = 'templates/select-command.html';
42337 this.modalOption.size = 'lg';
42339 return this.$uibModal.open(this.modalOption);
42341 MyModal.prototype.selectCommand = function () {
42342 this.modalOption.templateUrl = 'templates/select-command.html';
42343 this.modalOption.controller = 'selectCommandController';
42344 this.modalOption.controllerAs = 'c';
42345 this.modalOption.size = 'lg';
42346 return this.$uibModal.open(this.modalOption);
42348 MyModal.prototype.preview = function () {
42349 this.modalOption.templateUrl = 'templates/preview.html';
42350 this.modalOption.controller = 'previewController';
42351 this.modalOption.controllerAs = 'c';
42352 this.modalOption.size = 'lg';
42353 return this.$uibModal.open(this.modalOption);
42355 MyModal.$inject = ['$uibModal'];
42358 services.MyModal = MyModal;
42359 })(services = app.services || (app.services = {}));
42360 })(app || (app = {}));
42364 (function (services) {
42365 var WebSocket = (function () {
42366 function WebSocket($rootScope) {
42367 this.$rootScope = $rootScope;
42368 this.socket = io.connect();
42370 WebSocket.prototype.on = function (eventName, callback) {
42371 var socket = this.socket;
42372 var rootScope = this.$rootScope;
42373 socket.on(eventName, function () {
42374 var args = arguments;
42375 rootScope.$apply(function () {
42376 callback.apply(socket, args);
42380 WebSocket.prototype.emit = function (eventName, data, callback) {
42381 var socket = this.socket;
42382 var rootScope = this.$rootScope;
42383 this.socket.emit(eventName, data, function () {
42384 var args = arguments;
42385 rootScope.$apply(function () {
42387 callback.apply(socket, args);
42393 services.WebSocket = WebSocket;
42394 })(services = app.services || (app.services = {}));
42395 })(app || (app = {}));
42399 (function (directives) {
42400 var Command = (function () {
42401 function Command() {
42402 this.restrict = 'E';
42403 this.replace = true;
42405 this.controller = 'commandController';
42406 this.controllerAs = 'ctrl';
42407 this.bindToController = {
42413 this.templateUrl = 'templates/command.html';
42415 Command.Factory = function () {
42416 var directive = function () {
42417 return new Command();
42419 directive.$inject = [];
42424 directives.Command = Command;
42425 var CommandController = (function () {
42426 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
42427 this.APIEndPoint = APIEndPoint;
42428 this.$scope = $scope;
42429 this.MyModal = MyModal;
42430 this.WebSocket = WebSocket;
42431 var controller = this;
42433 .getOptionControlFile(this.name)
42435 .then(function (result) {
42436 controller.options = result.info;
42441 .then(function (result) {
42442 controller.dirs = result.info;
42444 this.heading = "[" + this.index + "]: dcdFilePrint";
42445 this.isOpen = true;
42446 this.$scope.$on('close', function () {
42447 controller.isOpen = false;
42449 this.WebSocket.on('console', function (msg) {
42450 controller.messages = msg.split('\n');
42453 CommandController.prototype.submit = function () {
42455 angular.forEach(this.options, function (option) {
42457 name: option.option,
42460 angular.forEach(option.arg, function (arg) {
42462 if (typeof arg.input === 'object') {
42463 obj.arguments.push(arg.input.name);
42466 obj.arguments.push(arg.input);
42470 if (obj.arguments.length > 0) {
42475 command: this.name,
42476 workspace: this.workspace.fileId,
42480 .execute(JSON.stringify(execObj))
42481 .then(function (result) {
42482 console.log(result);
42485 CommandController.prototype.removeMySelf = function (index) {
42486 this.remove()(index, this.list);
42488 CommandController.prototype.reloadFiles = function () {
42490 var fileId = this.workspace.fileId;
42494 .then(function (result) {
42495 var status = result.status;
42496 if (status === 'success') {
42497 _this.files = result.info;
42500 console.log(result.message);
42504 CommandController.prototype.debug = function () {
42507 .then(function (result) {
42508 console.log(result);
42511 CommandController.prototype.help = function () {
42514 .then(function (result) {
42515 console.log(result);
42518 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
42519 return CommandController;
42521 directives.CommandController = CommandController;
42522 })(directives = app.directives || (app.directives = {}));
42523 })(app || (app = {}));
42527 (function (directives) {
42528 var HeaderMenu = (function () {
42529 function HeaderMenu() {
42530 this.restrict = 'E';
42531 this.replace = true;
42532 this.templateUrl = 'templates/header-menu.html';
42534 HeaderMenu.Factory = function () {
42535 var directive = function () {
42536 return new HeaderMenu();
42542 directives.HeaderMenu = HeaderMenu;
42543 })(directives = app.directives || (app.directives = {}));
42544 })(app || (app = {}));
42548 (function (directives) {
42549 var Option = (function () {
42550 function Option() {
42551 this.restrict = 'E';
42552 this.replace = true;
42553 this.controller = 'optionController';
42554 this.bindToController = {
42559 this.templateUrl = 'templates/option.html';
42560 this.controllerAs = 'ctrl';
42562 Option.Factory = function () {
42563 var directive = function () {
42564 return new Option();
42566 directive.$inject = [];
42571 directives.Option = Option;
42572 var OptionController = (function () {
42573 function OptionController() {
42574 var controller = this;
42575 angular.forEach(controller.info.arg, function (arg) {
42576 if (arg.initialValue) {
42577 if (arg.formType === 'number') {
42578 arg.input = parseInt(arg.initialValue);
42581 arg.input = arg.initialValue;
42586 OptionController.$inject = [];
42587 return OptionController;
42589 directives.OptionController = OptionController;
42590 })(directives = app.directives || (app.directives = {}));
42591 })(app || (app = {}));
42595 (function (directives) {
42596 var Directory = (function () {
42597 function Directory() {
42598 this.restrict = 'E';
42599 this.replace = true;
42600 this.controller = 'directoryController';
42601 this.controllerAs = 'ctrl';
42602 this.bindToController = {
42608 this.templateUrl = 'templates/directory.html';
42610 Directory.Factory = function () {
42611 var directive = function () {
42612 return new Directory();
42618 directives.Directory = Directory;
42619 var DirectoryController = (function () {
42620 function DirectoryController(APIEndPoint, $scope) {
42621 this.APIEndPoint = APIEndPoint;
42622 this.$scope = $scope;
42623 var controller = this;
42625 .getFiles(this.info.fileId)
42627 .then(function (result) {
42628 if (result.status === 'success') {
42629 controller.files = result.info;
42630 angular.forEach(result.info, function (file) {
42631 if (file.fileType === '0') {
42633 if (controller.info.path === '/') {
42634 o.path = '/' + file.name;
42637 o.path = controller.info.path + '/' + file.name;
42639 controller.add()(o, controller.list);
42646 DirectoryController.$inject = ['APIEndPoint', '$scope'];
42647 return DirectoryController;
42649 directives.DirectoryController = DirectoryController;
42650 })(directives = app.directives || (app.directives = {}));
42651 })(app || (app = {}));
42655 (function (controllers) {
42656 var Execution = (function () {
42657 function Execution(MyModal, $scope) {
42658 this.MyModal = MyModal;
42659 this.$scope = $scope;
42660 this.commandInfoList = [];
42663 Execution.prototype.add = function () {
42664 this.$scope.$broadcast('close');
42665 var commandInfoList = this.commandInfoList;
42666 var commandInstance = this.MyModal.selectCommand();
42669 .then(function (command) {
42670 commandInfoList.push(new app.declares.CommandInfo(command));
42673 Execution.prototype.open = function () {
42674 var result = this.MyModal.open('SelectCommand');
42675 console.log(result);
42677 Execution.prototype.remove = function (index, list) {
42678 list.splice(index, 1);
42680 Execution.prototype.close = function () {
42681 console.log("close");
42683 Execution.$inject = ['MyModal', '$scope'];
42686 controllers.Execution = Execution;
42687 })(controllers = app.controllers || (app.controllers = {}));
42688 })(app || (app = {}));
42692 (function (controllers) {
42693 var Workspace = (function () {
42694 function Workspace($scope, APIEndPoint, MyModal) {
42695 this.$scope = $scope;
42696 this.APIEndPoint = APIEndPoint;
42697 this.MyModal = MyModal;
42698 this.directoryList = [];
42699 var controller = this;
42700 var directoryList = this.directoryList;
42702 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
42710 directoryList.push(o);
42712 Workspace.prototype.addDirectory = function (info, directoryList) {
42713 directoryList.push(info);
42715 Workspace.prototype.debug = function () {
42716 this.MyModal.preview();
42718 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
42721 controllers.Workspace = Workspace;
42722 })(controllers = app.controllers || (app.controllers = {}));
42723 })(app || (app = {}));
42727 (function (controllers) {
42728 var History = (function () {
42729 function History($scope) {
42730 this.page = "History";
42732 History.$inject = ['$scope'];
42735 controllers.History = History;
42736 })(controllers = app.controllers || (app.controllers = {}));
42737 })(app || (app = {}));
42741 (function (controllers) {
42742 var SelectCommand = (function () {
42743 function SelectCommand($scope, APIEndPoint, $modalInstance) {
42744 this.APIEndPoint = APIEndPoint;
42745 this.$modalInstance = $modalInstance;
42746 var controller = this;
42749 .$promise.then(function (result) {
42750 controller.tags = result.info;
42754 .$promise.then(function (result) {
42755 controller.commands = result.info;
42757 this.currentTag = 'all';
42759 SelectCommand.prototype.changeTag = function (tag) {
42760 this.currentTag = tag;
42762 SelectCommand.prototype.selectCommand = function (command) {
42763 this.$modalInstance.close(command);
42765 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
42766 return SelectCommand;
42768 controllers.SelectCommand = SelectCommand;
42769 })(controllers = app.controllers || (app.controllers = {}));
42770 })(app || (app = {}));
42774 (function (controllers) {
42775 var Preview = (function () {
42776 function Preview($scope, APIEndPoint, $modalInstance) {
42777 this.APIEndPoint = APIEndPoint;
42778 this.$modalInstance = $modalInstance;
42779 var controller = this;
42780 console.log('preview');
42782 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
42785 controllers.Preview = Preview;
42786 })(controllers = app.controllers || (app.controllers = {}));
42787 })(app || (app = {}));
42789 (function (filters) {
42791 return function (commands, tag) {
42793 angular.forEach(commands, function (command) {
42795 angular.forEach(command.tags, function (value) {
42800 result.push(command);
42806 })(filters || (filters = {}));
42810 var appName = 'zephyr';
42811 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
42812 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
42813 $urlRouterProvider.otherwise('/execution');
42814 $locationProvider.html5Mode({
42819 .state('execution', {
42821 templateUrl: 'templates/execution.html',
42822 controller: 'executionController',
42825 .state('workspace', {
42827 templateUrl: 'templates/workspace.html',
42828 controller: 'workspaceController',
42831 .state('history', {
42833 templateUrl: 'templates/history.html',
42834 controller: 'historyController',
42838 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
42839 app.zephyr.service('MyModal', app.services.MyModal);
42840 app.zephyr.service('WebSocket', app.services.WebSocket);
42841 app.zephyr.filter('Tag', filters.Tag);
42842 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
42843 app.zephyr.controller('previewController', app.controllers.Preview);
42844 app.zephyr.controller('executionController', app.controllers.Execution);
42845 app.zephyr.controller('workspaceController', app.controllers.Workspace);
42846 app.zephyr.controller('historyController', app.controllers.History);
42847 app.zephyr.controller('commandController', app.directives.CommandController);
42848 app.zephyr.controller('optionController', app.directives.OptionController);
42849 app.zephyr.controller('directoryController', app.directives.DirectoryController);
42850 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
42851 app.zephyr.directive('command', app.directives.Command.Factory());
42852 app.zephyr.directive('option', app.directives.Option.Factory());
42853 app.zephyr.directive('directory', app.directives.Directory.Factory());
42854 })(app || (app = {}));
42859 /***/ function(module, exports) {
42864 (function (declares) {
42865 var CommandInfo = (function () {
42866 function CommandInfo(name) {
42869 return CommandInfo;
42871 declares.CommandInfo = CommandInfo;
42872 })(declares = app.declares || (app.declares = {}));
42873 })(app || (app = {}));
42877 (function (services) {
42878 var APIEndPoint = (function () {
42879 function APIEndPoint($resource, $http) {
42880 this.$resource = $resource;
42881 this.$http = $http;
42883 APIEndPoint.prototype.resource = function (endPoint, data) {
42884 var customAction = {
42890 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
42892 return this.$resource(endPoint, {}, { execute: execute });
42894 APIEndPoint.prototype.getOptionControlFile = function (command) {
42895 var endPoint = '/api/v1/optionControlFile/' + command;
42896 return this.resource(endPoint, {}).get();
42898 APIEndPoint.prototype.getFiles = function (fileId) {
42899 var endPoint = '/api/v1/workspace';
42901 endPoint += '/' + fileId;
42903 return this.resource(endPoint, {}).get();
42905 APIEndPoint.prototype.getDirectories = function () {
42906 var endPoint = '/api/v1/all/workspace/directory';
42907 return this.resource(endPoint, {}).get();
42909 APIEndPoint.prototype.getTags = function () {
42910 var endPoint = '/api/v1/tagList';
42911 return this.resource(endPoint, {}).get();
42913 APIEndPoint.prototype.getCommands = function () {
42914 var endPoint = '/api/v1/commandList';
42915 return this.resource(endPoint, {}).get();
42917 APIEndPoint.prototype.execute = function (data) {
42918 var endPoint = '/api/v1/execution';
42919 var fd = new FormData();
42920 fd.append('data', data);
42921 return this.$http.post(endPoint, fd, {
42922 headers: { 'Content-Type': undefined },
42923 transformRequest: angular.identity
42926 APIEndPoint.prototype.debug = function () {
42927 var endPoint = '/api/v1/debug';
42928 return this.$http.get(endPoint);
42930 APIEndPoint.prototype.help = function (command) {
42931 var endPoint = '/api/v1/help/' + command;
42932 return this.$http.get(endPoint);
42934 return APIEndPoint;
42936 services.APIEndPoint = APIEndPoint;
42937 })(services = app.services || (app.services = {}));
42938 })(app || (app = {}));
42942 (function (services) {
42943 var MyModal = (function () {
42944 function MyModal($uibModal) {
42945 this.$uibModal = $uibModal;
42946 this.modalOption = {
42953 MyModal.prototype.open = function (modalName) {
42954 if (modalName === 'SelectCommand') {
42955 this.modalOption.templateUrl = 'templates/select-command.html';
42956 this.modalOption.size = 'lg';
42958 return this.$uibModal.open(this.modalOption);
42960 MyModal.prototype.selectCommand = function () {
42961 this.modalOption.templateUrl = 'templates/select-command.html';
42962 this.modalOption.controller = 'selectCommandController';
42963 this.modalOption.controllerAs = 'c';
42964 this.modalOption.size = 'lg';
42965 return this.$uibModal.open(this.modalOption);
42967 MyModal.prototype.preview = function () {
42968 this.modalOption.templateUrl = 'templates/preview.html';
42969 this.modalOption.controller = 'previewController';
42970 this.modalOption.controllerAs = 'c';
42971 this.modalOption.size = 'lg';
42972 return this.$uibModal.open(this.modalOption);
42974 MyModal.$inject = ['$uibModal'];
42977 services.MyModal = MyModal;
42978 })(services = app.services || (app.services = {}));
42979 })(app || (app = {}));
42983 (function (services) {
42984 var WebSocket = (function () {
42985 function WebSocket($rootScope) {
42986 this.$rootScope = $rootScope;
42987 this.socket = io.connect();
42989 WebSocket.prototype.on = function (eventName, callback) {
42990 var socket = this.socket;
42991 var rootScope = this.$rootScope;
42992 socket.on(eventName, function () {
42993 var args = arguments;
42994 rootScope.$apply(function () {
42995 callback.apply(socket, args);
42999 WebSocket.prototype.emit = function (eventName, data, callback) {
43000 var socket = this.socket;
43001 var rootScope = this.$rootScope;
43002 this.socket.emit(eventName, data, function () {
43003 var args = arguments;
43004 rootScope.$apply(function () {
43006 callback.apply(socket, args);
43012 services.WebSocket = WebSocket;
43013 })(services = app.services || (app.services = {}));
43014 })(app || (app = {}));
43018 (function (directives) {
43019 var Command = (function () {
43020 function Command() {
43021 this.restrict = 'E';
43022 this.replace = true;
43024 this.controller = 'commandController';
43025 this.controllerAs = 'ctrl';
43026 this.bindToController = {
43032 this.templateUrl = 'templates/command.html';
43034 Command.Factory = function () {
43035 var directive = function () {
43036 return new Command();
43038 directive.$inject = [];
43043 directives.Command = Command;
43044 var CommandController = (function () {
43045 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
43046 this.APIEndPoint = APIEndPoint;
43047 this.$scope = $scope;
43048 this.MyModal = MyModal;
43049 this.WebSocket = WebSocket;
43050 var controller = this;
43052 .getOptionControlFile(this.name)
43054 .then(function (result) {
43055 controller.options = result.info;
43060 .then(function (result) {
43061 controller.dirs = result.info;
43063 this.heading = "[" + this.index + "]: dcdFilePrint";
43064 this.isOpen = true;
43065 this.$scope.$on('close', function () {
43066 controller.isOpen = false;
43068 this.WebSocket.on('console', function (msg) {
43069 controller.messages = msg.split('\n');
43072 CommandController.prototype.submit = function () {
43074 angular.forEach(this.options, function (option) {
43076 name: option.option,
43079 angular.forEach(option.arg, function (arg) {
43081 if (typeof arg.input === 'object') {
43082 obj.arguments.push(arg.input.name);
43085 obj.arguments.push(arg.input);
43089 if (obj.arguments.length > 0) {
43094 command: this.name,
43095 workspace: this.workspace.fileId,
43099 .execute(JSON.stringify(execObj))
43100 .then(function (result) {
43101 console.log(result);
43104 CommandController.prototype.removeMySelf = function (index) {
43105 this.remove()(index, this.list);
43107 CommandController.prototype.reloadFiles = function () {
43109 var fileId = this.workspace.fileId;
43113 .then(function (result) {
43114 var status = result.status;
43115 if (status === 'success') {
43116 _this.files = result.info;
43119 console.log(result.message);
43123 CommandController.prototype.debug = function () {
43126 .then(function (result) {
43127 console.log(result);
43130 CommandController.prototype.help = function () {
43133 .then(function (result) {
43134 console.log(result);
43137 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
43138 return CommandController;
43140 directives.CommandController = CommandController;
43141 })(directives = app.directives || (app.directives = {}));
43142 })(app || (app = {}));
43146 (function (directives) {
43147 var HeaderMenu = (function () {
43148 function HeaderMenu() {
43149 this.restrict = 'E';
43150 this.replace = true;
43151 this.templateUrl = 'templates/header-menu.html';
43153 HeaderMenu.Factory = function () {
43154 var directive = function () {
43155 return new HeaderMenu();
43161 directives.HeaderMenu = HeaderMenu;
43162 })(directives = app.directives || (app.directives = {}));
43163 })(app || (app = {}));
43167 (function (directives) {
43168 var Option = (function () {
43169 function Option() {
43170 this.restrict = 'E';
43171 this.replace = true;
43172 this.controller = 'optionController';
43173 this.bindToController = {
43178 this.templateUrl = 'templates/option.html';
43179 this.controllerAs = 'ctrl';
43181 Option.Factory = function () {
43182 var directive = function () {
43183 return new Option();
43185 directive.$inject = [];
43190 directives.Option = Option;
43191 var OptionController = (function () {
43192 function OptionController() {
43193 var controller = this;
43194 angular.forEach(controller.info.arg, function (arg) {
43195 if (arg.initialValue) {
43196 if (arg.formType === 'number') {
43197 arg.input = parseInt(arg.initialValue);
43200 arg.input = arg.initialValue;
43205 OptionController.$inject = [];
43206 return OptionController;
43208 directives.OptionController = OptionController;
43209 })(directives = app.directives || (app.directives = {}));
43210 })(app || (app = {}));
43214 (function (directives) {
43215 var Directory = (function () {
43216 function Directory() {
43217 this.restrict = 'E';
43218 this.replace = true;
43219 this.controller = 'directoryController';
43220 this.controllerAs = 'ctrl';
43221 this.bindToController = {
43227 this.templateUrl = 'templates/directory.html';
43229 Directory.Factory = function () {
43230 var directive = function () {
43231 return new Directory();
43237 directives.Directory = Directory;
43238 var DirectoryController = (function () {
43239 function DirectoryController(APIEndPoint, $scope) {
43240 this.APIEndPoint = APIEndPoint;
43241 this.$scope = $scope;
43242 var controller = this;
43244 .getFiles(this.info.fileId)
43246 .then(function (result) {
43247 if (result.status === 'success') {
43248 controller.files = result.info;
43249 angular.forEach(result.info, function (file) {
43250 if (file.fileType === '0') {
43252 if (controller.info.path === '/') {
43253 o.path = '/' + file.name;
43256 o.path = controller.info.path + '/' + file.name;
43258 controller.add()(o, controller.list);
43265 DirectoryController.$inject = ['APIEndPoint', '$scope'];
43266 return DirectoryController;
43268 directives.DirectoryController = DirectoryController;
43269 })(directives = app.directives || (app.directives = {}));
43270 })(app || (app = {}));
43274 (function (controllers) {
43275 var Execution = (function () {
43276 function Execution(MyModal, $scope) {
43277 this.MyModal = MyModal;
43278 this.$scope = $scope;
43279 this.commandInfoList = [];
43282 Execution.prototype.add = function () {
43283 this.$scope.$broadcast('close');
43284 var commandInfoList = this.commandInfoList;
43285 var commandInstance = this.MyModal.selectCommand();
43288 .then(function (command) {
43289 commandInfoList.push(new app.declares.CommandInfo(command));
43292 Execution.prototype.open = function () {
43293 var result = this.MyModal.open('SelectCommand');
43294 console.log(result);
43296 Execution.prototype.remove = function (index, list) {
43297 list.splice(index, 1);
43299 Execution.prototype.close = function () {
43300 console.log("close");
43302 Execution.$inject = ['MyModal', '$scope'];
43305 controllers.Execution = Execution;
43306 })(controllers = app.controllers || (app.controllers = {}));
43307 })(app || (app = {}));
43311 (function (controllers) {
43312 var Workspace = (function () {
43313 function Workspace($scope, APIEndPoint, MyModal) {
43314 this.$scope = $scope;
43315 this.APIEndPoint = APIEndPoint;
43316 this.MyModal = MyModal;
43317 this.directoryList = [];
43318 var controller = this;
43319 var directoryList = this.directoryList;
43321 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
43329 directoryList.push(o);
43331 Workspace.prototype.addDirectory = function (info, directoryList) {
43332 directoryList.push(info);
43334 Workspace.prototype.debug = function () {
43335 this.MyModal.preview();
43337 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
43340 controllers.Workspace = Workspace;
43341 })(controllers = app.controllers || (app.controllers = {}));
43342 })(app || (app = {}));
43346 (function (controllers) {
43347 var History = (function () {
43348 function History($scope) {
43349 this.page = "History";
43351 History.$inject = ['$scope'];
43354 controllers.History = History;
43355 })(controllers = app.controllers || (app.controllers = {}));
43356 })(app || (app = {}));
43360 (function (controllers) {
43361 var SelectCommand = (function () {
43362 function SelectCommand($scope, APIEndPoint, $modalInstance) {
43363 this.APIEndPoint = APIEndPoint;
43364 this.$modalInstance = $modalInstance;
43365 var controller = this;
43368 .$promise.then(function (result) {
43369 controller.tags = result.info;
43373 .$promise.then(function (result) {
43374 controller.commands = result.info;
43376 this.currentTag = 'all';
43378 SelectCommand.prototype.changeTag = function (tag) {
43379 this.currentTag = tag;
43381 SelectCommand.prototype.selectCommand = function (command) {
43382 this.$modalInstance.close(command);
43384 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
43385 return SelectCommand;
43387 controllers.SelectCommand = SelectCommand;
43388 })(controllers = app.controllers || (app.controllers = {}));
43389 })(app || (app = {}));
43393 (function (controllers) {
43394 var Preview = (function () {
43395 function Preview($scope, APIEndPoint, $modalInstance) {
43396 this.APIEndPoint = APIEndPoint;
43397 this.$modalInstance = $modalInstance;
43398 var controller = this;
43399 console.log('preview');
43401 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
43404 controllers.Preview = Preview;
43405 })(controllers = app.controllers || (app.controllers = {}));
43406 })(app || (app = {}));
43408 (function (filters) {
43410 return function (commands, tag) {
43412 angular.forEach(commands, function (command) {
43414 angular.forEach(command.tags, function (value) {
43419 result.push(command);
43425 })(filters || (filters = {}));
43429 var appName = 'zephyr';
43430 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
43431 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
43432 $urlRouterProvider.otherwise('/execution');
43433 $locationProvider.html5Mode({
43438 .state('execution', {
43440 templateUrl: 'templates/execution.html',
43441 controller: 'executionController',
43444 .state('workspace', {
43446 templateUrl: 'templates/workspace.html',
43447 controller: 'workspaceController',
43450 .state('history', {
43452 templateUrl: 'templates/history.html',
43453 controller: 'historyController',
43457 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
43458 app.zephyr.service('MyModal', app.services.MyModal);
43459 app.zephyr.service('WebSocket', app.services.WebSocket);
43460 app.zephyr.filter('Tag', filters.Tag);
43461 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
43462 app.zephyr.controller('previewController', app.controllers.Preview);
43463 app.zephyr.controller('executionController', app.controllers.Execution);
43464 app.zephyr.controller('workspaceController', app.controllers.Workspace);
43465 app.zephyr.controller('historyController', app.controllers.History);
43466 app.zephyr.controller('commandController', app.directives.CommandController);
43467 app.zephyr.controller('optionController', app.directives.OptionController);
43468 app.zephyr.controller('directoryController', app.directives.DirectoryController);
43469 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
43470 app.zephyr.directive('command', app.directives.Command.Factory());
43471 app.zephyr.directive('option', app.directives.Option.Factory());
43472 app.zephyr.directive('directory', app.directives.Directory.Factory());
43473 })(app || (app = {}));
43478 /***/ function(module, exports) {
43483 (function (declares) {
43484 var CommandInfo = (function () {
43485 function CommandInfo(name) {
43488 return CommandInfo;
43490 declares.CommandInfo = CommandInfo;
43491 })(declares = app.declares || (app.declares = {}));
43492 })(app || (app = {}));
43496 (function (services) {
43497 var APIEndPoint = (function () {
43498 function APIEndPoint($resource, $http) {
43499 this.$resource = $resource;
43500 this.$http = $http;
43502 APIEndPoint.prototype.resource = function (endPoint, data) {
43503 var customAction = {
43509 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
43511 return this.$resource(endPoint, {}, { execute: execute });
43513 APIEndPoint.prototype.getOptionControlFile = function (command) {
43514 var endPoint = '/api/v1/optionControlFile/' + command;
43515 return this.resource(endPoint, {}).get();
43517 APIEndPoint.prototype.getFiles = function (fileId) {
43518 var endPoint = '/api/v1/workspace';
43520 endPoint += '/' + fileId;
43522 return this.resource(endPoint, {}).get();
43524 APIEndPoint.prototype.getDirectories = function () {
43525 var endPoint = '/api/v1/all/workspace/directory';
43526 return this.resource(endPoint, {}).get();
43528 APIEndPoint.prototype.getTags = function () {
43529 var endPoint = '/api/v1/tagList';
43530 return this.resource(endPoint, {}).get();
43532 APIEndPoint.prototype.getCommands = function () {
43533 var endPoint = '/api/v1/commandList';
43534 return this.resource(endPoint, {}).get();
43536 APIEndPoint.prototype.execute = function (data) {
43537 var endPoint = '/api/v1/execution';
43538 var fd = new FormData();
43539 fd.append('data', data);
43540 return this.$http.post(endPoint, fd, {
43541 headers: { 'Content-Type': undefined },
43542 transformRequest: angular.identity
43545 APIEndPoint.prototype.debug = function () {
43546 var endPoint = '/api/v1/debug';
43547 return this.$http.get(endPoint);
43549 APIEndPoint.prototype.help = function (command) {
43550 var endPoint = '/api/v1/help/' + command;
43551 return this.$http.get(endPoint);
43553 return APIEndPoint;
43555 services.APIEndPoint = APIEndPoint;
43556 })(services = app.services || (app.services = {}));
43557 })(app || (app = {}));
43561 (function (services) {
43562 var MyModal = (function () {
43563 function MyModal($uibModal) {
43564 this.$uibModal = $uibModal;
43565 this.modalOption = {
43572 MyModal.prototype.open = function (modalName) {
43573 if (modalName === 'SelectCommand') {
43574 this.modalOption.templateUrl = 'templates/select-command.html';
43575 this.modalOption.size = 'lg';
43577 return this.$uibModal.open(this.modalOption);
43579 MyModal.prototype.selectCommand = function () {
43580 this.modalOption.templateUrl = 'templates/select-command.html';
43581 this.modalOption.controller = 'selectCommandController';
43582 this.modalOption.controllerAs = 'c';
43583 this.modalOption.size = 'lg';
43584 return this.$uibModal.open(this.modalOption);
43586 MyModal.prototype.preview = function () {
43587 this.modalOption.templateUrl = 'templates/preview.html';
43588 this.modalOption.controller = 'previewController';
43589 this.modalOption.controllerAs = 'c';
43590 this.modalOption.size = 'lg';
43591 return this.$uibModal.open(this.modalOption);
43593 MyModal.$inject = ['$uibModal'];
43596 services.MyModal = MyModal;
43597 })(services = app.services || (app.services = {}));
43598 })(app || (app = {}));
43602 (function (services) {
43603 var WebSocket = (function () {
43604 function WebSocket($rootScope) {
43605 this.$rootScope = $rootScope;
43606 this.socket = io.connect();
43608 WebSocket.prototype.on = function (eventName, callback) {
43609 var socket = this.socket;
43610 var rootScope = this.$rootScope;
43611 socket.on(eventName, function () {
43612 var args = arguments;
43613 rootScope.$apply(function () {
43614 callback.apply(socket, args);
43618 WebSocket.prototype.emit = function (eventName, data, callback) {
43619 var socket = this.socket;
43620 var rootScope = this.$rootScope;
43621 this.socket.emit(eventName, data, function () {
43622 var args = arguments;
43623 rootScope.$apply(function () {
43625 callback.apply(socket, args);
43631 services.WebSocket = WebSocket;
43632 })(services = app.services || (app.services = {}));
43633 })(app || (app = {}));
43637 (function (directives) {
43638 var Command = (function () {
43639 function Command() {
43640 this.restrict = 'E';
43641 this.replace = true;
43643 this.controller = 'commandController';
43644 this.controllerAs = 'ctrl';
43645 this.bindToController = {
43651 this.templateUrl = 'templates/command.html';
43653 Command.Factory = function () {
43654 var directive = function () {
43655 return new Command();
43657 directive.$inject = [];
43662 directives.Command = Command;
43663 var CommandController = (function () {
43664 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
43665 this.APIEndPoint = APIEndPoint;
43666 this.$scope = $scope;
43667 this.MyModal = MyModal;
43668 this.WebSocket = WebSocket;
43669 var controller = this;
43671 .getOptionControlFile(this.name)
43673 .then(function (result) {
43674 controller.options = result.info;
43679 .then(function (result) {
43680 controller.dirs = result.info;
43682 this.heading = "[" + this.index + "]: dcdFilePrint";
43683 this.isOpen = true;
43684 this.$scope.$on('close', function () {
43685 controller.isOpen = false;
43687 this.WebSocket.on('console', function (msg) {
43688 controller.messages = msg.split('\n');
43691 CommandController.prototype.submit = function () {
43693 angular.forEach(this.options, function (option) {
43695 name: option.option,
43698 angular.forEach(option.arg, function (arg) {
43700 if (typeof arg.input === 'object') {
43701 obj.arguments.push(arg.input.name);
43704 obj.arguments.push(arg.input);
43708 if (obj.arguments.length > 0) {
43713 command: this.name,
43714 workspace: this.workspace.fileId,
43718 .execute(JSON.stringify(execObj))
43719 .then(function (result) {
43720 console.log(result);
43723 CommandController.prototype.removeMySelf = function (index) {
43724 this.remove()(index, this.list);
43726 CommandController.prototype.reloadFiles = function () {
43728 var fileId = this.workspace.fileId;
43732 .then(function (result) {
43733 var status = result.status;
43734 if (status === 'success') {
43735 _this.files = result.info;
43738 console.log(result.message);
43742 CommandController.prototype.debug = function () {
43745 .then(function (result) {
43746 console.log(result);
43749 CommandController.prototype.help = function () {
43752 .then(function (result) {
43753 console.log(result);
43756 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
43757 return CommandController;
43759 directives.CommandController = CommandController;
43760 })(directives = app.directives || (app.directives = {}));
43761 })(app || (app = {}));
43765 (function (directives) {
43766 var HeaderMenu = (function () {
43767 function HeaderMenu() {
43768 this.restrict = 'E';
43769 this.replace = true;
43770 this.templateUrl = 'templates/header-menu.html';
43772 HeaderMenu.Factory = function () {
43773 var directive = function () {
43774 return new HeaderMenu();
43780 directives.HeaderMenu = HeaderMenu;
43781 })(directives = app.directives || (app.directives = {}));
43782 })(app || (app = {}));
43786 (function (directives) {
43787 var Option = (function () {
43788 function Option() {
43789 this.restrict = 'E';
43790 this.replace = true;
43791 this.controller = 'optionController';
43792 this.bindToController = {
43797 this.templateUrl = 'templates/option.html';
43798 this.controllerAs = 'ctrl';
43800 Option.Factory = function () {
43801 var directive = function () {
43802 return new Option();
43804 directive.$inject = [];
43809 directives.Option = Option;
43810 var OptionController = (function () {
43811 function OptionController() {
43812 var controller = this;
43813 angular.forEach(controller.info.arg, function (arg) {
43814 if (arg.initialValue) {
43815 if (arg.formType === 'number') {
43816 arg.input = parseInt(arg.initialValue);
43819 arg.input = arg.initialValue;
43824 OptionController.$inject = [];
43825 return OptionController;
43827 directives.OptionController = OptionController;
43828 })(directives = app.directives || (app.directives = {}));
43829 })(app || (app = {}));
43833 (function (directives) {
43834 var Directory = (function () {
43835 function Directory() {
43836 this.restrict = 'E';
43837 this.replace = true;
43838 this.controller = 'directoryController';
43839 this.controllerAs = 'ctrl';
43840 this.bindToController = {
43846 this.templateUrl = 'templates/directory.html';
43848 Directory.Factory = function () {
43849 var directive = function () {
43850 return new Directory();
43856 directives.Directory = Directory;
43857 var DirectoryController = (function () {
43858 function DirectoryController(APIEndPoint, $scope) {
43859 this.APIEndPoint = APIEndPoint;
43860 this.$scope = $scope;
43861 var controller = this;
43863 .getFiles(this.info.fileId)
43865 .then(function (result) {
43866 if (result.status === 'success') {
43867 controller.files = result.info;
43868 angular.forEach(result.info, function (file) {
43869 if (file.fileType === '0') {
43871 if (controller.info.path === '/') {
43872 o.path = '/' + file.name;
43875 o.path = controller.info.path + '/' + file.name;
43877 controller.add()(o, controller.list);
43884 DirectoryController.$inject = ['APIEndPoint', '$scope'];
43885 return DirectoryController;
43887 directives.DirectoryController = DirectoryController;
43888 })(directives = app.directives || (app.directives = {}));
43889 })(app || (app = {}));
43893 (function (controllers) {
43894 var Execution = (function () {
43895 function Execution(MyModal, $scope) {
43896 this.MyModal = MyModal;
43897 this.$scope = $scope;
43898 this.commandInfoList = [];
43901 Execution.prototype.add = function () {
43902 this.$scope.$broadcast('close');
43903 var commandInfoList = this.commandInfoList;
43904 var commandInstance = this.MyModal.selectCommand();
43907 .then(function (command) {
43908 commandInfoList.push(new app.declares.CommandInfo(command));
43911 Execution.prototype.open = function () {
43912 var result = this.MyModal.open('SelectCommand');
43913 console.log(result);
43915 Execution.prototype.remove = function (index, list) {
43916 list.splice(index, 1);
43918 Execution.prototype.close = function () {
43919 console.log("close");
43921 Execution.$inject = ['MyModal', '$scope'];
43924 controllers.Execution = Execution;
43925 })(controllers = app.controllers || (app.controllers = {}));
43926 })(app || (app = {}));
43930 (function (controllers) {
43931 var Workspace = (function () {
43932 function Workspace($scope, APIEndPoint, MyModal) {
43933 this.$scope = $scope;
43934 this.APIEndPoint = APIEndPoint;
43935 this.MyModal = MyModal;
43936 this.directoryList = [];
43937 var controller = this;
43938 var directoryList = this.directoryList;
43940 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
43948 directoryList.push(o);
43950 Workspace.prototype.addDirectory = function (info, directoryList) {
43951 directoryList.push(info);
43953 Workspace.prototype.debug = function () {
43954 this.MyModal.preview();
43956 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
43959 controllers.Workspace = Workspace;
43960 })(controllers = app.controllers || (app.controllers = {}));
43961 })(app || (app = {}));
43965 (function (controllers) {
43966 var History = (function () {
43967 function History($scope) {
43968 this.page = "History";
43970 History.$inject = ['$scope'];
43973 controllers.History = History;
43974 })(controllers = app.controllers || (app.controllers = {}));
43975 })(app || (app = {}));
43979 (function (controllers) {
43980 var SelectCommand = (function () {
43981 function SelectCommand($scope, APIEndPoint, $modalInstance) {
43982 this.APIEndPoint = APIEndPoint;
43983 this.$modalInstance = $modalInstance;
43984 var controller = this;
43987 .$promise.then(function (result) {
43988 controller.tags = result.info;
43992 .$promise.then(function (result) {
43993 controller.commands = result.info;
43995 this.currentTag = 'all';
43997 SelectCommand.prototype.changeTag = function (tag) {
43998 this.currentTag = tag;
44000 SelectCommand.prototype.selectCommand = function (command) {
44001 this.$modalInstance.close(command);
44003 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
44004 return SelectCommand;
44006 controllers.SelectCommand = SelectCommand;
44007 })(controllers = app.controllers || (app.controllers = {}));
44008 })(app || (app = {}));
44012 (function (controllers) {
44013 var Preview = (function () {
44014 function Preview($scope, APIEndPoint, $modalInstance) {
44015 this.APIEndPoint = APIEndPoint;
44016 this.$modalInstance = $modalInstance;
44017 var controller = this;
44018 console.log('preview');
44020 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
44023 controllers.Preview = Preview;
44024 })(controllers = app.controllers || (app.controllers = {}));
44025 })(app || (app = {}));
44027 (function (filters) {
44029 return function (commands, tag) {
44031 angular.forEach(commands, function (command) {
44033 angular.forEach(command.tags, function (value) {
44038 result.push(command);
44044 })(filters || (filters = {}));
44048 var appName = 'zephyr';
44049 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
44050 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
44051 $urlRouterProvider.otherwise('/execution');
44052 $locationProvider.html5Mode({
44057 .state('execution', {
44059 templateUrl: 'templates/execution.html',
44060 controller: 'executionController',
44063 .state('workspace', {
44065 templateUrl: 'templates/workspace.html',
44066 controller: 'workspaceController',
44069 .state('history', {
44071 templateUrl: 'templates/history.html',
44072 controller: 'historyController',
44076 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
44077 app.zephyr.service('MyModal', app.services.MyModal);
44078 app.zephyr.service('WebSocket', app.services.WebSocket);
44079 app.zephyr.filter('Tag', filters.Tag);
44080 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
44081 app.zephyr.controller('previewController', app.controllers.Preview);
44082 app.zephyr.controller('executionController', app.controllers.Execution);
44083 app.zephyr.controller('workspaceController', app.controllers.Workspace);
44084 app.zephyr.controller('historyController', app.controllers.History);
44085 app.zephyr.controller('commandController', app.directives.CommandController);
44086 app.zephyr.controller('optionController', app.directives.OptionController);
44087 app.zephyr.controller('directoryController', app.directives.DirectoryController);
44088 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
44089 app.zephyr.directive('command', app.directives.Command.Factory());
44090 app.zephyr.directive('option', app.directives.Option.Factory());
44091 app.zephyr.directive('directory', app.directives.Directory.Factory());
44092 })(app || (app = {}));
44097 /***/ function(module, exports) {
44102 (function (declares) {
44103 var CommandInfo = (function () {
44104 function CommandInfo(name) {
44107 return CommandInfo;
44109 declares.CommandInfo = CommandInfo;
44110 })(declares = app.declares || (app.declares = {}));
44111 })(app || (app = {}));
44115 (function (services) {
44116 var APIEndPoint = (function () {
44117 function APIEndPoint($resource, $http) {
44118 this.$resource = $resource;
44119 this.$http = $http;
44121 APIEndPoint.prototype.resource = function (endPoint, data) {
44122 var customAction = {
44128 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
44130 return this.$resource(endPoint, {}, { execute: execute });
44132 APIEndPoint.prototype.getOptionControlFile = function (command) {
44133 var endPoint = '/api/v1/optionControlFile/' + command;
44134 return this.resource(endPoint, {}).get();
44136 APIEndPoint.prototype.getFiles = function (fileId) {
44137 var endPoint = '/api/v1/workspace';
44139 endPoint += '/' + fileId;
44141 return this.resource(endPoint, {}).get();
44143 APIEndPoint.prototype.getDirectories = function () {
44144 var endPoint = '/api/v1/all/workspace/directory';
44145 return this.resource(endPoint, {}).get();
44147 APIEndPoint.prototype.getTags = function () {
44148 var endPoint = '/api/v1/tagList';
44149 return this.resource(endPoint, {}).get();
44151 APIEndPoint.prototype.getCommands = function () {
44152 var endPoint = '/api/v1/commandList';
44153 return this.resource(endPoint, {}).get();
44155 APIEndPoint.prototype.execute = function (data) {
44156 var endPoint = '/api/v1/execution';
44157 var fd = new FormData();
44158 fd.append('data', data);
44159 return this.$http.post(endPoint, fd, {
44160 headers: { 'Content-Type': undefined },
44161 transformRequest: angular.identity
44164 APIEndPoint.prototype.debug = function () {
44165 var endPoint = '/api/v1/debug';
44166 return this.$http.get(endPoint);
44168 APIEndPoint.prototype.help = function (command) {
44169 var endPoint = '/api/v1/help/' + command;
44170 return this.$http.get(endPoint);
44172 return APIEndPoint;
44174 services.APIEndPoint = APIEndPoint;
44175 })(services = app.services || (app.services = {}));
44176 })(app || (app = {}));
44180 (function (services) {
44181 var MyModal = (function () {
44182 function MyModal($uibModal) {
44183 this.$uibModal = $uibModal;
44184 this.modalOption = {
44191 MyModal.prototype.open = function (modalName) {
44192 if (modalName === 'SelectCommand') {
44193 this.modalOption.templateUrl = 'templates/select-command.html';
44194 this.modalOption.size = 'lg';
44196 return this.$uibModal.open(this.modalOption);
44198 MyModal.prototype.selectCommand = function () {
44199 this.modalOption.templateUrl = 'templates/select-command.html';
44200 this.modalOption.controller = 'selectCommandController';
44201 this.modalOption.controllerAs = 'c';
44202 this.modalOption.size = 'lg';
44203 return this.$uibModal.open(this.modalOption);
44205 MyModal.prototype.preview = function () {
44206 this.modalOption.templateUrl = 'templates/preview.html';
44207 this.modalOption.controller = 'previewController';
44208 this.modalOption.controllerAs = 'c';
44209 this.modalOption.size = 'lg';
44210 return this.$uibModal.open(this.modalOption);
44212 MyModal.$inject = ['$uibModal'];
44215 services.MyModal = MyModal;
44216 })(services = app.services || (app.services = {}));
44217 })(app || (app = {}));
44221 (function (services) {
44222 var WebSocket = (function () {
44223 function WebSocket($rootScope) {
44224 this.$rootScope = $rootScope;
44225 this.socket = io.connect();
44227 WebSocket.prototype.on = function (eventName, callback) {
44228 var socket = this.socket;
44229 var rootScope = this.$rootScope;
44230 socket.on(eventName, function () {
44231 var args = arguments;
44232 rootScope.$apply(function () {
44233 callback.apply(socket, args);
44237 WebSocket.prototype.emit = function (eventName, data, callback) {
44238 var socket = this.socket;
44239 var rootScope = this.$rootScope;
44240 this.socket.emit(eventName, data, function () {
44241 var args = arguments;
44242 rootScope.$apply(function () {
44244 callback.apply(socket, args);
44250 services.WebSocket = WebSocket;
44251 })(services = app.services || (app.services = {}));
44252 })(app || (app = {}));
44256 (function (directives) {
44257 var Command = (function () {
44258 function Command() {
44259 this.restrict = 'E';
44260 this.replace = true;
44262 this.controller = 'commandController';
44263 this.controllerAs = 'ctrl';
44264 this.bindToController = {
44270 this.templateUrl = 'templates/command.html';
44272 Command.Factory = function () {
44273 var directive = function () {
44274 return new Command();
44276 directive.$inject = [];
44281 directives.Command = Command;
44282 var CommandController = (function () {
44283 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
44284 this.APIEndPoint = APIEndPoint;
44285 this.$scope = $scope;
44286 this.MyModal = MyModal;
44287 this.WebSocket = WebSocket;
44288 var controller = this;
44290 .getOptionControlFile(this.name)
44292 .then(function (result) {
44293 controller.options = result.info;
44298 .then(function (result) {
44299 controller.dirs = result.info;
44301 this.heading = "[" + this.index + "]: dcdFilePrint";
44302 this.isOpen = true;
44303 this.$scope.$on('close', function () {
44304 controller.isOpen = false;
44306 this.WebSocket.on('console', function (msg) {
44307 controller.messages = msg.split('\n');
44310 CommandController.prototype.submit = function () {
44312 angular.forEach(this.options, function (option) {
44314 name: option.option,
44317 angular.forEach(option.arg, function (arg) {
44319 if (typeof arg.input === 'object') {
44320 obj.arguments.push(arg.input.name);
44323 obj.arguments.push(arg.input);
44327 if (obj.arguments.length > 0) {
44332 command: this.name,
44333 workspace: this.workspace.fileId,
44337 .execute(JSON.stringify(execObj))
44338 .then(function (result) {
44339 console.log(result);
44342 CommandController.prototype.removeMySelf = function (index) {
44343 this.remove()(index, this.list);
44345 CommandController.prototype.reloadFiles = function () {
44347 var fileId = this.workspace.fileId;
44351 .then(function (result) {
44352 var status = result.status;
44353 if (status === 'success') {
44354 _this.files = result.info;
44357 console.log(result.message);
44361 CommandController.prototype.debug = function () {
44364 .then(function (result) {
44365 console.log(result);
44368 CommandController.prototype.help = function () {
44371 .then(function (result) {
44372 console.log(result);
44375 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
44376 return CommandController;
44378 directives.CommandController = CommandController;
44379 })(directives = app.directives || (app.directives = {}));
44380 })(app || (app = {}));
44384 (function (directives) {
44385 var HeaderMenu = (function () {
44386 function HeaderMenu() {
44387 this.restrict = 'E';
44388 this.replace = true;
44389 this.templateUrl = 'templates/header-menu.html';
44391 HeaderMenu.Factory = function () {
44392 var directive = function () {
44393 return new HeaderMenu();
44399 directives.HeaderMenu = HeaderMenu;
44400 })(directives = app.directives || (app.directives = {}));
44401 })(app || (app = {}));
44405 (function (directives) {
44406 var Option = (function () {
44407 function Option() {
44408 this.restrict = 'E';
44409 this.replace = true;
44410 this.controller = 'optionController';
44411 this.bindToController = {
44416 this.templateUrl = 'templates/option.html';
44417 this.controllerAs = 'ctrl';
44419 Option.Factory = function () {
44420 var directive = function () {
44421 return new Option();
44423 directive.$inject = [];
44428 directives.Option = Option;
44429 var OptionController = (function () {
44430 function OptionController() {
44431 var controller = this;
44432 angular.forEach(controller.info.arg, function (arg) {
44433 if (arg.initialValue) {
44434 if (arg.formType === 'number') {
44435 arg.input = parseInt(arg.initialValue);
44438 arg.input = arg.initialValue;
44443 OptionController.$inject = [];
44444 return OptionController;
44446 directives.OptionController = OptionController;
44447 })(directives = app.directives || (app.directives = {}));
44448 })(app || (app = {}));
44452 (function (directives) {
44453 var Directory = (function () {
44454 function Directory() {
44455 this.restrict = 'E';
44456 this.replace = true;
44457 this.controller = 'directoryController';
44458 this.controllerAs = 'ctrl';
44459 this.bindToController = {
44465 this.templateUrl = 'templates/directory.html';
44467 Directory.Factory = function () {
44468 var directive = function () {
44469 return new Directory();
44475 directives.Directory = Directory;
44476 var DirectoryController = (function () {
44477 function DirectoryController(APIEndPoint, $scope) {
44478 this.APIEndPoint = APIEndPoint;
44479 this.$scope = $scope;
44480 var controller = this;
44482 .getFiles(this.info.fileId)
44484 .then(function (result) {
44485 if (result.status === 'success') {
44486 controller.files = result.info;
44487 angular.forEach(result.info, function (file) {
44488 if (file.fileType === '0') {
44490 if (controller.info.path === '/') {
44491 o.path = '/' + file.name;
44494 o.path = controller.info.path + '/' + file.name;
44496 controller.add()(o, controller.list);
44503 DirectoryController.$inject = ['APIEndPoint', '$scope'];
44504 return DirectoryController;
44506 directives.DirectoryController = DirectoryController;
44507 })(directives = app.directives || (app.directives = {}));
44508 })(app || (app = {}));
44512 (function (controllers) {
44513 var Execution = (function () {
44514 function Execution(MyModal, $scope) {
44515 this.MyModal = MyModal;
44516 this.$scope = $scope;
44517 this.commandInfoList = [];
44520 Execution.prototype.add = function () {
44521 this.$scope.$broadcast('close');
44522 var commandInfoList = this.commandInfoList;
44523 var commandInstance = this.MyModal.selectCommand();
44526 .then(function (command) {
44527 commandInfoList.push(new app.declares.CommandInfo(command));
44530 Execution.prototype.open = function () {
44531 var result = this.MyModal.open('SelectCommand');
44532 console.log(result);
44534 Execution.prototype.remove = function (index, list) {
44535 list.splice(index, 1);
44537 Execution.prototype.close = function () {
44538 console.log("close");
44540 Execution.$inject = ['MyModal', '$scope'];
44543 controllers.Execution = Execution;
44544 })(controllers = app.controllers || (app.controllers = {}));
44545 })(app || (app = {}));
44549 (function (controllers) {
44550 var Workspace = (function () {
44551 function Workspace($scope, APIEndPoint, MyModal) {
44552 this.$scope = $scope;
44553 this.APIEndPoint = APIEndPoint;
44554 this.MyModal = MyModal;
44555 this.directoryList = [];
44556 var controller = this;
44557 var directoryList = this.directoryList;
44559 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
44567 directoryList.push(o);
44569 Workspace.prototype.addDirectory = function (info, directoryList) {
44570 directoryList.push(info);
44572 Workspace.prototype.debug = function () {
44573 this.MyModal.preview();
44575 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
44578 controllers.Workspace = Workspace;
44579 })(controllers = app.controllers || (app.controllers = {}));
44580 })(app || (app = {}));
44584 (function (controllers) {
44585 var History = (function () {
44586 function History($scope) {
44587 this.page = "History";
44589 History.$inject = ['$scope'];
44592 controllers.History = History;
44593 })(controllers = app.controllers || (app.controllers = {}));
44594 })(app || (app = {}));
44598 (function (controllers) {
44599 var SelectCommand = (function () {
44600 function SelectCommand($scope, APIEndPoint, $modalInstance) {
44601 this.APIEndPoint = APIEndPoint;
44602 this.$modalInstance = $modalInstance;
44603 var controller = this;
44606 .$promise.then(function (result) {
44607 controller.tags = result.info;
44611 .$promise.then(function (result) {
44612 controller.commands = result.info;
44614 this.currentTag = 'all';
44616 SelectCommand.prototype.changeTag = function (tag) {
44617 this.currentTag = tag;
44619 SelectCommand.prototype.selectCommand = function (command) {
44620 this.$modalInstance.close(command);
44622 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
44623 return SelectCommand;
44625 controllers.SelectCommand = SelectCommand;
44626 })(controllers = app.controllers || (app.controllers = {}));
44627 })(app || (app = {}));
44631 (function (controllers) {
44632 var Preview = (function () {
44633 function Preview($scope, APIEndPoint, $modalInstance) {
44634 this.APIEndPoint = APIEndPoint;
44635 this.$modalInstance = $modalInstance;
44636 var controller = this;
44637 console.log('preview');
44639 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
44642 controllers.Preview = Preview;
44643 })(controllers = app.controllers || (app.controllers = {}));
44644 })(app || (app = {}));
44646 (function (filters) {
44648 return function (commands, tag) {
44650 angular.forEach(commands, function (command) {
44652 angular.forEach(command.tags, function (value) {
44657 result.push(command);
44663 })(filters || (filters = {}));
44667 var appName = 'zephyr';
44668 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
44669 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
44670 $urlRouterProvider.otherwise('/execution');
44671 $locationProvider.html5Mode({
44676 .state('execution', {
44678 templateUrl: 'templates/execution.html',
44679 controller: 'executionController',
44682 .state('workspace', {
44684 templateUrl: 'templates/workspace.html',
44685 controller: 'workspaceController',
44688 .state('history', {
44690 templateUrl: 'templates/history.html',
44691 controller: 'historyController',
44695 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
44696 app.zephyr.service('MyModal', app.services.MyModal);
44697 app.zephyr.service('WebSocket', app.services.WebSocket);
44698 app.zephyr.filter('Tag', filters.Tag);
44699 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
44700 app.zephyr.controller('previewController', app.controllers.Preview);
44701 app.zephyr.controller('executionController', app.controllers.Execution);
44702 app.zephyr.controller('workspaceController', app.controllers.Workspace);
44703 app.zephyr.controller('historyController', app.controllers.History);
44704 app.zephyr.controller('commandController', app.directives.CommandController);
44705 app.zephyr.controller('optionController', app.directives.OptionController);
44706 app.zephyr.controller('directoryController', app.directives.DirectoryController);
44707 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
44708 app.zephyr.directive('command', app.directives.Command.Factory());
44709 app.zephyr.directive('option', app.directives.Option.Factory());
44710 app.zephyr.directive('directory', app.directives.Directory.Factory());
44711 })(app || (app = {}));
44716 /***/ function(module, exports) {
44721 (function (declares) {
44722 var CommandInfo = (function () {
44723 function CommandInfo(name) {
44726 return CommandInfo;
44728 declares.CommandInfo = CommandInfo;
44729 })(declares = app.declares || (app.declares = {}));
44730 })(app || (app = {}));
44734 (function (services) {
44735 var APIEndPoint = (function () {
44736 function APIEndPoint($resource, $http) {
44737 this.$resource = $resource;
44738 this.$http = $http;
44740 APIEndPoint.prototype.resource = function (endPoint, data) {
44741 var customAction = {
44747 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
44749 return this.$resource(endPoint, {}, { execute: execute });
44751 APIEndPoint.prototype.getOptionControlFile = function (command) {
44752 var endPoint = '/api/v1/optionControlFile/' + command;
44753 return this.resource(endPoint, {}).get();
44755 APIEndPoint.prototype.getFiles = function (fileId) {
44756 var endPoint = '/api/v1/workspace';
44758 endPoint += '/' + fileId;
44760 return this.resource(endPoint, {}).get();
44762 APIEndPoint.prototype.getDirectories = function () {
44763 var endPoint = '/api/v1/all/workspace/directory';
44764 return this.resource(endPoint, {}).get();
44766 APIEndPoint.prototype.getTags = function () {
44767 var endPoint = '/api/v1/tagList';
44768 return this.resource(endPoint, {}).get();
44770 APIEndPoint.prototype.getCommands = function () {
44771 var endPoint = '/api/v1/commandList';
44772 return this.resource(endPoint, {}).get();
44774 APIEndPoint.prototype.execute = function (data) {
44775 var endPoint = '/api/v1/execution';
44776 var fd = new FormData();
44777 fd.append('data', data);
44778 return this.$http.post(endPoint, fd, {
44779 headers: { 'Content-Type': undefined },
44780 transformRequest: angular.identity
44783 APIEndPoint.prototype.debug = function () {
44784 var endPoint = '/api/v1/debug';
44785 return this.$http.get(endPoint);
44787 APIEndPoint.prototype.help = function (command) {
44788 var endPoint = '/api/v1/help/' + command;
44789 return this.$http.get(endPoint);
44791 return APIEndPoint;
44793 services.APIEndPoint = APIEndPoint;
44794 })(services = app.services || (app.services = {}));
44795 })(app || (app = {}));
44799 (function (services) {
44800 var MyModal = (function () {
44801 function MyModal($uibModal) {
44802 this.$uibModal = $uibModal;
44803 this.modalOption = {
44810 MyModal.prototype.open = function (modalName) {
44811 if (modalName === 'SelectCommand') {
44812 this.modalOption.templateUrl = 'templates/select-command.html';
44813 this.modalOption.size = 'lg';
44815 return this.$uibModal.open(this.modalOption);
44817 MyModal.prototype.selectCommand = function () {
44818 this.modalOption.templateUrl = 'templates/select-command.html';
44819 this.modalOption.controller = 'selectCommandController';
44820 this.modalOption.controllerAs = 'c';
44821 this.modalOption.size = 'lg';
44822 return this.$uibModal.open(this.modalOption);
44824 MyModal.prototype.preview = function () {
44825 this.modalOption.templateUrl = 'templates/preview.html';
44826 this.modalOption.controller = 'previewController';
44827 this.modalOption.controllerAs = 'c';
44828 this.modalOption.size = 'lg';
44829 return this.$uibModal.open(this.modalOption);
44831 MyModal.$inject = ['$uibModal'];
44834 services.MyModal = MyModal;
44835 })(services = app.services || (app.services = {}));
44836 })(app || (app = {}));
44840 (function (services) {
44841 var WebSocket = (function () {
44842 function WebSocket($rootScope) {
44843 this.$rootScope = $rootScope;
44844 this.socket = io.connect();
44846 WebSocket.prototype.on = function (eventName, callback) {
44847 var socket = this.socket;
44848 var rootScope = this.$rootScope;
44849 socket.on(eventName, function () {
44850 var args = arguments;
44851 rootScope.$apply(function () {
44852 callback.apply(socket, args);
44856 WebSocket.prototype.emit = function (eventName, data, callback) {
44857 var socket = this.socket;
44858 var rootScope = this.$rootScope;
44859 this.socket.emit(eventName, data, function () {
44860 var args = arguments;
44861 rootScope.$apply(function () {
44863 callback.apply(socket, args);
44869 services.WebSocket = WebSocket;
44870 })(services = app.services || (app.services = {}));
44871 })(app || (app = {}));
44875 (function (directives) {
44876 var Command = (function () {
44877 function Command() {
44878 this.restrict = 'E';
44879 this.replace = true;
44881 this.controller = 'commandController';
44882 this.controllerAs = 'ctrl';
44883 this.bindToController = {
44889 this.templateUrl = 'templates/command.html';
44891 Command.Factory = function () {
44892 var directive = function () {
44893 return new Command();
44895 directive.$inject = [];
44900 directives.Command = Command;
44901 var CommandController = (function () {
44902 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
44903 this.APIEndPoint = APIEndPoint;
44904 this.$scope = $scope;
44905 this.MyModal = MyModal;
44906 this.WebSocket = WebSocket;
44907 var controller = this;
44909 .getOptionControlFile(this.name)
44911 .then(function (result) {
44912 controller.options = result.info;
44917 .then(function (result) {
44918 controller.dirs = result.info;
44920 this.heading = "[" + this.index + "]: dcdFilePrint";
44921 this.isOpen = true;
44922 this.$scope.$on('close', function () {
44923 controller.isOpen = false;
44925 this.WebSocket.on('console', function (msg) {
44926 controller.messages = msg.split('\n');
44929 CommandController.prototype.submit = function () {
44931 angular.forEach(this.options, function (option) {
44933 name: option.option,
44936 angular.forEach(option.arg, function (arg) {
44938 if (typeof arg.input === 'object') {
44939 obj.arguments.push(arg.input.name);
44942 obj.arguments.push(arg.input);
44946 if (obj.arguments.length > 0) {
44951 command: this.name,
44952 workspace: this.workspace.fileId,
44956 .execute(JSON.stringify(execObj))
44957 .then(function (result) {
44958 console.log(result);
44961 CommandController.prototype.removeMySelf = function (index) {
44962 this.remove()(index, this.list);
44964 CommandController.prototype.reloadFiles = function () {
44966 var fileId = this.workspace.fileId;
44970 .then(function (result) {
44971 var status = result.status;
44972 if (status === 'success') {
44973 _this.files = result.info;
44976 console.log(result.message);
44980 CommandController.prototype.debug = function () {
44983 .then(function (result) {
44984 console.log(result);
44987 CommandController.prototype.help = function () {
44990 .then(function (result) {
44991 console.log(result);
44994 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
44995 return CommandController;
44997 directives.CommandController = CommandController;
44998 })(directives = app.directives || (app.directives = {}));
44999 })(app || (app = {}));
45003 (function (directives) {
45004 var HeaderMenu = (function () {
45005 function HeaderMenu() {
45006 this.restrict = 'E';
45007 this.replace = true;
45008 this.templateUrl = 'templates/header-menu.html';
45010 HeaderMenu.Factory = function () {
45011 var directive = function () {
45012 return new HeaderMenu();
45018 directives.HeaderMenu = HeaderMenu;
45019 })(directives = app.directives || (app.directives = {}));
45020 })(app || (app = {}));
45024 (function (directives) {
45025 var Option = (function () {
45026 function Option() {
45027 this.restrict = 'E';
45028 this.replace = true;
45029 this.controller = 'optionController';
45030 this.bindToController = {
45035 this.templateUrl = 'templates/option.html';
45036 this.controllerAs = 'ctrl';
45038 Option.Factory = function () {
45039 var directive = function () {
45040 return new Option();
45042 directive.$inject = [];
45047 directives.Option = Option;
45048 var OptionController = (function () {
45049 function OptionController() {
45050 var controller = this;
45051 angular.forEach(controller.info.arg, function (arg) {
45052 if (arg.initialValue) {
45053 if (arg.formType === 'number') {
45054 arg.input = parseInt(arg.initialValue);
45057 arg.input = arg.initialValue;
45062 OptionController.$inject = [];
45063 return OptionController;
45065 directives.OptionController = OptionController;
45066 })(directives = app.directives || (app.directives = {}));
45067 })(app || (app = {}));
45071 (function (directives) {
45072 var Directory = (function () {
45073 function Directory() {
45074 this.restrict = 'E';
45075 this.replace = true;
45076 this.controller = 'directoryController';
45077 this.controllerAs = 'ctrl';
45078 this.bindToController = {
45084 this.templateUrl = 'templates/directory.html';
45086 Directory.Factory = function () {
45087 var directive = function () {
45088 return new Directory();
45094 directives.Directory = Directory;
45095 var DirectoryController = (function () {
45096 function DirectoryController(APIEndPoint, $scope) {
45097 this.APIEndPoint = APIEndPoint;
45098 this.$scope = $scope;
45099 var controller = this;
45101 .getFiles(this.info.fileId)
45103 .then(function (result) {
45104 if (result.status === 'success') {
45105 controller.files = result.info;
45106 angular.forEach(result.info, function (file) {
45107 if (file.fileType === '0') {
45109 if (controller.info.path === '/') {
45110 o.path = '/' + file.name;
45113 o.path = controller.info.path + '/' + file.name;
45115 controller.add()(o, controller.list);
45122 DirectoryController.$inject = ['APIEndPoint', '$scope'];
45123 return DirectoryController;
45125 directives.DirectoryController = DirectoryController;
45126 })(directives = app.directives || (app.directives = {}));
45127 })(app || (app = {}));
45131 (function (controllers) {
45132 var Execution = (function () {
45133 function Execution(MyModal, $scope) {
45134 this.MyModal = MyModal;
45135 this.$scope = $scope;
45136 this.commandInfoList = [];
45139 Execution.prototype.add = function () {
45140 this.$scope.$broadcast('close');
45141 var commandInfoList = this.commandInfoList;
45142 var commandInstance = this.MyModal.selectCommand();
45145 .then(function (command) {
45146 commandInfoList.push(new app.declares.CommandInfo(command));
45149 Execution.prototype.open = function () {
45150 var result = this.MyModal.open('SelectCommand');
45151 console.log(result);
45153 Execution.prototype.remove = function (index, list) {
45154 list.splice(index, 1);
45156 Execution.prototype.close = function () {
45157 console.log("close");
45159 Execution.$inject = ['MyModal', '$scope'];
45162 controllers.Execution = Execution;
45163 })(controllers = app.controllers || (app.controllers = {}));
45164 })(app || (app = {}));
45168 (function (controllers) {
45169 var Workspace = (function () {
45170 function Workspace($scope, APIEndPoint, MyModal) {
45171 this.$scope = $scope;
45172 this.APIEndPoint = APIEndPoint;
45173 this.MyModal = MyModal;
45174 this.directoryList = [];
45175 var controller = this;
45176 var directoryList = this.directoryList;
45178 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
45186 directoryList.push(o);
45188 Workspace.prototype.addDirectory = function (info, directoryList) {
45189 directoryList.push(info);
45191 Workspace.prototype.debug = function () {
45192 this.MyModal.preview();
45194 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
45197 controllers.Workspace = Workspace;
45198 })(controllers = app.controllers || (app.controllers = {}));
45199 })(app || (app = {}));
45203 (function (controllers) {
45204 var History = (function () {
45205 function History($scope) {
45206 this.page = "History";
45208 History.$inject = ['$scope'];
45211 controllers.History = History;
45212 })(controllers = app.controllers || (app.controllers = {}));
45213 })(app || (app = {}));
45217 (function (controllers) {
45218 var SelectCommand = (function () {
45219 function SelectCommand($scope, APIEndPoint, $modalInstance) {
45220 this.APIEndPoint = APIEndPoint;
45221 this.$modalInstance = $modalInstance;
45222 var controller = this;
45225 .$promise.then(function (result) {
45226 controller.tags = result.info;
45230 .$promise.then(function (result) {
45231 controller.commands = result.info;
45233 this.currentTag = 'all';
45235 SelectCommand.prototype.changeTag = function (tag) {
45236 this.currentTag = tag;
45238 SelectCommand.prototype.selectCommand = function (command) {
45239 this.$modalInstance.close(command);
45241 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
45242 return SelectCommand;
45244 controllers.SelectCommand = SelectCommand;
45245 })(controllers = app.controllers || (app.controllers = {}));
45246 })(app || (app = {}));
45250 (function (controllers) {
45251 var Preview = (function () {
45252 function Preview($scope, APIEndPoint, $modalInstance) {
45253 this.APIEndPoint = APIEndPoint;
45254 this.$modalInstance = $modalInstance;
45255 var controller = this;
45256 console.log('preview');
45258 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
45261 controllers.Preview = Preview;
45262 })(controllers = app.controllers || (app.controllers = {}));
45263 })(app || (app = {}));
45265 (function (filters) {
45267 return function (commands, tag) {
45269 angular.forEach(commands, function (command) {
45271 angular.forEach(command.tags, function (value) {
45276 result.push(command);
45282 })(filters || (filters = {}));
45286 var appName = 'zephyr';
45287 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
45288 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
45289 $urlRouterProvider.otherwise('/execution');
45290 $locationProvider.html5Mode({
45295 .state('execution', {
45297 templateUrl: 'templates/execution.html',
45298 controller: 'executionController',
45301 .state('workspace', {
45303 templateUrl: 'templates/workspace.html',
45304 controller: 'workspaceController',
45307 .state('history', {
45309 templateUrl: 'templates/history.html',
45310 controller: 'historyController',
45314 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
45315 app.zephyr.service('MyModal', app.services.MyModal);
45316 app.zephyr.service('WebSocket', app.services.WebSocket);
45317 app.zephyr.filter('Tag', filters.Tag);
45318 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
45319 app.zephyr.controller('previewController', app.controllers.Preview);
45320 app.zephyr.controller('executionController', app.controllers.Execution);
45321 app.zephyr.controller('workspaceController', app.controllers.Workspace);
45322 app.zephyr.controller('historyController', app.controllers.History);
45323 app.zephyr.controller('commandController', app.directives.CommandController);
45324 app.zephyr.controller('optionController', app.directives.OptionController);
45325 app.zephyr.controller('directoryController', app.directives.DirectoryController);
45326 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
45327 app.zephyr.directive('command', app.directives.Command.Factory());
45328 app.zephyr.directive('option', app.directives.Option.Factory());
45329 app.zephyr.directive('directory', app.directives.Directory.Factory());
45330 })(app || (app = {}));
45335 /***/ function(module, exports) {
45340 (function (declares) {
45341 var CommandInfo = (function () {
45342 function CommandInfo(name) {
45345 return CommandInfo;
45347 declares.CommandInfo = CommandInfo;
45348 })(declares = app.declares || (app.declares = {}));
45349 })(app || (app = {}));
45353 (function (services) {
45354 var APIEndPoint = (function () {
45355 function APIEndPoint($resource, $http) {
45356 this.$resource = $resource;
45357 this.$http = $http;
45359 APIEndPoint.prototype.resource = function (endPoint, data) {
45360 var customAction = {
45366 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
45368 return this.$resource(endPoint, {}, { execute: execute });
45370 APIEndPoint.prototype.getOptionControlFile = function (command) {
45371 var endPoint = '/api/v1/optionControlFile/' + command;
45372 return this.resource(endPoint, {}).get();
45374 APIEndPoint.prototype.getFiles = function (fileId) {
45375 var endPoint = '/api/v1/workspace';
45377 endPoint += '/' + fileId;
45379 return this.resource(endPoint, {}).get();
45381 APIEndPoint.prototype.getDirectories = function () {
45382 var endPoint = '/api/v1/all/workspace/directory';
45383 return this.resource(endPoint, {}).get();
45385 APIEndPoint.prototype.getTags = function () {
45386 var endPoint = '/api/v1/tagList';
45387 return this.resource(endPoint, {}).get();
45389 APIEndPoint.prototype.getCommands = function () {
45390 var endPoint = '/api/v1/commandList';
45391 return this.resource(endPoint, {}).get();
45393 APIEndPoint.prototype.execute = function (data) {
45394 var endPoint = '/api/v1/execution';
45395 var fd = new FormData();
45396 fd.append('data', data);
45397 return this.$http.post(endPoint, fd, {
45398 headers: { 'Content-Type': undefined },
45399 transformRequest: angular.identity
45402 APIEndPoint.prototype.debug = function () {
45403 var endPoint = '/api/v1/debug';
45404 return this.$http.get(endPoint);
45406 APIEndPoint.prototype.help = function (command) {
45407 var endPoint = '/api/v1/help/' + command;
45408 return this.$http.get(endPoint);
45410 return APIEndPoint;
45412 services.APIEndPoint = APIEndPoint;
45413 })(services = app.services || (app.services = {}));
45414 })(app || (app = {}));
45418 (function (services) {
45419 var MyModal = (function () {
45420 function MyModal($uibModal) {
45421 this.$uibModal = $uibModal;
45422 this.modalOption = {
45429 MyModal.prototype.open = function (modalName) {
45430 if (modalName === 'SelectCommand') {
45431 this.modalOption.templateUrl = 'templates/select-command.html';
45432 this.modalOption.size = 'lg';
45434 return this.$uibModal.open(this.modalOption);
45436 MyModal.prototype.selectCommand = function () {
45437 this.modalOption.templateUrl = 'templates/select-command.html';
45438 this.modalOption.controller = 'selectCommandController';
45439 this.modalOption.controllerAs = 'c';
45440 this.modalOption.size = 'lg';
45441 return this.$uibModal.open(this.modalOption);
45443 MyModal.prototype.preview = function () {
45444 this.modalOption.templateUrl = 'templates/preview.html';
45445 this.modalOption.controller = 'previewController';
45446 this.modalOption.controllerAs = 'c';
45447 this.modalOption.size = 'lg';
45448 return this.$uibModal.open(this.modalOption);
45450 MyModal.$inject = ['$uibModal'];
45453 services.MyModal = MyModal;
45454 })(services = app.services || (app.services = {}));
45455 })(app || (app = {}));
45459 (function (services) {
45460 var WebSocket = (function () {
45461 function WebSocket($rootScope) {
45462 this.$rootScope = $rootScope;
45463 this.socket = io.connect();
45465 WebSocket.prototype.on = function (eventName, callback) {
45466 var socket = this.socket;
45467 var rootScope = this.$rootScope;
45468 socket.on(eventName, function () {
45469 var args = arguments;
45470 rootScope.$apply(function () {
45471 callback.apply(socket, args);
45475 WebSocket.prototype.emit = function (eventName, data, callback) {
45476 var socket = this.socket;
45477 var rootScope = this.$rootScope;
45478 this.socket.emit(eventName, data, function () {
45479 var args = arguments;
45480 rootScope.$apply(function () {
45482 callback.apply(socket, args);
45488 services.WebSocket = WebSocket;
45489 })(services = app.services || (app.services = {}));
45490 })(app || (app = {}));
45494 (function (directives) {
45495 var Command = (function () {
45496 function Command() {
45497 this.restrict = 'E';
45498 this.replace = true;
45500 this.controller = 'commandController';
45501 this.controllerAs = 'ctrl';
45502 this.bindToController = {
45508 this.templateUrl = 'templates/command.html';
45510 Command.Factory = function () {
45511 var directive = function () {
45512 return new Command();
45514 directive.$inject = [];
45519 directives.Command = Command;
45520 var CommandController = (function () {
45521 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
45522 this.APIEndPoint = APIEndPoint;
45523 this.$scope = $scope;
45524 this.MyModal = MyModal;
45525 this.WebSocket = WebSocket;
45526 var controller = this;
45528 .getOptionControlFile(this.name)
45530 .then(function (result) {
45531 controller.options = result.info;
45536 .then(function (result) {
45537 controller.dirs = result.info;
45539 this.heading = "[" + this.index + "]: dcdFilePrint";
45540 this.isOpen = true;
45541 this.$scope.$on('close', function () {
45542 controller.isOpen = false;
45544 this.WebSocket.on('console', function (msg) {
45545 controller.messages = msg.split('\n');
45548 CommandController.prototype.submit = function () {
45550 angular.forEach(this.options, function (option) {
45552 name: option.option,
45555 angular.forEach(option.arg, function (arg) {
45557 if (typeof arg.input === 'object') {
45558 obj.arguments.push(arg.input.name);
45561 obj.arguments.push(arg.input);
45565 if (obj.arguments.length > 0) {
45570 command: this.name,
45571 workspace: this.workspace.fileId,
45575 .execute(JSON.stringify(execObj))
45576 .then(function (result) {
45577 console.log(result);
45580 CommandController.prototype.removeMySelf = function (index) {
45581 this.remove()(index, this.list);
45583 CommandController.prototype.reloadFiles = function () {
45585 var fileId = this.workspace.fileId;
45589 .then(function (result) {
45590 var status = result.status;
45591 if (status === 'success') {
45592 _this.files = result.info;
45595 console.log(result.message);
45599 CommandController.prototype.debug = function () {
45602 .then(function (result) {
45603 console.log(result);
45606 CommandController.prototype.help = function () {
45609 .then(function (result) {
45610 console.log(result);
45613 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
45614 return CommandController;
45616 directives.CommandController = CommandController;
45617 })(directives = app.directives || (app.directives = {}));
45618 })(app || (app = {}));
45622 (function (directives) {
45623 var HeaderMenu = (function () {
45624 function HeaderMenu() {
45625 this.restrict = 'E';
45626 this.replace = true;
45627 this.templateUrl = 'templates/header-menu.html';
45629 HeaderMenu.Factory = function () {
45630 var directive = function () {
45631 return new HeaderMenu();
45637 directives.HeaderMenu = HeaderMenu;
45638 })(directives = app.directives || (app.directives = {}));
45639 })(app || (app = {}));
45643 (function (directives) {
45644 var Option = (function () {
45645 function Option() {
45646 this.restrict = 'E';
45647 this.replace = true;
45648 this.controller = 'optionController';
45649 this.bindToController = {
45654 this.templateUrl = 'templates/option.html';
45655 this.controllerAs = 'ctrl';
45657 Option.Factory = function () {
45658 var directive = function () {
45659 return new Option();
45661 directive.$inject = [];
45666 directives.Option = Option;
45667 var OptionController = (function () {
45668 function OptionController() {
45669 var controller = this;
45670 angular.forEach(controller.info.arg, function (arg) {
45671 if (arg.initialValue) {
45672 if (arg.formType === 'number') {
45673 arg.input = parseInt(arg.initialValue);
45676 arg.input = arg.initialValue;
45681 OptionController.$inject = [];
45682 return OptionController;
45684 directives.OptionController = OptionController;
45685 })(directives = app.directives || (app.directives = {}));
45686 })(app || (app = {}));
45690 (function (directives) {
45691 var Directory = (function () {
45692 function Directory() {
45693 this.restrict = 'E';
45694 this.replace = true;
45695 this.controller = 'directoryController';
45696 this.controllerAs = 'ctrl';
45697 this.bindToController = {
45703 this.templateUrl = 'templates/directory.html';
45705 Directory.Factory = function () {
45706 var directive = function () {
45707 return new Directory();
45713 directives.Directory = Directory;
45714 var DirectoryController = (function () {
45715 function DirectoryController(APIEndPoint, $scope) {
45716 this.APIEndPoint = APIEndPoint;
45717 this.$scope = $scope;
45718 var controller = this;
45720 .getFiles(this.info.fileId)
45722 .then(function (result) {
45723 if (result.status === 'success') {
45724 controller.files = result.info;
45725 angular.forEach(result.info, function (file) {
45726 if (file.fileType === '0') {
45728 if (controller.info.path === '/') {
45729 o.path = '/' + file.name;
45732 o.path = controller.info.path + '/' + file.name;
45734 controller.add()(o, controller.list);
45741 DirectoryController.$inject = ['APIEndPoint', '$scope'];
45742 return DirectoryController;
45744 directives.DirectoryController = DirectoryController;
45745 })(directives = app.directives || (app.directives = {}));
45746 })(app || (app = {}));
45750 (function (controllers) {
45751 var Execution = (function () {
45752 function Execution(MyModal, $scope) {
45753 this.MyModal = MyModal;
45754 this.$scope = $scope;
45755 this.commandInfoList = [];
45758 Execution.prototype.add = function () {
45759 this.$scope.$broadcast('close');
45760 var commandInfoList = this.commandInfoList;
45761 var commandInstance = this.MyModal.selectCommand();
45764 .then(function (command) {
45765 commandInfoList.push(new app.declares.CommandInfo(command));
45768 Execution.prototype.open = function () {
45769 var result = this.MyModal.open('SelectCommand');
45770 console.log(result);
45772 Execution.prototype.remove = function (index, list) {
45773 list.splice(index, 1);
45775 Execution.prototype.close = function () {
45776 console.log("close");
45778 Execution.$inject = ['MyModal', '$scope'];
45781 controllers.Execution = Execution;
45782 })(controllers = app.controllers || (app.controllers = {}));
45783 })(app || (app = {}));
45787 (function (controllers) {
45788 var Workspace = (function () {
45789 function Workspace($scope, APIEndPoint, MyModal) {
45790 this.$scope = $scope;
45791 this.APIEndPoint = APIEndPoint;
45792 this.MyModal = MyModal;
45793 this.directoryList = [];
45794 var controller = this;
45795 var directoryList = this.directoryList;
45797 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
45805 directoryList.push(o);
45807 Workspace.prototype.addDirectory = function (info, directoryList) {
45808 directoryList.push(info);
45810 Workspace.prototype.debug = function () {
45811 this.MyModal.preview();
45813 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
45816 controllers.Workspace = Workspace;
45817 })(controllers = app.controllers || (app.controllers = {}));
45818 })(app || (app = {}));
45822 (function (controllers) {
45823 var History = (function () {
45824 function History($scope) {
45825 this.page = "History";
45827 History.$inject = ['$scope'];
45830 controllers.History = History;
45831 })(controllers = app.controllers || (app.controllers = {}));
45832 })(app || (app = {}));
45836 (function (controllers) {
45837 var SelectCommand = (function () {
45838 function SelectCommand($scope, APIEndPoint, $modalInstance) {
45839 this.APIEndPoint = APIEndPoint;
45840 this.$modalInstance = $modalInstance;
45841 var controller = this;
45844 .$promise.then(function (result) {
45845 controller.tags = result.info;
45849 .$promise.then(function (result) {
45850 controller.commands = result.info;
45852 this.currentTag = 'all';
45854 SelectCommand.prototype.changeTag = function (tag) {
45855 this.currentTag = tag;
45857 SelectCommand.prototype.selectCommand = function (command) {
45858 this.$modalInstance.close(command);
45860 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
45861 return SelectCommand;
45863 controllers.SelectCommand = SelectCommand;
45864 })(controllers = app.controllers || (app.controllers = {}));
45865 })(app || (app = {}));
45869 (function (controllers) {
45870 var Preview = (function () {
45871 function Preview($scope, APIEndPoint, $modalInstance) {
45872 this.APIEndPoint = APIEndPoint;
45873 this.$modalInstance = $modalInstance;
45874 var controller = this;
45875 console.log('preview');
45877 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
45880 controllers.Preview = Preview;
45881 })(controllers = app.controllers || (app.controllers = {}));
45882 })(app || (app = {}));
45884 (function (filters) {
45886 return function (commands, tag) {
45888 angular.forEach(commands, function (command) {
45890 angular.forEach(command.tags, function (value) {
45895 result.push(command);
45901 })(filters || (filters = {}));
45905 var appName = 'zephyr';
45906 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
45907 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
45908 $urlRouterProvider.otherwise('/execution');
45909 $locationProvider.html5Mode({
45914 .state('execution', {
45916 templateUrl: 'templates/execution.html',
45917 controller: 'executionController',
45920 .state('workspace', {
45922 templateUrl: 'templates/workspace.html',
45923 controller: 'workspaceController',
45926 .state('history', {
45928 templateUrl: 'templates/history.html',
45929 controller: 'historyController',
45933 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
45934 app.zephyr.service('MyModal', app.services.MyModal);
45935 app.zephyr.service('WebSocket', app.services.WebSocket);
45936 app.zephyr.filter('Tag', filters.Tag);
45937 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
45938 app.zephyr.controller('previewController', app.controllers.Preview);
45939 app.zephyr.controller('executionController', app.controllers.Execution);
45940 app.zephyr.controller('workspaceController', app.controllers.Workspace);
45941 app.zephyr.controller('historyController', app.controllers.History);
45942 app.zephyr.controller('commandController', app.directives.CommandController);
45943 app.zephyr.controller('optionController', app.directives.OptionController);
45944 app.zephyr.controller('directoryController', app.directives.DirectoryController);
45945 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
45946 app.zephyr.directive('command', app.directives.Command.Factory());
45947 app.zephyr.directive('option', app.directives.Option.Factory());
45948 app.zephyr.directive('directory', app.directives.Directory.Factory());
45949 })(app || (app = {}));
45954 /***/ function(module, exports) {
45959 (function (declares) {
45960 var CommandInfo = (function () {
45961 function CommandInfo(name) {
45964 return CommandInfo;
45966 declares.CommandInfo = CommandInfo;
45967 })(declares = app.declares || (app.declares = {}));
45968 })(app || (app = {}));
45972 (function (services) {
45973 var APIEndPoint = (function () {
45974 function APIEndPoint($resource, $http) {
45975 this.$resource = $resource;
45976 this.$http = $http;
45978 APIEndPoint.prototype.resource = function (endPoint, data) {
45979 var customAction = {
45985 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
45987 return this.$resource(endPoint, {}, { execute: execute });
45989 APIEndPoint.prototype.getOptionControlFile = function (command) {
45990 var endPoint = '/api/v1/optionControlFile/' + command;
45991 return this.resource(endPoint, {}).get();
45993 APIEndPoint.prototype.getFiles = function (fileId) {
45994 var endPoint = '/api/v1/workspace';
45996 endPoint += '/' + fileId;
45998 return this.resource(endPoint, {}).get();
46000 APIEndPoint.prototype.getDirectories = function () {
46001 var endPoint = '/api/v1/all/workspace/directory';
46002 return this.resource(endPoint, {}).get();
46004 APIEndPoint.prototype.getTags = function () {
46005 var endPoint = '/api/v1/tagList';
46006 return this.resource(endPoint, {}).get();
46008 APIEndPoint.prototype.getCommands = function () {
46009 var endPoint = '/api/v1/commandList';
46010 return this.resource(endPoint, {}).get();
46012 APIEndPoint.prototype.execute = function (data) {
46013 var endPoint = '/api/v1/execution';
46014 var fd = new FormData();
46015 fd.append('data', data);
46016 return this.$http.post(endPoint, fd, {
46017 headers: { 'Content-Type': undefined },
46018 transformRequest: angular.identity
46021 APIEndPoint.prototype.debug = function () {
46022 var endPoint = '/api/v1/debug';
46023 return this.$http.get(endPoint);
46025 APIEndPoint.prototype.help = function (command) {
46026 var endPoint = '/api/v1/help/' + command;
46027 return this.$http.get(endPoint);
46029 return APIEndPoint;
46031 services.APIEndPoint = APIEndPoint;
46032 })(services = app.services || (app.services = {}));
46033 })(app || (app = {}));
46037 (function (services) {
46038 var MyModal = (function () {
46039 function MyModal($uibModal) {
46040 this.$uibModal = $uibModal;
46041 this.modalOption = {
46048 MyModal.prototype.open = function (modalName) {
46049 if (modalName === 'SelectCommand') {
46050 this.modalOption.templateUrl = 'templates/select-command.html';
46051 this.modalOption.size = 'lg';
46053 return this.$uibModal.open(this.modalOption);
46055 MyModal.prototype.selectCommand = function () {
46056 this.modalOption.templateUrl = 'templates/select-command.html';
46057 this.modalOption.controller = 'selectCommandController';
46058 this.modalOption.controllerAs = 'c';
46059 this.modalOption.size = 'lg';
46060 return this.$uibModal.open(this.modalOption);
46062 MyModal.prototype.preview = function () {
46063 this.modalOption.templateUrl = 'templates/preview.html';
46064 this.modalOption.controller = 'previewController';
46065 this.modalOption.controllerAs = 'c';
46066 this.modalOption.size = 'lg';
46067 return this.$uibModal.open(this.modalOption);
46069 MyModal.$inject = ['$uibModal'];
46072 services.MyModal = MyModal;
46073 })(services = app.services || (app.services = {}));
46074 })(app || (app = {}));
46078 (function (services) {
46079 var WebSocket = (function () {
46080 function WebSocket($rootScope) {
46081 this.$rootScope = $rootScope;
46082 this.socket = io.connect();
46084 WebSocket.prototype.on = function (eventName, callback) {
46085 var socket = this.socket;
46086 var rootScope = this.$rootScope;
46087 socket.on(eventName, function () {
46088 var args = arguments;
46089 rootScope.$apply(function () {
46090 callback.apply(socket, args);
46094 WebSocket.prototype.emit = function (eventName, data, callback) {
46095 var socket = this.socket;
46096 var rootScope = this.$rootScope;
46097 this.socket.emit(eventName, data, function () {
46098 var args = arguments;
46099 rootScope.$apply(function () {
46101 callback.apply(socket, args);
46107 services.WebSocket = WebSocket;
46108 })(services = app.services || (app.services = {}));
46109 })(app || (app = {}));
46113 (function (directives) {
46114 var Command = (function () {
46115 function Command() {
46116 this.restrict = 'E';
46117 this.replace = true;
46119 this.controller = 'commandController';
46120 this.controllerAs = 'ctrl';
46121 this.bindToController = {
46127 this.templateUrl = 'templates/command.html';
46129 Command.Factory = function () {
46130 var directive = function () {
46131 return new Command();
46133 directive.$inject = [];
46138 directives.Command = Command;
46139 var CommandController = (function () {
46140 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
46141 this.APIEndPoint = APIEndPoint;
46142 this.$scope = $scope;
46143 this.MyModal = MyModal;
46144 this.WebSocket = WebSocket;
46145 var controller = this;
46147 .getOptionControlFile(this.name)
46149 .then(function (result) {
46150 controller.options = result.info;
46155 .then(function (result) {
46156 controller.dirs = result.info;
46158 this.heading = "[" + this.index + "]: dcdFilePrint";
46159 this.isOpen = true;
46160 this.$scope.$on('close', function () {
46161 controller.isOpen = false;
46163 this.WebSocket.on('console', function (msg) {
46164 controller.messages = msg.split('\n');
46167 CommandController.prototype.submit = function () {
46169 angular.forEach(this.options, function (option) {
46171 name: option.option,
46174 angular.forEach(option.arg, function (arg) {
46176 if (typeof arg.input === 'object') {
46177 obj.arguments.push(arg.input.name);
46180 obj.arguments.push(arg.input);
46184 if (obj.arguments.length > 0) {
46189 command: this.name,
46190 workspace: this.workspace.fileId,
46194 .execute(JSON.stringify(execObj))
46195 .then(function (result) {
46196 console.log(result);
46199 CommandController.prototype.removeMySelf = function (index) {
46200 this.remove()(index, this.list);
46202 CommandController.prototype.reloadFiles = function () {
46204 var fileId = this.workspace.fileId;
46208 .then(function (result) {
46209 var status = result.status;
46210 if (status === 'success') {
46211 _this.files = result.info;
46214 console.log(result.message);
46218 CommandController.prototype.debug = function () {
46221 .then(function (result) {
46222 console.log(result);
46225 CommandController.prototype.help = function () {
46228 .then(function (result) {
46229 console.log(result);
46232 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
46233 return CommandController;
46235 directives.CommandController = CommandController;
46236 })(directives = app.directives || (app.directives = {}));
46237 })(app || (app = {}));
46241 (function (directives) {
46242 var HeaderMenu = (function () {
46243 function HeaderMenu() {
46244 this.restrict = 'E';
46245 this.replace = true;
46246 this.templateUrl = 'templates/header-menu.html';
46248 HeaderMenu.Factory = function () {
46249 var directive = function () {
46250 return new HeaderMenu();
46256 directives.HeaderMenu = HeaderMenu;
46257 })(directives = app.directives || (app.directives = {}));
46258 })(app || (app = {}));
46262 (function (directives) {
46263 var Option = (function () {
46264 function Option() {
46265 this.restrict = 'E';
46266 this.replace = true;
46267 this.controller = 'optionController';
46268 this.bindToController = {
46273 this.templateUrl = 'templates/option.html';
46274 this.controllerAs = 'ctrl';
46276 Option.Factory = function () {
46277 var directive = function () {
46278 return new Option();
46280 directive.$inject = [];
46285 directives.Option = Option;
46286 var OptionController = (function () {
46287 function OptionController() {
46288 var controller = this;
46289 angular.forEach(controller.info.arg, function (arg) {
46290 if (arg.initialValue) {
46291 if (arg.formType === 'number') {
46292 arg.input = parseInt(arg.initialValue);
46295 arg.input = arg.initialValue;
46300 OptionController.$inject = [];
46301 return OptionController;
46303 directives.OptionController = OptionController;
46304 })(directives = app.directives || (app.directives = {}));
46305 })(app || (app = {}));
46309 (function (directives) {
46310 var Directory = (function () {
46311 function Directory() {
46312 this.restrict = 'E';
46313 this.replace = true;
46314 this.controller = 'directoryController';
46315 this.controllerAs = 'ctrl';
46316 this.bindToController = {
46322 this.templateUrl = 'templates/directory.html';
46324 Directory.Factory = function () {
46325 var directive = function () {
46326 return new Directory();
46332 directives.Directory = Directory;
46333 var DirectoryController = (function () {
46334 function DirectoryController(APIEndPoint, $scope) {
46335 this.APIEndPoint = APIEndPoint;
46336 this.$scope = $scope;
46337 var controller = this;
46339 .getFiles(this.info.fileId)
46341 .then(function (result) {
46342 if (result.status === 'success') {
46343 controller.files = result.info;
46344 angular.forEach(result.info, function (file) {
46345 if (file.fileType === '0') {
46347 if (controller.info.path === '/') {
46348 o.path = '/' + file.name;
46351 o.path = controller.info.path + '/' + file.name;
46353 controller.add()(o, controller.list);
46360 DirectoryController.$inject = ['APIEndPoint', '$scope'];
46361 return DirectoryController;
46363 directives.DirectoryController = DirectoryController;
46364 })(directives = app.directives || (app.directives = {}));
46365 })(app || (app = {}));
46369 (function (controllers) {
46370 var Execution = (function () {
46371 function Execution(MyModal, $scope) {
46372 this.MyModal = MyModal;
46373 this.$scope = $scope;
46374 this.commandInfoList = [];
46377 Execution.prototype.add = function () {
46378 this.$scope.$broadcast('close');
46379 var commandInfoList = this.commandInfoList;
46380 var commandInstance = this.MyModal.selectCommand();
46383 .then(function (command) {
46384 commandInfoList.push(new app.declares.CommandInfo(command));
46387 Execution.prototype.open = function () {
46388 var result = this.MyModal.open('SelectCommand');
46389 console.log(result);
46391 Execution.prototype.remove = function (index, list) {
46392 list.splice(index, 1);
46394 Execution.prototype.close = function () {
46395 console.log("close");
46397 Execution.$inject = ['MyModal', '$scope'];
46400 controllers.Execution = Execution;
46401 })(controllers = app.controllers || (app.controllers = {}));
46402 })(app || (app = {}));
46406 (function (controllers) {
46407 var Workspace = (function () {
46408 function Workspace($scope, APIEndPoint, MyModal) {
46409 this.$scope = $scope;
46410 this.APIEndPoint = APIEndPoint;
46411 this.MyModal = MyModal;
46412 this.directoryList = [];
46413 var controller = this;
46414 var directoryList = this.directoryList;
46416 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
46424 directoryList.push(o);
46426 Workspace.prototype.addDirectory = function (info, directoryList) {
46427 directoryList.push(info);
46429 Workspace.prototype.debug = function () {
46430 this.MyModal.preview();
46432 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
46435 controllers.Workspace = Workspace;
46436 })(controllers = app.controllers || (app.controllers = {}));
46437 })(app || (app = {}));
46441 (function (controllers) {
46442 var History = (function () {
46443 function History($scope) {
46444 this.page = "History";
46446 History.$inject = ['$scope'];
46449 controllers.History = History;
46450 })(controllers = app.controllers || (app.controllers = {}));
46451 })(app || (app = {}));
46455 (function (controllers) {
46456 var SelectCommand = (function () {
46457 function SelectCommand($scope, APIEndPoint, $modalInstance) {
46458 this.APIEndPoint = APIEndPoint;
46459 this.$modalInstance = $modalInstance;
46460 var controller = this;
46463 .$promise.then(function (result) {
46464 controller.tags = result.info;
46468 .$promise.then(function (result) {
46469 controller.commands = result.info;
46471 this.currentTag = 'all';
46473 SelectCommand.prototype.changeTag = function (tag) {
46474 this.currentTag = tag;
46476 SelectCommand.prototype.selectCommand = function (command) {
46477 this.$modalInstance.close(command);
46479 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
46480 return SelectCommand;
46482 controllers.SelectCommand = SelectCommand;
46483 })(controllers = app.controllers || (app.controllers = {}));
46484 })(app || (app = {}));
46488 (function (controllers) {
46489 var Preview = (function () {
46490 function Preview($scope, APIEndPoint, $modalInstance) {
46491 this.APIEndPoint = APIEndPoint;
46492 this.$modalInstance = $modalInstance;
46493 var controller = this;
46494 console.log('preview');
46496 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
46499 controllers.Preview = Preview;
46500 })(controllers = app.controllers || (app.controllers = {}));
46501 })(app || (app = {}));
46503 (function (filters) {
46505 return function (commands, tag) {
46507 angular.forEach(commands, function (command) {
46509 angular.forEach(command.tags, function (value) {
46514 result.push(command);
46520 })(filters || (filters = {}));
46524 var appName = 'zephyr';
46525 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
46526 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
46527 $urlRouterProvider.otherwise('/execution');
46528 $locationProvider.html5Mode({
46533 .state('execution', {
46535 templateUrl: 'templates/execution.html',
46536 controller: 'executionController',
46539 .state('workspace', {
46541 templateUrl: 'templates/workspace.html',
46542 controller: 'workspaceController',
46545 .state('history', {
46547 templateUrl: 'templates/history.html',
46548 controller: 'historyController',
46552 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
46553 app.zephyr.service('MyModal', app.services.MyModal);
46554 app.zephyr.service('WebSocket', app.services.WebSocket);
46555 app.zephyr.filter('Tag', filters.Tag);
46556 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
46557 app.zephyr.controller('previewController', app.controllers.Preview);
46558 app.zephyr.controller('executionController', app.controllers.Execution);
46559 app.zephyr.controller('workspaceController', app.controllers.Workspace);
46560 app.zephyr.controller('historyController', app.controllers.History);
46561 app.zephyr.controller('commandController', app.directives.CommandController);
46562 app.zephyr.controller('optionController', app.directives.OptionController);
46563 app.zephyr.controller('directoryController', app.directives.DirectoryController);
46564 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
46565 app.zephyr.directive('command', app.directives.Command.Factory());
46566 app.zephyr.directive('option', app.directives.Option.Factory());
46567 app.zephyr.directive('directory', app.directives.Directory.Factory());
46568 })(app || (app = {}));
46573 /***/ function(module, exports) {
46578 (function (declares) {
46579 var CommandInfo = (function () {
46580 function CommandInfo(name) {
46583 return CommandInfo;
46585 declares.CommandInfo = CommandInfo;
46586 })(declares = app.declares || (app.declares = {}));
46587 })(app || (app = {}));
46591 (function (services) {
46592 var APIEndPoint = (function () {
46593 function APIEndPoint($resource, $http) {
46594 this.$resource = $resource;
46595 this.$http = $http;
46597 APIEndPoint.prototype.resource = function (endPoint, data) {
46598 var customAction = {
46604 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
46606 return this.$resource(endPoint, {}, { execute: execute });
46608 APIEndPoint.prototype.getOptionControlFile = function (command) {
46609 var endPoint = '/api/v1/optionControlFile/' + command;
46610 return this.resource(endPoint, {}).get();
46612 APIEndPoint.prototype.getFiles = function (fileId) {
46613 var endPoint = '/api/v1/workspace';
46615 endPoint += '/' + fileId;
46617 return this.resource(endPoint, {}).get();
46619 APIEndPoint.prototype.getDirectories = function () {
46620 var endPoint = '/api/v1/all/workspace/directory';
46621 return this.resource(endPoint, {}).get();
46623 APIEndPoint.prototype.getTags = function () {
46624 var endPoint = '/api/v1/tagList';
46625 return this.resource(endPoint, {}).get();
46627 APIEndPoint.prototype.getCommands = function () {
46628 var endPoint = '/api/v1/commandList';
46629 return this.resource(endPoint, {}).get();
46631 APIEndPoint.prototype.execute = function (data) {
46632 var endPoint = '/api/v1/execution';
46633 var fd = new FormData();
46634 fd.append('data', data);
46635 return this.$http.post(endPoint, fd, {
46636 headers: { 'Content-Type': undefined },
46637 transformRequest: angular.identity
46640 APIEndPoint.prototype.debug = function () {
46641 var endPoint = '/api/v1/debug';
46642 return this.$http.get(endPoint);
46644 APIEndPoint.prototype.help = function (command) {
46645 var endPoint = '/api/v1/help/' + command;
46646 return this.$http.get(endPoint);
46648 return APIEndPoint;
46650 services.APIEndPoint = APIEndPoint;
46651 })(services = app.services || (app.services = {}));
46652 })(app || (app = {}));
46656 (function (services) {
46657 var MyModal = (function () {
46658 function MyModal($uibModal) {
46659 this.$uibModal = $uibModal;
46660 this.modalOption = {
46667 MyModal.prototype.open = function (modalName) {
46668 if (modalName === 'SelectCommand') {
46669 this.modalOption.templateUrl = 'templates/select-command.html';
46670 this.modalOption.size = 'lg';
46672 return this.$uibModal.open(this.modalOption);
46674 MyModal.prototype.selectCommand = function () {
46675 this.modalOption.templateUrl = 'templates/select-command.html';
46676 this.modalOption.controller = 'selectCommandController';
46677 this.modalOption.controllerAs = 'c';
46678 this.modalOption.size = 'lg';
46679 return this.$uibModal.open(this.modalOption);
46681 MyModal.prototype.preview = function () {
46682 this.modalOption.templateUrl = 'templates/preview.html';
46683 this.modalOption.controller = 'previewController';
46684 this.modalOption.controllerAs = 'c';
46685 this.modalOption.size = 'lg';
46686 return this.$uibModal.open(this.modalOption);
46688 MyModal.$inject = ['$uibModal'];
46691 services.MyModal = MyModal;
46692 })(services = app.services || (app.services = {}));
46693 })(app || (app = {}));
46697 (function (services) {
46698 var WebSocket = (function () {
46699 function WebSocket($rootScope) {
46700 this.$rootScope = $rootScope;
46701 this.socket = io.connect();
46703 WebSocket.prototype.on = function (eventName, callback) {
46704 var socket = this.socket;
46705 var rootScope = this.$rootScope;
46706 socket.on(eventName, function () {
46707 var args = arguments;
46708 rootScope.$apply(function () {
46709 callback.apply(socket, args);
46713 WebSocket.prototype.emit = function (eventName, data, callback) {
46714 var socket = this.socket;
46715 var rootScope = this.$rootScope;
46716 this.socket.emit(eventName, data, function () {
46717 var args = arguments;
46718 rootScope.$apply(function () {
46720 callback.apply(socket, args);
46726 services.WebSocket = WebSocket;
46727 })(services = app.services || (app.services = {}));
46728 })(app || (app = {}));
46732 (function (directives) {
46733 var Command = (function () {
46734 function Command() {
46735 this.restrict = 'E';
46736 this.replace = true;
46738 this.controller = 'commandController';
46739 this.controllerAs = 'ctrl';
46740 this.bindToController = {
46746 this.templateUrl = 'templates/command.html';
46748 Command.Factory = function () {
46749 var directive = function () {
46750 return new Command();
46752 directive.$inject = [];
46757 directives.Command = Command;
46758 var CommandController = (function () {
46759 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
46760 this.APIEndPoint = APIEndPoint;
46761 this.$scope = $scope;
46762 this.MyModal = MyModal;
46763 this.WebSocket = WebSocket;
46764 var controller = this;
46766 .getOptionControlFile(this.name)
46768 .then(function (result) {
46769 controller.options = result.info;
46774 .then(function (result) {
46775 controller.dirs = result.info;
46777 this.heading = "[" + this.index + "]: dcdFilePrint";
46778 this.isOpen = true;
46779 this.$scope.$on('close', function () {
46780 controller.isOpen = false;
46782 this.WebSocket.on('console', function (msg) {
46783 controller.messages = msg.split('\n');
46786 CommandController.prototype.submit = function () {
46788 angular.forEach(this.options, function (option) {
46790 name: option.option,
46793 angular.forEach(option.arg, function (arg) {
46795 if (typeof arg.input === 'object') {
46796 obj.arguments.push(arg.input.name);
46799 obj.arguments.push(arg.input);
46803 if (obj.arguments.length > 0) {
46808 command: this.name,
46809 workspace: this.workspace.fileId,
46813 .execute(JSON.stringify(execObj))
46814 .then(function (result) {
46815 console.log(result);
46818 CommandController.prototype.removeMySelf = function (index) {
46819 this.remove()(index, this.list);
46821 CommandController.prototype.reloadFiles = function () {
46823 var fileId = this.workspace.fileId;
46827 .then(function (result) {
46828 var status = result.status;
46829 if (status === 'success') {
46830 _this.files = result.info;
46833 console.log(result.message);
46837 CommandController.prototype.debug = function () {
46840 .then(function (result) {
46841 console.log(result);
46844 CommandController.prototype.help = function () {
46847 .then(function (result) {
46848 console.log(result);
46851 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
46852 return CommandController;
46854 directives.CommandController = CommandController;
46855 })(directives = app.directives || (app.directives = {}));
46856 })(app || (app = {}));
46860 (function (directives) {
46861 var HeaderMenu = (function () {
46862 function HeaderMenu() {
46863 this.restrict = 'E';
46864 this.replace = true;
46865 this.templateUrl = 'templates/header-menu.html';
46867 HeaderMenu.Factory = function () {
46868 var directive = function () {
46869 return new HeaderMenu();
46875 directives.HeaderMenu = HeaderMenu;
46876 })(directives = app.directives || (app.directives = {}));
46877 })(app || (app = {}));
46881 (function (directives) {
46882 var Option = (function () {
46883 function Option() {
46884 this.restrict = 'E';
46885 this.replace = true;
46886 this.controller = 'optionController';
46887 this.bindToController = {
46892 this.templateUrl = 'templates/option.html';
46893 this.controllerAs = 'ctrl';
46895 Option.Factory = function () {
46896 var directive = function () {
46897 return new Option();
46899 directive.$inject = [];
46904 directives.Option = Option;
46905 var OptionController = (function () {
46906 function OptionController() {
46907 var controller = this;
46908 angular.forEach(controller.info.arg, function (arg) {
46909 if (arg.initialValue) {
46910 if (arg.formType === 'number') {
46911 arg.input = parseInt(arg.initialValue);
46914 arg.input = arg.initialValue;
46919 OptionController.$inject = [];
46920 return OptionController;
46922 directives.OptionController = OptionController;
46923 })(directives = app.directives || (app.directives = {}));
46924 })(app || (app = {}));
46928 (function (directives) {
46929 var Directory = (function () {
46930 function Directory() {
46931 this.restrict = 'E';
46932 this.replace = true;
46933 this.controller = 'directoryController';
46934 this.controllerAs = 'ctrl';
46935 this.bindToController = {
46941 this.templateUrl = 'templates/directory.html';
46943 Directory.Factory = function () {
46944 var directive = function () {
46945 return new Directory();
46951 directives.Directory = Directory;
46952 var DirectoryController = (function () {
46953 function DirectoryController(APIEndPoint, $scope) {
46954 this.APIEndPoint = APIEndPoint;
46955 this.$scope = $scope;
46956 var controller = this;
46958 .getFiles(this.info.fileId)
46960 .then(function (result) {
46961 if (result.status === 'success') {
46962 controller.files = result.info;
46963 angular.forEach(result.info, function (file) {
46964 if (file.fileType === '0') {
46966 if (controller.info.path === '/') {
46967 o.path = '/' + file.name;
46970 o.path = controller.info.path + '/' + file.name;
46972 controller.add()(o, controller.list);
46979 DirectoryController.$inject = ['APIEndPoint', '$scope'];
46980 return DirectoryController;
46982 directives.DirectoryController = DirectoryController;
46983 })(directives = app.directives || (app.directives = {}));
46984 })(app || (app = {}));
46988 (function (controllers) {
46989 var Execution = (function () {
46990 function Execution(MyModal, $scope) {
46991 this.MyModal = MyModal;
46992 this.$scope = $scope;
46993 this.commandInfoList = [];
46996 Execution.prototype.add = function () {
46997 this.$scope.$broadcast('close');
46998 var commandInfoList = this.commandInfoList;
46999 var commandInstance = this.MyModal.selectCommand();
47002 .then(function (command) {
47003 commandInfoList.push(new app.declares.CommandInfo(command));
47006 Execution.prototype.open = function () {
47007 var result = this.MyModal.open('SelectCommand');
47008 console.log(result);
47010 Execution.prototype.remove = function (index, list) {
47011 list.splice(index, 1);
47013 Execution.prototype.close = function () {
47014 console.log("close");
47016 Execution.$inject = ['MyModal', '$scope'];
47019 controllers.Execution = Execution;
47020 })(controllers = app.controllers || (app.controllers = {}));
47021 })(app || (app = {}));
47025 (function (controllers) {
47026 var Workspace = (function () {
47027 function Workspace($scope, APIEndPoint, MyModal) {
47028 this.$scope = $scope;
47029 this.APIEndPoint = APIEndPoint;
47030 this.MyModal = MyModal;
47031 this.directoryList = [];
47032 var controller = this;
47033 var directoryList = this.directoryList;
47035 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
47043 directoryList.push(o);
47045 Workspace.prototype.addDirectory = function (info, directoryList) {
47046 directoryList.push(info);
47048 Workspace.prototype.debug = function () {
47049 this.MyModal.preview();
47051 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
47054 controllers.Workspace = Workspace;
47055 })(controllers = app.controllers || (app.controllers = {}));
47056 })(app || (app = {}));
47060 (function (controllers) {
47061 var History = (function () {
47062 function History($scope) {
47063 this.page = "History";
47065 History.$inject = ['$scope'];
47068 controllers.History = History;
47069 })(controllers = app.controllers || (app.controllers = {}));
47070 })(app || (app = {}));
47074 (function (controllers) {
47075 var SelectCommand = (function () {
47076 function SelectCommand($scope, APIEndPoint, $modalInstance) {
47077 this.APIEndPoint = APIEndPoint;
47078 this.$modalInstance = $modalInstance;
47079 var controller = this;
47082 .$promise.then(function (result) {
47083 controller.tags = result.info;
47087 .$promise.then(function (result) {
47088 controller.commands = result.info;
47090 this.currentTag = 'all';
47092 SelectCommand.prototype.changeTag = function (tag) {
47093 this.currentTag = tag;
47095 SelectCommand.prototype.selectCommand = function (command) {
47096 this.$modalInstance.close(command);
47098 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
47099 return SelectCommand;
47101 controllers.SelectCommand = SelectCommand;
47102 })(controllers = app.controllers || (app.controllers = {}));
47103 })(app || (app = {}));
47107 (function (controllers) {
47108 var Preview = (function () {
47109 function Preview($scope, APIEndPoint, $modalInstance) {
47110 this.APIEndPoint = APIEndPoint;
47111 this.$modalInstance = $modalInstance;
47112 var controller = this;
47113 console.log('preview');
47115 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
47118 controllers.Preview = Preview;
47119 })(controllers = app.controllers || (app.controllers = {}));
47120 })(app || (app = {}));
47122 (function (filters) {
47124 return function (commands, tag) {
47126 angular.forEach(commands, function (command) {
47128 angular.forEach(command.tags, function (value) {
47133 result.push(command);
47139 })(filters || (filters = {}));
47143 var appName = 'zephyr';
47144 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
47145 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
47146 $urlRouterProvider.otherwise('/execution');
47147 $locationProvider.html5Mode({
47152 .state('execution', {
47154 templateUrl: 'templates/execution.html',
47155 controller: 'executionController',
47158 .state('workspace', {
47160 templateUrl: 'templates/workspace.html',
47161 controller: 'workspaceController',
47164 .state('history', {
47166 templateUrl: 'templates/history.html',
47167 controller: 'historyController',
47171 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
47172 app.zephyr.service('MyModal', app.services.MyModal);
47173 app.zephyr.service('WebSocket', app.services.WebSocket);
47174 app.zephyr.filter('Tag', filters.Tag);
47175 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
47176 app.zephyr.controller('previewController', app.controllers.Preview);
47177 app.zephyr.controller('executionController', app.controllers.Execution);
47178 app.zephyr.controller('workspaceController', app.controllers.Workspace);
47179 app.zephyr.controller('historyController', app.controllers.History);
47180 app.zephyr.controller('commandController', app.directives.CommandController);
47181 app.zephyr.controller('optionController', app.directives.OptionController);
47182 app.zephyr.controller('directoryController', app.directives.DirectoryController);
47183 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
47184 app.zephyr.directive('command', app.directives.Command.Factory());
47185 app.zephyr.directive('option', app.directives.Option.Factory());
47186 app.zephyr.directive('directory', app.directives.Directory.Factory());
47187 })(app || (app = {}));
47192 /***/ function(module, exports) {
47197 (function (declares) {
47198 var CommandInfo = (function () {
47199 function CommandInfo(name) {
47202 return CommandInfo;
47204 declares.CommandInfo = CommandInfo;
47205 })(declares = app.declares || (app.declares = {}));
47206 })(app || (app = {}));
47210 (function (services) {
47211 var APIEndPoint = (function () {
47212 function APIEndPoint($resource, $http) {
47213 this.$resource = $resource;
47214 this.$http = $http;
47216 APIEndPoint.prototype.resource = function (endPoint, data) {
47217 var customAction = {
47223 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
47225 return this.$resource(endPoint, {}, { execute: execute });
47227 APIEndPoint.prototype.getOptionControlFile = function (command) {
47228 var endPoint = '/api/v1/optionControlFile/' + command;
47229 return this.resource(endPoint, {}).get();
47231 APIEndPoint.prototype.getFiles = function (fileId) {
47232 var endPoint = '/api/v1/workspace';
47234 endPoint += '/' + fileId;
47236 return this.resource(endPoint, {}).get();
47238 APIEndPoint.prototype.getDirectories = function () {
47239 var endPoint = '/api/v1/all/workspace/directory';
47240 return this.resource(endPoint, {}).get();
47242 APIEndPoint.prototype.getTags = function () {
47243 var endPoint = '/api/v1/tagList';
47244 return this.resource(endPoint, {}).get();
47246 APIEndPoint.prototype.getCommands = function () {
47247 var endPoint = '/api/v1/commandList';
47248 return this.resource(endPoint, {}).get();
47250 APIEndPoint.prototype.execute = function (data) {
47251 var endPoint = '/api/v1/execution';
47252 var fd = new FormData();
47253 fd.append('data', data);
47254 return this.$http.post(endPoint, fd, {
47255 headers: { 'Content-Type': undefined },
47256 transformRequest: angular.identity
47259 APIEndPoint.prototype.debug = function () {
47260 var endPoint = '/api/v1/debug';
47261 return this.$http.get(endPoint);
47263 APIEndPoint.prototype.help = function (command) {
47264 var endPoint = '/api/v1/help/' + command;
47265 return this.$http.get(endPoint);
47267 return APIEndPoint;
47269 services.APIEndPoint = APIEndPoint;
47270 })(services = app.services || (app.services = {}));
47271 })(app || (app = {}));
47275 (function (services) {
47276 var MyModal = (function () {
47277 function MyModal($uibModal) {
47278 this.$uibModal = $uibModal;
47279 this.modalOption = {
47286 MyModal.prototype.open = function (modalName) {
47287 if (modalName === 'SelectCommand') {
47288 this.modalOption.templateUrl = 'templates/select-command.html';
47289 this.modalOption.size = 'lg';
47291 return this.$uibModal.open(this.modalOption);
47293 MyModal.prototype.selectCommand = function () {
47294 this.modalOption.templateUrl = 'templates/select-command.html';
47295 this.modalOption.controller = 'selectCommandController';
47296 this.modalOption.controllerAs = 'c';
47297 this.modalOption.size = 'lg';
47298 return this.$uibModal.open(this.modalOption);
47300 MyModal.prototype.preview = function () {
47301 this.modalOption.templateUrl = 'templates/preview.html';
47302 this.modalOption.controller = 'previewController';
47303 this.modalOption.controllerAs = 'c';
47304 this.modalOption.size = 'lg';
47305 return this.$uibModal.open(this.modalOption);
47307 MyModal.$inject = ['$uibModal'];
47310 services.MyModal = MyModal;
47311 })(services = app.services || (app.services = {}));
47312 })(app || (app = {}));
47316 (function (services) {
47317 var WebSocket = (function () {
47318 function WebSocket($rootScope) {
47319 this.$rootScope = $rootScope;
47320 this.socket = io.connect();
47322 WebSocket.prototype.on = function (eventName, callback) {
47323 var socket = this.socket;
47324 var rootScope = this.$rootScope;
47325 socket.on(eventName, function () {
47326 var args = arguments;
47327 rootScope.$apply(function () {
47328 callback.apply(socket, args);
47332 WebSocket.prototype.emit = function (eventName, data, callback) {
47333 var socket = this.socket;
47334 var rootScope = this.$rootScope;
47335 this.socket.emit(eventName, data, function () {
47336 var args = arguments;
47337 rootScope.$apply(function () {
47339 callback.apply(socket, args);
47345 services.WebSocket = WebSocket;
47346 })(services = app.services || (app.services = {}));
47347 })(app || (app = {}));
47351 (function (directives) {
47352 var Command = (function () {
47353 function Command() {
47354 this.restrict = 'E';
47355 this.replace = true;
47357 this.controller = 'commandController';
47358 this.controllerAs = 'ctrl';
47359 this.bindToController = {
47365 this.templateUrl = 'templates/command.html';
47367 Command.Factory = function () {
47368 var directive = function () {
47369 return new Command();
47371 directive.$inject = [];
47376 directives.Command = Command;
47377 var CommandController = (function () {
47378 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
47379 this.APIEndPoint = APIEndPoint;
47380 this.$scope = $scope;
47381 this.MyModal = MyModal;
47382 this.WebSocket = WebSocket;
47383 var controller = this;
47385 .getOptionControlFile(this.name)
47387 .then(function (result) {
47388 controller.options = result.info;
47393 .then(function (result) {
47394 controller.dirs = result.info;
47396 this.heading = "[" + this.index + "]: dcdFilePrint";
47397 this.isOpen = true;
47398 this.$scope.$on('close', function () {
47399 controller.isOpen = false;
47401 this.WebSocket.on('console', function (msg) {
47402 controller.messages = msg.split('\n');
47405 CommandController.prototype.submit = function () {
47407 angular.forEach(this.options, function (option) {
47409 name: option.option,
47412 angular.forEach(option.arg, function (arg) {
47414 if (typeof arg.input === 'object') {
47415 obj.arguments.push(arg.input.name);
47418 obj.arguments.push(arg.input);
47422 if (obj.arguments.length > 0) {
47427 command: this.name,
47428 workspace: this.workspace.fileId,
47432 .execute(JSON.stringify(execObj))
47433 .then(function (result) {
47434 console.log(result);
47437 CommandController.prototype.removeMySelf = function (index) {
47438 this.remove()(index, this.list);
47440 CommandController.prototype.reloadFiles = function () {
47442 var fileId = this.workspace.fileId;
47446 .then(function (result) {
47447 var status = result.status;
47448 if (status === 'success') {
47449 _this.files = result.info;
47452 console.log(result.message);
47456 CommandController.prototype.debug = function () {
47459 .then(function (result) {
47460 console.log(result);
47463 CommandController.prototype.help = function () {
47466 .then(function (result) {
47467 console.log(result);
47470 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
47471 return CommandController;
47473 directives.CommandController = CommandController;
47474 })(directives = app.directives || (app.directives = {}));
47475 })(app || (app = {}));
47479 (function (directives) {
47480 var HeaderMenu = (function () {
47481 function HeaderMenu() {
47482 this.restrict = 'E';
47483 this.replace = true;
47484 this.templateUrl = 'templates/header-menu.html';
47486 HeaderMenu.Factory = function () {
47487 var directive = function () {
47488 return new HeaderMenu();
47494 directives.HeaderMenu = HeaderMenu;
47495 })(directives = app.directives || (app.directives = {}));
47496 })(app || (app = {}));
47500 (function (directives) {
47501 var Option = (function () {
47502 function Option() {
47503 this.restrict = 'E';
47504 this.replace = true;
47505 this.controller = 'optionController';
47506 this.bindToController = {
47511 this.templateUrl = 'templates/option.html';
47512 this.controllerAs = 'ctrl';
47514 Option.Factory = function () {
47515 var directive = function () {
47516 return new Option();
47518 directive.$inject = [];
47523 directives.Option = Option;
47524 var OptionController = (function () {
47525 function OptionController() {
47526 var controller = this;
47527 angular.forEach(controller.info.arg, function (arg) {
47528 if (arg.initialValue) {
47529 if (arg.formType === 'number') {
47530 arg.input = parseInt(arg.initialValue);
47533 arg.input = arg.initialValue;
47538 OptionController.$inject = [];
47539 return OptionController;
47541 directives.OptionController = OptionController;
47542 })(directives = app.directives || (app.directives = {}));
47543 })(app || (app = {}));
47547 (function (directives) {
47548 var Directory = (function () {
47549 function Directory() {
47550 this.restrict = 'E';
47551 this.replace = true;
47552 this.controller = 'directoryController';
47553 this.controllerAs = 'ctrl';
47554 this.bindToController = {
47560 this.templateUrl = 'templates/directory.html';
47562 Directory.Factory = function () {
47563 var directive = function () {
47564 return new Directory();
47570 directives.Directory = Directory;
47571 var DirectoryController = (function () {
47572 function DirectoryController(APIEndPoint, $scope) {
47573 this.APIEndPoint = APIEndPoint;
47574 this.$scope = $scope;
47575 var controller = this;
47577 .getFiles(this.info.fileId)
47579 .then(function (result) {
47580 if (result.status === 'success') {
47581 controller.files = result.info;
47582 angular.forEach(result.info, function (file) {
47583 if (file.fileType === '0') {
47585 if (controller.info.path === '/') {
47586 o.path = '/' + file.name;
47589 o.path = controller.info.path + '/' + file.name;
47591 controller.add()(o, controller.list);
47598 DirectoryController.$inject = ['APIEndPoint', '$scope'];
47599 return DirectoryController;
47601 directives.DirectoryController = DirectoryController;
47602 })(directives = app.directives || (app.directives = {}));
47603 })(app || (app = {}));
47607 (function (controllers) {
47608 var Execution = (function () {
47609 function Execution(MyModal, $scope) {
47610 this.MyModal = MyModal;
47611 this.$scope = $scope;
47612 this.commandInfoList = [];
47615 Execution.prototype.add = function () {
47616 this.$scope.$broadcast('close');
47617 var commandInfoList = this.commandInfoList;
47618 var commandInstance = this.MyModal.selectCommand();
47621 .then(function (command) {
47622 commandInfoList.push(new app.declares.CommandInfo(command));
47625 Execution.prototype.open = function () {
47626 var result = this.MyModal.open('SelectCommand');
47627 console.log(result);
47629 Execution.prototype.remove = function (index, list) {
47630 list.splice(index, 1);
47632 Execution.prototype.close = function () {
47633 console.log("close");
47635 Execution.$inject = ['MyModal', '$scope'];
47638 controllers.Execution = Execution;
47639 })(controllers = app.controllers || (app.controllers = {}));
47640 })(app || (app = {}));
47644 (function (controllers) {
47645 var Workspace = (function () {
47646 function Workspace($scope, APIEndPoint, MyModal) {
47647 this.$scope = $scope;
47648 this.APIEndPoint = APIEndPoint;
47649 this.MyModal = MyModal;
47650 this.directoryList = [];
47651 var controller = this;
47652 var directoryList = this.directoryList;
47654 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
47662 directoryList.push(o);
47664 Workspace.prototype.addDirectory = function (info, directoryList) {
47665 directoryList.push(info);
47667 Workspace.prototype.debug = function () {
47668 this.MyModal.preview();
47670 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
47673 controllers.Workspace = Workspace;
47674 })(controllers = app.controllers || (app.controllers = {}));
47675 })(app || (app = {}));
47679 (function (controllers) {
47680 var History = (function () {
47681 function History($scope) {
47682 this.page = "History";
47684 History.$inject = ['$scope'];
47687 controllers.History = History;
47688 })(controllers = app.controllers || (app.controllers = {}));
47689 })(app || (app = {}));
47693 (function (controllers) {
47694 var SelectCommand = (function () {
47695 function SelectCommand($scope, APIEndPoint, $modalInstance) {
47696 this.APIEndPoint = APIEndPoint;
47697 this.$modalInstance = $modalInstance;
47698 var controller = this;
47701 .$promise.then(function (result) {
47702 controller.tags = result.info;
47706 .$promise.then(function (result) {
47707 controller.commands = result.info;
47709 this.currentTag = 'all';
47711 SelectCommand.prototype.changeTag = function (tag) {
47712 this.currentTag = tag;
47714 SelectCommand.prototype.selectCommand = function (command) {
47715 this.$modalInstance.close(command);
47717 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
47718 return SelectCommand;
47720 controllers.SelectCommand = SelectCommand;
47721 })(controllers = app.controllers || (app.controllers = {}));
47722 })(app || (app = {}));
47726 (function (controllers) {
47727 var Preview = (function () {
47728 function Preview($scope, APIEndPoint, $modalInstance) {
47729 this.APIEndPoint = APIEndPoint;
47730 this.$modalInstance = $modalInstance;
47731 var controller = this;
47732 console.log('preview');
47734 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
47737 controllers.Preview = Preview;
47738 })(controllers = app.controllers || (app.controllers = {}));
47739 })(app || (app = {}));
47741 (function (filters) {
47743 return function (commands, tag) {
47745 angular.forEach(commands, function (command) {
47747 angular.forEach(command.tags, function (value) {
47752 result.push(command);
47758 })(filters || (filters = {}));
47762 var appName = 'zephyr';
47763 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
47764 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
47765 $urlRouterProvider.otherwise('/execution');
47766 $locationProvider.html5Mode({
47771 .state('execution', {
47773 templateUrl: 'templates/execution.html',
47774 controller: 'executionController',
47777 .state('workspace', {
47779 templateUrl: 'templates/workspace.html',
47780 controller: 'workspaceController',
47783 .state('history', {
47785 templateUrl: 'templates/history.html',
47786 controller: 'historyController',
47790 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
47791 app.zephyr.service('MyModal', app.services.MyModal);
47792 app.zephyr.service('WebSocket', app.services.WebSocket);
47793 app.zephyr.filter('Tag', filters.Tag);
47794 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
47795 app.zephyr.controller('previewController', app.controllers.Preview);
47796 app.zephyr.controller('executionController', app.controllers.Execution);
47797 app.zephyr.controller('workspaceController', app.controllers.Workspace);
47798 app.zephyr.controller('historyController', app.controllers.History);
47799 app.zephyr.controller('commandController', app.directives.CommandController);
47800 app.zephyr.controller('optionController', app.directives.OptionController);
47801 app.zephyr.controller('directoryController', app.directives.DirectoryController);
47802 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
47803 app.zephyr.directive('command', app.directives.Command.Factory());
47804 app.zephyr.directive('option', app.directives.Option.Factory());
47805 app.zephyr.directive('directory', app.directives.Directory.Factory());
47806 })(app || (app = {}));
47811 /***/ function(module, exports) {
47816 (function (declares) {
47817 var CommandInfo = (function () {
47818 function CommandInfo(name) {
47821 return CommandInfo;
47823 declares.CommandInfo = CommandInfo;
47824 })(declares = app.declares || (app.declares = {}));
47825 })(app || (app = {}));
47829 (function (services) {
47830 var APIEndPoint = (function () {
47831 function APIEndPoint($resource, $http) {
47832 this.$resource = $resource;
47833 this.$http = $http;
47835 APIEndPoint.prototype.resource = function (endPoint, data) {
47836 var customAction = {
47842 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
47844 return this.$resource(endPoint, {}, { execute: execute });
47846 APIEndPoint.prototype.getOptionControlFile = function (command) {
47847 var endPoint = '/api/v1/optionControlFile/' + command;
47848 return this.resource(endPoint, {}).get();
47850 APIEndPoint.prototype.getFiles = function (fileId) {
47851 var endPoint = '/api/v1/workspace';
47853 endPoint += '/' + fileId;
47855 return this.resource(endPoint, {}).get();
47857 APIEndPoint.prototype.getDirectories = function () {
47858 var endPoint = '/api/v1/all/workspace/directory';
47859 return this.resource(endPoint, {}).get();
47861 APIEndPoint.prototype.getTags = function () {
47862 var endPoint = '/api/v1/tagList';
47863 return this.resource(endPoint, {}).get();
47865 APIEndPoint.prototype.getCommands = function () {
47866 var endPoint = '/api/v1/commandList';
47867 return this.resource(endPoint, {}).get();
47869 APIEndPoint.prototype.execute = function (data) {
47870 var endPoint = '/api/v1/execution';
47871 var fd = new FormData();
47872 fd.append('data', data);
47873 return this.$http.post(endPoint, fd, {
47874 headers: { 'Content-Type': undefined },
47875 transformRequest: angular.identity
47878 APIEndPoint.prototype.debug = function () {
47879 var endPoint = '/api/v1/debug';
47880 return this.$http.get(endPoint);
47882 APIEndPoint.prototype.help = function (command) {
47883 var endPoint = '/api/v1/help/' + command;
47884 return this.$http.get(endPoint);
47886 return APIEndPoint;
47888 services.APIEndPoint = APIEndPoint;
47889 })(services = app.services || (app.services = {}));
47890 })(app || (app = {}));
47894 (function (services) {
47895 var MyModal = (function () {
47896 function MyModal($uibModal) {
47897 this.$uibModal = $uibModal;
47898 this.modalOption = {
47905 MyModal.prototype.open = function (modalName) {
47906 if (modalName === 'SelectCommand') {
47907 this.modalOption.templateUrl = 'templates/select-command.html';
47908 this.modalOption.size = 'lg';
47910 return this.$uibModal.open(this.modalOption);
47912 MyModal.prototype.selectCommand = function () {
47913 this.modalOption.templateUrl = 'templates/select-command.html';
47914 this.modalOption.controller = 'selectCommandController';
47915 this.modalOption.controllerAs = 'c';
47916 this.modalOption.size = 'lg';
47917 return this.$uibModal.open(this.modalOption);
47919 MyModal.prototype.preview = function () {
47920 this.modalOption.templateUrl = 'templates/preview.html';
47921 this.modalOption.controller = 'previewController';
47922 this.modalOption.controllerAs = 'c';
47923 this.modalOption.size = 'lg';
47924 return this.$uibModal.open(this.modalOption);
47926 MyModal.$inject = ['$uibModal'];
47929 services.MyModal = MyModal;
47930 })(services = app.services || (app.services = {}));
47931 })(app || (app = {}));
47935 (function (services) {
47936 var WebSocket = (function () {
47937 function WebSocket($rootScope) {
47938 this.$rootScope = $rootScope;
47939 this.socket = io.connect();
47941 WebSocket.prototype.on = function (eventName, callback) {
47942 var socket = this.socket;
47943 var rootScope = this.$rootScope;
47944 socket.on(eventName, function () {
47945 var args = arguments;
47946 rootScope.$apply(function () {
47947 callback.apply(socket, args);
47951 WebSocket.prototype.emit = function (eventName, data, callback) {
47952 var socket = this.socket;
47953 var rootScope = this.$rootScope;
47954 this.socket.emit(eventName, data, function () {
47955 var args = arguments;
47956 rootScope.$apply(function () {
47958 callback.apply(socket, args);
47964 services.WebSocket = WebSocket;
47965 })(services = app.services || (app.services = {}));
47966 })(app || (app = {}));
47970 (function (directives) {
47971 var Command = (function () {
47972 function Command() {
47973 this.restrict = 'E';
47974 this.replace = true;
47976 this.controller = 'commandController';
47977 this.controllerAs = 'ctrl';
47978 this.bindToController = {
47984 this.templateUrl = 'templates/command.html';
47986 Command.Factory = function () {
47987 var directive = function () {
47988 return new Command();
47990 directive.$inject = [];
47995 directives.Command = Command;
47996 var CommandController = (function () {
47997 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
47998 this.APIEndPoint = APIEndPoint;
47999 this.$scope = $scope;
48000 this.MyModal = MyModal;
48001 this.WebSocket = WebSocket;
48002 var controller = this;
48004 .getOptionControlFile(this.name)
48006 .then(function (result) {
48007 controller.options = result.info;
48012 .then(function (result) {
48013 controller.dirs = result.info;
48015 this.heading = "[" + this.index + "]: dcdFilePrint";
48016 this.isOpen = true;
48017 this.$scope.$on('close', function () {
48018 controller.isOpen = false;
48020 this.WebSocket.on('console', function (msg) {
48021 controller.messages = msg.split('\n');
48024 CommandController.prototype.submit = function () {
48026 angular.forEach(this.options, function (option) {
48028 name: option.option,
48031 angular.forEach(option.arg, function (arg) {
48033 if (typeof arg.input === 'object') {
48034 obj.arguments.push(arg.input.name);
48037 obj.arguments.push(arg.input);
48041 if (obj.arguments.length > 0) {
48046 command: this.name,
48047 workspace: this.workspace.fileId,
48051 .execute(JSON.stringify(execObj))
48052 .then(function (result) {
48053 console.log(result);
48056 CommandController.prototype.removeMySelf = function (index) {
48057 this.remove()(index, this.list);
48059 CommandController.prototype.reloadFiles = function () {
48061 var fileId = this.workspace.fileId;
48065 .then(function (result) {
48066 var status = result.status;
48067 if (status === 'success') {
48068 _this.files = result.info;
48071 console.log(result.message);
48075 CommandController.prototype.debug = function () {
48078 .then(function (result) {
48079 console.log(result);
48082 CommandController.prototype.help = function () {
48085 .then(function (result) {
48086 console.log(result);
48089 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
48090 return CommandController;
48092 directives.CommandController = CommandController;
48093 })(directives = app.directives || (app.directives = {}));
48094 })(app || (app = {}));
48098 (function (directives) {
48099 var HeaderMenu = (function () {
48100 function HeaderMenu() {
48101 this.restrict = 'E';
48102 this.replace = true;
48103 this.templateUrl = 'templates/header-menu.html';
48105 HeaderMenu.Factory = function () {
48106 var directive = function () {
48107 return new HeaderMenu();
48113 directives.HeaderMenu = HeaderMenu;
48114 })(directives = app.directives || (app.directives = {}));
48115 })(app || (app = {}));
48119 (function (directives) {
48120 var Option = (function () {
48121 function Option() {
48122 this.restrict = 'E';
48123 this.replace = true;
48124 this.controller = 'optionController';
48125 this.bindToController = {
48130 this.templateUrl = 'templates/option.html';
48131 this.controllerAs = 'ctrl';
48133 Option.Factory = function () {
48134 var directive = function () {
48135 return new Option();
48137 directive.$inject = [];
48142 directives.Option = Option;
48143 var OptionController = (function () {
48144 function OptionController() {
48145 var controller = this;
48146 angular.forEach(controller.info.arg, function (arg) {
48147 if (arg.initialValue) {
48148 if (arg.formType === 'number') {
48149 arg.input = parseInt(arg.initialValue);
48152 arg.input = arg.initialValue;
48157 OptionController.$inject = [];
48158 return OptionController;
48160 directives.OptionController = OptionController;
48161 })(directives = app.directives || (app.directives = {}));
48162 })(app || (app = {}));
48166 (function (directives) {
48167 var Directory = (function () {
48168 function Directory() {
48169 this.restrict = 'E';
48170 this.replace = true;
48171 this.controller = 'directoryController';
48172 this.controllerAs = 'ctrl';
48173 this.bindToController = {
48179 this.templateUrl = 'templates/directory.html';
48181 Directory.Factory = function () {
48182 var directive = function () {
48183 return new Directory();
48189 directives.Directory = Directory;
48190 var DirectoryController = (function () {
48191 function DirectoryController(APIEndPoint, $scope) {
48192 this.APIEndPoint = APIEndPoint;
48193 this.$scope = $scope;
48194 var controller = this;
48196 .getFiles(this.info.fileId)
48198 .then(function (result) {
48199 if (result.status === 'success') {
48200 controller.files = result.info;
48201 angular.forEach(result.info, function (file) {
48202 if (file.fileType === '0') {
48204 if (controller.info.path === '/') {
48205 o.path = '/' + file.name;
48208 o.path = controller.info.path + '/' + file.name;
48210 controller.add()(o, controller.list);
48217 DirectoryController.$inject = ['APIEndPoint', '$scope'];
48218 return DirectoryController;
48220 directives.DirectoryController = DirectoryController;
48221 })(directives = app.directives || (app.directives = {}));
48222 })(app || (app = {}));
48226 (function (controllers) {
48227 var Execution = (function () {
48228 function Execution(MyModal, $scope) {
48229 this.MyModal = MyModal;
48230 this.$scope = $scope;
48231 this.commandInfoList = [];
48234 Execution.prototype.add = function () {
48235 this.$scope.$broadcast('close');
48236 var commandInfoList = this.commandInfoList;
48237 var commandInstance = this.MyModal.selectCommand();
48240 .then(function (command) {
48241 commandInfoList.push(new app.declares.CommandInfo(command));
48244 Execution.prototype.open = function () {
48245 var result = this.MyModal.open('SelectCommand');
48246 console.log(result);
48248 Execution.prototype.remove = function (index, list) {
48249 list.splice(index, 1);
48251 Execution.prototype.close = function () {
48252 console.log("close");
48254 Execution.$inject = ['MyModal', '$scope'];
48257 controllers.Execution = Execution;
48258 })(controllers = app.controllers || (app.controllers = {}));
48259 })(app || (app = {}));
48263 (function (controllers) {
48264 var Workspace = (function () {
48265 function Workspace($scope, APIEndPoint, MyModal) {
48266 this.$scope = $scope;
48267 this.APIEndPoint = APIEndPoint;
48268 this.MyModal = MyModal;
48269 this.directoryList = [];
48270 var controller = this;
48271 var directoryList = this.directoryList;
48273 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
48281 directoryList.push(o);
48283 Workspace.prototype.addDirectory = function (info, directoryList) {
48284 directoryList.push(info);
48286 Workspace.prototype.debug = function () {
48287 this.MyModal.preview();
48289 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
48292 controllers.Workspace = Workspace;
48293 })(controllers = app.controllers || (app.controllers = {}));
48294 })(app || (app = {}));
48298 (function (controllers) {
48299 var History = (function () {
48300 function History($scope) {
48301 this.page = "History";
48303 History.$inject = ['$scope'];
48306 controllers.History = History;
48307 })(controllers = app.controllers || (app.controllers = {}));
48308 })(app || (app = {}));
48312 (function (controllers) {
48313 var SelectCommand = (function () {
48314 function SelectCommand($scope, APIEndPoint, $modalInstance) {
48315 this.APIEndPoint = APIEndPoint;
48316 this.$modalInstance = $modalInstance;
48317 var controller = this;
48320 .$promise.then(function (result) {
48321 controller.tags = result.info;
48325 .$promise.then(function (result) {
48326 controller.commands = result.info;
48328 this.currentTag = 'all';
48330 SelectCommand.prototype.changeTag = function (tag) {
48331 this.currentTag = tag;
48333 SelectCommand.prototype.selectCommand = function (command) {
48334 this.$modalInstance.close(command);
48336 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
48337 return SelectCommand;
48339 controllers.SelectCommand = SelectCommand;
48340 })(controllers = app.controllers || (app.controllers = {}));
48341 })(app || (app = {}));
48345 (function (controllers) {
48346 var Preview = (function () {
48347 function Preview($scope, APIEndPoint, $modalInstance) {
48348 this.APIEndPoint = APIEndPoint;
48349 this.$modalInstance = $modalInstance;
48350 var controller = this;
48351 console.log('preview');
48353 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
48356 controllers.Preview = Preview;
48357 })(controllers = app.controllers || (app.controllers = {}));
48358 })(app || (app = {}));
48360 (function (filters) {
48362 return function (commands, tag) {
48364 angular.forEach(commands, function (command) {
48366 angular.forEach(command.tags, function (value) {
48371 result.push(command);
48377 })(filters || (filters = {}));
48381 var appName = 'zephyr';
48382 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
48383 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
48384 $urlRouterProvider.otherwise('/execution');
48385 $locationProvider.html5Mode({
48390 .state('execution', {
48392 templateUrl: 'templates/execution.html',
48393 controller: 'executionController',
48396 .state('workspace', {
48398 templateUrl: 'templates/workspace.html',
48399 controller: 'workspaceController',
48402 .state('history', {
48404 templateUrl: 'templates/history.html',
48405 controller: 'historyController',
48409 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
48410 app.zephyr.service('MyModal', app.services.MyModal);
48411 app.zephyr.service('WebSocket', app.services.WebSocket);
48412 app.zephyr.filter('Tag', filters.Tag);
48413 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
48414 app.zephyr.controller('previewController', app.controllers.Preview);
48415 app.zephyr.controller('executionController', app.controllers.Execution);
48416 app.zephyr.controller('workspaceController', app.controllers.Workspace);
48417 app.zephyr.controller('historyController', app.controllers.History);
48418 app.zephyr.controller('commandController', app.directives.CommandController);
48419 app.zephyr.controller('optionController', app.directives.OptionController);
48420 app.zephyr.controller('directoryController', app.directives.DirectoryController);
48421 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
48422 app.zephyr.directive('command', app.directives.Command.Factory());
48423 app.zephyr.directive('option', app.directives.Option.Factory());
48424 app.zephyr.directive('directory', app.directives.Directory.Factory());
48425 })(app || (app = {}));
48430 /***/ function(module, exports) {
48435 (function (declares) {
48436 var CommandInfo = (function () {
48437 function CommandInfo(name) {
48440 return CommandInfo;
48442 declares.CommandInfo = CommandInfo;
48443 })(declares = app.declares || (app.declares = {}));
48444 })(app || (app = {}));
48448 (function (services) {
48449 var APIEndPoint = (function () {
48450 function APIEndPoint($resource, $http) {
48451 this.$resource = $resource;
48452 this.$http = $http;
48454 APIEndPoint.prototype.resource = function (endPoint, data) {
48455 var customAction = {
48461 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
48463 return this.$resource(endPoint, {}, { execute: execute });
48465 APIEndPoint.prototype.getOptionControlFile = function (command) {
48466 var endPoint = '/api/v1/optionControlFile/' + command;
48467 return this.resource(endPoint, {}).get();
48469 APIEndPoint.prototype.getFiles = function (fileId) {
48470 var endPoint = '/api/v1/workspace';
48472 endPoint += '/' + fileId;
48474 return this.resource(endPoint, {}).get();
48476 APIEndPoint.prototype.getDirectories = function () {
48477 var endPoint = '/api/v1/all/workspace/directory';
48478 return this.resource(endPoint, {}).get();
48480 APIEndPoint.prototype.getTags = function () {
48481 var endPoint = '/api/v1/tagList';
48482 return this.resource(endPoint, {}).get();
48484 APIEndPoint.prototype.getCommands = function () {
48485 var endPoint = '/api/v1/commandList';
48486 return this.resource(endPoint, {}).get();
48488 APIEndPoint.prototype.execute = function (data) {
48489 var endPoint = '/api/v1/execution';
48490 var fd = new FormData();
48491 fd.append('data', data);
48492 return this.$http.post(endPoint, fd, {
48493 headers: { 'Content-Type': undefined },
48494 transformRequest: angular.identity
48497 APIEndPoint.prototype.debug = function () {
48498 var endPoint = '/api/v1/debug';
48499 return this.$http.get(endPoint);
48501 APIEndPoint.prototype.help = function (command) {
48502 var endPoint = '/api/v1/help/' + command;
48503 return this.$http.get(endPoint);
48505 return APIEndPoint;
48507 services.APIEndPoint = APIEndPoint;
48508 })(services = app.services || (app.services = {}));
48509 })(app || (app = {}));
48513 (function (services) {
48514 var MyModal = (function () {
48515 function MyModal($uibModal) {
48516 this.$uibModal = $uibModal;
48517 this.modalOption = {
48524 MyModal.prototype.open = function (modalName) {
48525 if (modalName === 'SelectCommand') {
48526 this.modalOption.templateUrl = 'templates/select-command.html';
48527 this.modalOption.size = 'lg';
48529 return this.$uibModal.open(this.modalOption);
48531 MyModal.prototype.selectCommand = function () {
48532 this.modalOption.templateUrl = 'templates/select-command.html';
48533 this.modalOption.controller = 'selectCommandController';
48534 this.modalOption.controllerAs = 'c';
48535 this.modalOption.size = 'lg';
48536 return this.$uibModal.open(this.modalOption);
48538 MyModal.prototype.preview = function () {
48539 this.modalOption.templateUrl = 'templates/preview.html';
48540 this.modalOption.controller = 'previewController';
48541 this.modalOption.controllerAs = 'c';
48542 this.modalOption.size = 'lg';
48543 return this.$uibModal.open(this.modalOption);
48545 MyModal.$inject = ['$uibModal'];
48548 services.MyModal = MyModal;
48549 })(services = app.services || (app.services = {}));
48550 })(app || (app = {}));
48554 (function (services) {
48555 var WebSocket = (function () {
48556 function WebSocket($rootScope) {
48557 this.$rootScope = $rootScope;
48558 this.socket = io.connect();
48560 WebSocket.prototype.on = function (eventName, callback) {
48561 var socket = this.socket;
48562 var rootScope = this.$rootScope;
48563 socket.on(eventName, function () {
48564 var args = arguments;
48565 rootScope.$apply(function () {
48566 callback.apply(socket, args);
48570 WebSocket.prototype.emit = function (eventName, data, callback) {
48571 var socket = this.socket;
48572 var rootScope = this.$rootScope;
48573 this.socket.emit(eventName, data, function () {
48574 var args = arguments;
48575 rootScope.$apply(function () {
48577 callback.apply(socket, args);
48583 services.WebSocket = WebSocket;
48584 })(services = app.services || (app.services = {}));
48585 })(app || (app = {}));
48589 (function (directives) {
48590 var Command = (function () {
48591 function Command() {
48592 this.restrict = 'E';
48593 this.replace = true;
48595 this.controller = 'commandController';
48596 this.controllerAs = 'ctrl';
48597 this.bindToController = {
48603 this.templateUrl = 'templates/command.html';
48605 Command.Factory = function () {
48606 var directive = function () {
48607 return new Command();
48609 directive.$inject = [];
48614 directives.Command = Command;
48615 var CommandController = (function () {
48616 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
48617 this.APIEndPoint = APIEndPoint;
48618 this.$scope = $scope;
48619 this.MyModal = MyModal;
48620 this.WebSocket = WebSocket;
48621 var controller = this;
48623 .getOptionControlFile(this.name)
48625 .then(function (result) {
48626 controller.options = result.info;
48631 .then(function (result) {
48632 controller.dirs = result.info;
48634 this.heading = "[" + this.index + "]: dcdFilePrint";
48635 this.isOpen = true;
48636 this.$scope.$on('close', function () {
48637 controller.isOpen = false;
48639 this.WebSocket.on('console', function (msg) {
48640 controller.messages = msg.split('\n');
48643 CommandController.prototype.submit = function () {
48645 angular.forEach(this.options, function (option) {
48647 name: option.option,
48650 angular.forEach(option.arg, function (arg) {
48652 if (typeof arg.input === 'object') {
48653 obj.arguments.push(arg.input.name);
48656 obj.arguments.push(arg.input);
48660 if (obj.arguments.length > 0) {
48665 command: this.name,
48666 workspace: this.workspace.fileId,
48670 .execute(JSON.stringify(execObj))
48671 .then(function (result) {
48672 console.log(result);
48675 CommandController.prototype.removeMySelf = function (index) {
48676 this.remove()(index, this.list);
48678 CommandController.prototype.reloadFiles = function () {
48680 var fileId = this.workspace.fileId;
48684 .then(function (result) {
48685 var status = result.status;
48686 if (status === 'success') {
48687 _this.files = result.info;
48690 console.log(result.message);
48694 CommandController.prototype.debug = function () {
48697 .then(function (result) {
48698 console.log(result);
48701 CommandController.prototype.help = function () {
48704 .then(function (result) {
48705 console.log(result);
48708 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
48709 return CommandController;
48711 directives.CommandController = CommandController;
48712 })(directives = app.directives || (app.directives = {}));
48713 })(app || (app = {}));
48717 (function (directives) {
48718 var HeaderMenu = (function () {
48719 function HeaderMenu() {
48720 this.restrict = 'E';
48721 this.replace = true;
48722 this.templateUrl = 'templates/header-menu.html';
48724 HeaderMenu.Factory = function () {
48725 var directive = function () {
48726 return new HeaderMenu();
48732 directives.HeaderMenu = HeaderMenu;
48733 })(directives = app.directives || (app.directives = {}));
48734 })(app || (app = {}));
48738 (function (directives) {
48739 var Option = (function () {
48740 function Option() {
48741 this.restrict = 'E';
48742 this.replace = true;
48743 this.controller = 'optionController';
48744 this.bindToController = {
48749 this.templateUrl = 'templates/option.html';
48750 this.controllerAs = 'ctrl';
48752 Option.Factory = function () {
48753 var directive = function () {
48754 return new Option();
48756 directive.$inject = [];
48761 directives.Option = Option;
48762 var OptionController = (function () {
48763 function OptionController() {
48764 var controller = this;
48765 angular.forEach(controller.info.arg, function (arg) {
48766 if (arg.initialValue) {
48767 if (arg.formType === 'number') {
48768 arg.input = parseInt(arg.initialValue);
48771 arg.input = arg.initialValue;
48776 OptionController.$inject = [];
48777 return OptionController;
48779 directives.OptionController = OptionController;
48780 })(directives = app.directives || (app.directives = {}));
48781 })(app || (app = {}));
48785 (function (directives) {
48786 var Directory = (function () {
48787 function Directory() {
48788 this.restrict = 'E';
48789 this.replace = true;
48790 this.controller = 'directoryController';
48791 this.controllerAs = 'ctrl';
48792 this.bindToController = {
48798 this.templateUrl = 'templates/directory.html';
48800 Directory.Factory = function () {
48801 var directive = function () {
48802 return new Directory();
48808 directives.Directory = Directory;
48809 var DirectoryController = (function () {
48810 function DirectoryController(APIEndPoint, $scope) {
48811 this.APIEndPoint = APIEndPoint;
48812 this.$scope = $scope;
48813 var controller = this;
48815 .getFiles(this.info.fileId)
48817 .then(function (result) {
48818 if (result.status === 'success') {
48819 controller.files = result.info;
48820 angular.forEach(result.info, function (file) {
48821 if (file.fileType === '0') {
48823 if (controller.info.path === '/') {
48824 o.path = '/' + file.name;
48827 o.path = controller.info.path + '/' + file.name;
48829 controller.add()(o, controller.list);
48836 DirectoryController.$inject = ['APIEndPoint', '$scope'];
48837 return DirectoryController;
48839 directives.DirectoryController = DirectoryController;
48840 })(directives = app.directives || (app.directives = {}));
48841 })(app || (app = {}));
48845 (function (controllers) {
48846 var Execution = (function () {
48847 function Execution(MyModal, $scope) {
48848 this.MyModal = MyModal;
48849 this.$scope = $scope;
48850 this.commandInfoList = [];
48853 Execution.prototype.add = function () {
48854 this.$scope.$broadcast('close');
48855 var commandInfoList = this.commandInfoList;
48856 var commandInstance = this.MyModal.selectCommand();
48859 .then(function (command) {
48860 commandInfoList.push(new app.declares.CommandInfo(command));
48863 Execution.prototype.open = function () {
48864 var result = this.MyModal.open('SelectCommand');
48865 console.log(result);
48867 Execution.prototype.remove = function (index, list) {
48868 list.splice(index, 1);
48870 Execution.prototype.close = function () {
48871 console.log("close");
48873 Execution.$inject = ['MyModal', '$scope'];
48876 controllers.Execution = Execution;
48877 })(controllers = app.controllers || (app.controllers = {}));
48878 })(app || (app = {}));
48882 (function (controllers) {
48883 var Workspace = (function () {
48884 function Workspace($scope, APIEndPoint, MyModal) {
48885 this.$scope = $scope;
48886 this.APIEndPoint = APIEndPoint;
48887 this.MyModal = MyModal;
48888 this.directoryList = [];
48889 var controller = this;
48890 var directoryList = this.directoryList;
48892 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
48900 directoryList.push(o);
48902 Workspace.prototype.addDirectory = function (info, directoryList) {
48903 directoryList.push(info);
48905 Workspace.prototype.debug = function () {
48906 this.MyModal.preview();
48908 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
48911 controllers.Workspace = Workspace;
48912 })(controllers = app.controllers || (app.controllers = {}));
48913 })(app || (app = {}));
48917 (function (controllers) {
48918 var History = (function () {
48919 function History($scope) {
48920 this.page = "History";
48922 History.$inject = ['$scope'];
48925 controllers.History = History;
48926 })(controllers = app.controllers || (app.controllers = {}));
48927 })(app || (app = {}));
48931 (function (controllers) {
48932 var SelectCommand = (function () {
48933 function SelectCommand($scope, APIEndPoint, $modalInstance) {
48934 this.APIEndPoint = APIEndPoint;
48935 this.$modalInstance = $modalInstance;
48936 var controller = this;
48939 .$promise.then(function (result) {
48940 controller.tags = result.info;
48944 .$promise.then(function (result) {
48945 controller.commands = result.info;
48947 this.currentTag = 'all';
48949 SelectCommand.prototype.changeTag = function (tag) {
48950 this.currentTag = tag;
48952 SelectCommand.prototype.selectCommand = function (command) {
48953 this.$modalInstance.close(command);
48955 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
48956 return SelectCommand;
48958 controllers.SelectCommand = SelectCommand;
48959 })(controllers = app.controllers || (app.controllers = {}));
48960 })(app || (app = {}));
48964 (function (controllers) {
48965 var Preview = (function () {
48966 function Preview($scope, APIEndPoint, $modalInstance) {
48967 this.APIEndPoint = APIEndPoint;
48968 this.$modalInstance = $modalInstance;
48969 var controller = this;
48970 console.log('preview');
48972 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
48975 controllers.Preview = Preview;
48976 })(controllers = app.controllers || (app.controllers = {}));
48977 })(app || (app = {}));
48979 (function (filters) {
48981 return function (commands, tag) {
48983 angular.forEach(commands, function (command) {
48985 angular.forEach(command.tags, function (value) {
48990 result.push(command);
48996 })(filters || (filters = {}));
49000 var appName = 'zephyr';
49001 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
49002 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
49003 $urlRouterProvider.otherwise('/execution');
49004 $locationProvider.html5Mode({
49009 .state('execution', {
49011 templateUrl: 'templates/execution.html',
49012 controller: 'executionController',
49015 .state('workspace', {
49017 templateUrl: 'templates/workspace.html',
49018 controller: 'workspaceController',
49021 .state('history', {
49023 templateUrl: 'templates/history.html',
49024 controller: 'historyController',
49028 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
49029 app.zephyr.service('MyModal', app.services.MyModal);
49030 app.zephyr.service('WebSocket', app.services.WebSocket);
49031 app.zephyr.filter('Tag', filters.Tag);
49032 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
49033 app.zephyr.controller('previewController', app.controllers.Preview);
49034 app.zephyr.controller('executionController', app.controllers.Execution);
49035 app.zephyr.controller('workspaceController', app.controllers.Workspace);
49036 app.zephyr.controller('historyController', app.controllers.History);
49037 app.zephyr.controller('commandController', app.directives.CommandController);
49038 app.zephyr.controller('optionController', app.directives.OptionController);
49039 app.zephyr.controller('directoryController', app.directives.DirectoryController);
49040 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
49041 app.zephyr.directive('command', app.directives.Command.Factory());
49042 app.zephyr.directive('option', app.directives.Option.Factory());
49043 app.zephyr.directive('directory', app.directives.Directory.Factory());
49044 })(app || (app = {}));
49049 /***/ function(module, exports) {
49054 (function (declares) {
49055 var CommandInfo = (function () {
49056 function CommandInfo(name) {
49059 return CommandInfo;
49061 declares.CommandInfo = CommandInfo;
49062 })(declares = app.declares || (app.declares = {}));
49063 })(app || (app = {}));
49067 (function (services) {
49068 var APIEndPoint = (function () {
49069 function APIEndPoint($resource, $http) {
49070 this.$resource = $resource;
49071 this.$http = $http;
49073 APIEndPoint.prototype.resource = function (endPoint, data) {
49074 var customAction = {
49080 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
49082 return this.$resource(endPoint, {}, { execute: execute });
49084 APIEndPoint.prototype.getOptionControlFile = function (command) {
49085 var endPoint = '/api/v1/optionControlFile/' + command;
49086 return this.resource(endPoint, {}).get();
49088 APIEndPoint.prototype.getFiles = function (fileId) {
49089 var endPoint = '/api/v1/workspace';
49091 endPoint += '/' + fileId;
49093 return this.resource(endPoint, {}).get();
49095 APIEndPoint.prototype.getDirectories = function () {
49096 var endPoint = '/api/v1/all/workspace/directory';
49097 return this.resource(endPoint, {}).get();
49099 APIEndPoint.prototype.getTags = function () {
49100 var endPoint = '/api/v1/tagList';
49101 return this.resource(endPoint, {}).get();
49103 APIEndPoint.prototype.getCommands = function () {
49104 var endPoint = '/api/v1/commandList';
49105 return this.resource(endPoint, {}).get();
49107 APIEndPoint.prototype.execute = function (data) {
49108 var endPoint = '/api/v1/execution';
49109 var fd = new FormData();
49110 fd.append('data', data);
49111 return this.$http.post(endPoint, fd, {
49112 headers: { 'Content-Type': undefined },
49113 transformRequest: angular.identity
49116 APIEndPoint.prototype.debug = function () {
49117 var endPoint = '/api/v1/debug';
49118 return this.$http.get(endPoint);
49120 APIEndPoint.prototype.help = function (command) {
49121 var endPoint = '/api/v1/help/' + command;
49122 return this.$http.get(endPoint);
49124 return APIEndPoint;
49126 services.APIEndPoint = APIEndPoint;
49127 })(services = app.services || (app.services = {}));
49128 })(app || (app = {}));
49132 (function (services) {
49133 var MyModal = (function () {
49134 function MyModal($uibModal) {
49135 this.$uibModal = $uibModal;
49136 this.modalOption = {
49143 MyModal.prototype.open = function (modalName) {
49144 if (modalName === 'SelectCommand') {
49145 this.modalOption.templateUrl = 'templates/select-command.html';
49146 this.modalOption.size = 'lg';
49148 return this.$uibModal.open(this.modalOption);
49150 MyModal.prototype.selectCommand = function () {
49151 this.modalOption.templateUrl = 'templates/select-command.html';
49152 this.modalOption.controller = 'selectCommandController';
49153 this.modalOption.controllerAs = 'c';
49154 this.modalOption.size = 'lg';
49155 return this.$uibModal.open(this.modalOption);
49157 MyModal.prototype.preview = function () {
49158 this.modalOption.templateUrl = 'templates/preview.html';
49159 this.modalOption.controller = 'previewController';
49160 this.modalOption.controllerAs = 'c';
49161 this.modalOption.size = 'lg';
49162 return this.$uibModal.open(this.modalOption);
49164 MyModal.$inject = ['$uibModal'];
49167 services.MyModal = MyModal;
49168 })(services = app.services || (app.services = {}));
49169 })(app || (app = {}));
49173 (function (services) {
49174 var WebSocket = (function () {
49175 function WebSocket($rootScope) {
49176 this.$rootScope = $rootScope;
49177 this.socket = io.connect();
49179 WebSocket.prototype.on = function (eventName, callback) {
49180 var socket = this.socket;
49181 var rootScope = this.$rootScope;
49182 socket.on(eventName, function () {
49183 var args = arguments;
49184 rootScope.$apply(function () {
49185 callback.apply(socket, args);
49189 WebSocket.prototype.emit = function (eventName, data, callback) {
49190 var socket = this.socket;
49191 var rootScope = this.$rootScope;
49192 this.socket.emit(eventName, data, function () {
49193 var args = arguments;
49194 rootScope.$apply(function () {
49196 callback.apply(socket, args);
49202 services.WebSocket = WebSocket;
49203 })(services = app.services || (app.services = {}));
49204 })(app || (app = {}));
49208 (function (directives) {
49209 var Command = (function () {
49210 function Command() {
49211 this.restrict = 'E';
49212 this.replace = true;
49214 this.controller = 'commandController';
49215 this.controllerAs = 'ctrl';
49216 this.bindToController = {
49222 this.templateUrl = 'templates/command.html';
49224 Command.Factory = function () {
49225 var directive = function () {
49226 return new Command();
49228 directive.$inject = [];
49233 directives.Command = Command;
49234 var CommandController = (function () {
49235 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
49236 this.APIEndPoint = APIEndPoint;
49237 this.$scope = $scope;
49238 this.MyModal = MyModal;
49239 this.WebSocket = WebSocket;
49240 var controller = this;
49242 .getOptionControlFile(this.name)
49244 .then(function (result) {
49245 controller.options = result.info;
49250 .then(function (result) {
49251 controller.dirs = result.info;
49253 this.heading = "[" + this.index + "]: dcdFilePrint";
49254 this.isOpen = true;
49255 this.$scope.$on('close', function () {
49256 controller.isOpen = false;
49258 this.WebSocket.on('console', function (msg) {
49259 controller.messages = msg.split('\n');
49262 CommandController.prototype.submit = function () {
49264 angular.forEach(this.options, function (option) {
49266 name: option.option,
49269 angular.forEach(option.arg, function (arg) {
49271 if (typeof arg.input === 'object') {
49272 obj.arguments.push(arg.input.name);
49275 obj.arguments.push(arg.input);
49279 if (obj.arguments.length > 0) {
49284 command: this.name,
49285 workspace: this.workspace.fileId,
49289 .execute(JSON.stringify(execObj))
49290 .then(function (result) {
49291 console.log(result);
49294 CommandController.prototype.removeMySelf = function (index) {
49295 this.remove()(index, this.list);
49297 CommandController.prototype.reloadFiles = function () {
49299 var fileId = this.workspace.fileId;
49303 .then(function (result) {
49304 var status = result.status;
49305 if (status === 'success') {
49306 _this.files = result.info;
49309 console.log(result.message);
49313 CommandController.prototype.debug = function () {
49316 .then(function (result) {
49317 console.log(result);
49320 CommandController.prototype.help = function () {
49323 .then(function (result) {
49324 console.log(result);
49327 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
49328 return CommandController;
49330 directives.CommandController = CommandController;
49331 })(directives = app.directives || (app.directives = {}));
49332 })(app || (app = {}));
49336 (function (directives) {
49337 var HeaderMenu = (function () {
49338 function HeaderMenu() {
49339 this.restrict = 'E';
49340 this.replace = true;
49341 this.templateUrl = 'templates/header-menu.html';
49343 HeaderMenu.Factory = function () {
49344 var directive = function () {
49345 return new HeaderMenu();
49351 directives.HeaderMenu = HeaderMenu;
49352 })(directives = app.directives || (app.directives = {}));
49353 })(app || (app = {}));
49357 (function (directives) {
49358 var Option = (function () {
49359 function Option() {
49360 this.restrict = 'E';
49361 this.replace = true;
49362 this.controller = 'optionController';
49363 this.bindToController = {
49368 this.templateUrl = 'templates/option.html';
49369 this.controllerAs = 'ctrl';
49371 Option.Factory = function () {
49372 var directive = function () {
49373 return new Option();
49375 directive.$inject = [];
49380 directives.Option = Option;
49381 var OptionController = (function () {
49382 function OptionController() {
49383 var controller = this;
49384 angular.forEach(controller.info.arg, function (arg) {
49385 if (arg.initialValue) {
49386 if (arg.formType === 'number') {
49387 arg.input = parseInt(arg.initialValue);
49390 arg.input = arg.initialValue;
49395 OptionController.$inject = [];
49396 return OptionController;
49398 directives.OptionController = OptionController;
49399 })(directives = app.directives || (app.directives = {}));
49400 })(app || (app = {}));
49404 (function (directives) {
49405 var Directory = (function () {
49406 function Directory() {
49407 this.restrict = 'E';
49408 this.replace = true;
49409 this.controller = 'directoryController';
49410 this.controllerAs = 'ctrl';
49411 this.bindToController = {
49417 this.templateUrl = 'templates/directory.html';
49419 Directory.Factory = function () {
49420 var directive = function () {
49421 return new Directory();
49427 directives.Directory = Directory;
49428 var DirectoryController = (function () {
49429 function DirectoryController(APIEndPoint, $scope) {
49430 this.APIEndPoint = APIEndPoint;
49431 this.$scope = $scope;
49432 var controller = this;
49434 .getFiles(this.info.fileId)
49436 .then(function (result) {
49437 if (result.status === 'success') {
49438 controller.files = result.info;
49439 angular.forEach(result.info, function (file) {
49440 if (file.fileType === '0') {
49442 if (controller.info.path === '/') {
49443 o.path = '/' + file.name;
49446 o.path = controller.info.path + '/' + file.name;
49448 controller.add()(o, controller.list);
49455 DirectoryController.$inject = ['APIEndPoint', '$scope'];
49456 return DirectoryController;
49458 directives.DirectoryController = DirectoryController;
49459 })(directives = app.directives || (app.directives = {}));
49460 })(app || (app = {}));
49464 (function (controllers) {
49465 var Execution = (function () {
49466 function Execution(MyModal, $scope) {
49467 this.MyModal = MyModal;
49468 this.$scope = $scope;
49469 this.commandInfoList = [];
49472 Execution.prototype.add = function () {
49473 this.$scope.$broadcast('close');
49474 var commandInfoList = this.commandInfoList;
49475 var commandInstance = this.MyModal.selectCommand();
49478 .then(function (command) {
49479 commandInfoList.push(new app.declares.CommandInfo(command));
49482 Execution.prototype.open = function () {
49483 var result = this.MyModal.open('SelectCommand');
49484 console.log(result);
49486 Execution.prototype.remove = function (index, list) {
49487 list.splice(index, 1);
49489 Execution.prototype.close = function () {
49490 console.log("close");
49492 Execution.$inject = ['MyModal', '$scope'];
49495 controllers.Execution = Execution;
49496 })(controllers = app.controllers || (app.controllers = {}));
49497 })(app || (app = {}));
49501 (function (controllers) {
49502 var Workspace = (function () {
49503 function Workspace($scope, APIEndPoint, MyModal) {
49504 this.$scope = $scope;
49505 this.APIEndPoint = APIEndPoint;
49506 this.MyModal = MyModal;
49507 this.directoryList = [];
49508 var controller = this;
49509 var directoryList = this.directoryList;
49511 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
49519 directoryList.push(o);
49521 Workspace.prototype.addDirectory = function (info, directoryList) {
49522 directoryList.push(info);
49524 Workspace.prototype.debug = function () {
49525 this.MyModal.preview();
49527 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
49530 controllers.Workspace = Workspace;
49531 })(controllers = app.controllers || (app.controllers = {}));
49532 })(app || (app = {}));
49536 (function (controllers) {
49537 var History = (function () {
49538 function History($scope) {
49539 this.page = "History";
49541 History.$inject = ['$scope'];
49544 controllers.History = History;
49545 })(controllers = app.controllers || (app.controllers = {}));
49546 })(app || (app = {}));
49550 (function (controllers) {
49551 var SelectCommand = (function () {
49552 function SelectCommand($scope, APIEndPoint, $modalInstance) {
49553 this.APIEndPoint = APIEndPoint;
49554 this.$modalInstance = $modalInstance;
49555 var controller = this;
49558 .$promise.then(function (result) {
49559 controller.tags = result.info;
49563 .$promise.then(function (result) {
49564 controller.commands = result.info;
49566 this.currentTag = 'all';
49568 SelectCommand.prototype.changeTag = function (tag) {
49569 this.currentTag = tag;
49571 SelectCommand.prototype.selectCommand = function (command) {
49572 this.$modalInstance.close(command);
49574 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
49575 return SelectCommand;
49577 controllers.SelectCommand = SelectCommand;
49578 })(controllers = app.controllers || (app.controllers = {}));
49579 })(app || (app = {}));
49583 (function (controllers) {
49584 var Preview = (function () {
49585 function Preview($scope, APIEndPoint, $modalInstance) {
49586 this.APIEndPoint = APIEndPoint;
49587 this.$modalInstance = $modalInstance;
49588 var controller = this;
49589 console.log('preview');
49591 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
49594 controllers.Preview = Preview;
49595 })(controllers = app.controllers || (app.controllers = {}));
49596 })(app || (app = {}));
49598 (function (filters) {
49600 return function (commands, tag) {
49602 angular.forEach(commands, function (command) {
49604 angular.forEach(command.tags, function (value) {
49609 result.push(command);
49615 })(filters || (filters = {}));
49619 var appName = 'zephyr';
49620 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
49621 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
49622 $urlRouterProvider.otherwise('/execution');
49623 $locationProvider.html5Mode({
49628 .state('execution', {
49630 templateUrl: 'templates/execution.html',
49631 controller: 'executionController',
49634 .state('workspace', {
49636 templateUrl: 'templates/workspace.html',
49637 controller: 'workspaceController',
49640 .state('history', {
49642 templateUrl: 'templates/history.html',
49643 controller: 'historyController',
49647 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
49648 app.zephyr.service('MyModal', app.services.MyModal);
49649 app.zephyr.service('WebSocket', app.services.WebSocket);
49650 app.zephyr.filter('Tag', filters.Tag);
49651 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
49652 app.zephyr.controller('previewController', app.controllers.Preview);
49653 app.zephyr.controller('executionController', app.controllers.Execution);
49654 app.zephyr.controller('workspaceController', app.controllers.Workspace);
49655 app.zephyr.controller('historyController', app.controllers.History);
49656 app.zephyr.controller('commandController', app.directives.CommandController);
49657 app.zephyr.controller('optionController', app.directives.OptionController);
49658 app.zephyr.controller('directoryController', app.directives.DirectoryController);
49659 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
49660 app.zephyr.directive('command', app.directives.Command.Factory());
49661 app.zephyr.directive('option', app.directives.Option.Factory());
49662 app.zephyr.directive('directory', app.directives.Directory.Factory());
49663 })(app || (app = {}));