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 var messages = msg.split('\n');
41213 if (messages[0].substr(0, 6) === 'Usage:') {
41214 controller.messages = msg.split('\n');
41218 CommandController.prototype.submit = function () {
41220 angular.forEach(this.options, function (option) {
41222 name: option.option,
41225 angular.forEach(option.arg, function (arg) {
41227 if (typeof arg.input === 'object') {
41228 obj.arguments.push(arg.input.name);
41231 obj.arguments.push(arg.input);
41235 if (obj.arguments.length > 0) {
41240 command: this.name,
41241 workspace: this.workspace.fileId,
41245 .execute(JSON.stringify(execObj))
41246 .then(function (result) {
41247 console.log(result);
41250 CommandController.prototype.removeMySelf = function (index) {
41251 this.remove()(index, this.list);
41253 CommandController.prototype.reloadFiles = function () {
41255 var fileId = this.workspace.fileId;
41259 .then(function (result) {
41260 var status = result.status;
41261 if (status === 'success') {
41262 _this.files = result.info;
41265 console.log(result.message);
41269 CommandController.prototype.debug = function () {
41272 .then(function (result) {
41273 console.log(result);
41276 CommandController.prototype.help = function () {
41279 .then(function (result) {
41280 console.log(result);
41283 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
41284 return CommandController;
41286 directives.CommandController = CommandController;
41287 })(directives = app.directives || (app.directives = {}));
41288 })(app || (app = {}));
41292 (function (directives) {
41293 var HeaderMenu = (function () {
41294 function HeaderMenu() {
41295 this.restrict = 'E';
41296 this.replace = true;
41297 this.templateUrl = 'templates/header-menu.html';
41298 this.controller = 'HeaderMenuController';
41299 this.controllerAs = 'hmc';
41302 HeaderMenu.Factory = function () {
41303 var directive = function () {
41304 return new HeaderMenu();
41310 directives.HeaderMenu = HeaderMenu;
41311 var HeaderMenuController = (function () {
41312 function HeaderMenuController($state) {
41313 this.$state = $state;
41314 this.isExecution = this.$state.current.name === 'execution';
41315 this.isWorkspace = this.$state.current.name === 'workspace';
41316 this.isHistory = this.$state.current.name === 'history';
41318 HeaderMenuController.prototype.transit = function (state) {
41319 this.$state.go(state);
41321 HeaderMenuController.$inject = ['$state'];
41322 return HeaderMenuController;
41324 directives.HeaderMenuController = HeaderMenuController;
41325 })(directives = app.directives || (app.directives = {}));
41326 })(app || (app = {}));
41330 (function (directives) {
41331 var Option = (function () {
41332 function Option() {
41333 this.restrict = 'E';
41334 this.replace = true;
41335 this.controller = 'optionController';
41336 this.bindToController = {
41341 this.templateUrl = 'templates/option.html';
41342 this.controllerAs = 'ctrl';
41344 Option.Factory = function () {
41345 var directive = function () {
41346 return new Option();
41348 directive.$inject = [];
41353 directives.Option = Option;
41354 var OptionController = (function () {
41355 function OptionController() {
41356 var controller = this;
41357 angular.forEach(controller.info.arg, function (arg) {
41358 if (arg.initialValue) {
41359 if (arg.formType === 'number') {
41360 arg.input = parseInt(arg.initialValue);
41363 arg.input = arg.initialValue;
41368 OptionController.$inject = [];
41369 return OptionController;
41371 directives.OptionController = OptionController;
41372 })(directives = app.directives || (app.directives = {}));
41373 })(app || (app = {}));
41377 (function (directives) {
41378 var Directory = (function () {
41379 function Directory() {
41380 this.restrict = 'E';
41381 this.replace = true;
41382 this.controller = 'directoryController';
41383 this.controllerAs = 'ctrl';
41384 this.bindToController = {
41390 this.templateUrl = 'templates/directory.html';
41392 Directory.Factory = function () {
41393 var directive = function () {
41394 return new Directory();
41400 directives.Directory = Directory;
41401 var DirectoryController = (function () {
41402 function DirectoryController(APIEndPoint, $scope) {
41403 this.APIEndPoint = APIEndPoint;
41404 this.$scope = $scope;
41405 var controller = this;
41407 .getFiles(this.info.fileId)
41409 .then(function (result) {
41410 if (result.status === 'success') {
41411 controller.files = result.info;
41412 angular.forEach(result.info, function (file) {
41413 if (file.fileType === '0') {
41415 if (controller.info.path === '/') {
41416 o.path = '/' + file.name;
41419 o.path = controller.info.path + '/' + file.name;
41421 controller.add()(o, controller.list);
41428 DirectoryController.$inject = ['APIEndPoint', '$scope'];
41429 return DirectoryController;
41431 directives.DirectoryController = DirectoryController;
41432 })(directives = app.directives || (app.directives = {}));
41433 })(app || (app = {}));
41437 (function (controllers) {
41438 var Execution = (function () {
41439 function Execution(MyModal, $scope) {
41440 this.MyModal = MyModal;
41441 this.$scope = $scope;
41442 this.commandInfoList = [];
41445 Execution.prototype.add = function () {
41446 this.$scope.$broadcast('close');
41447 var commandInfoList = this.commandInfoList;
41448 var commandInstance = this.MyModal.selectCommand();
41451 .then(function (command) {
41452 commandInfoList.push(new app.declares.CommandInfo(command));
41455 Execution.prototype.open = function () {
41456 var result = this.MyModal.open('SelectCommand');
41457 console.log(result);
41459 Execution.prototype.remove = function (index, list) {
41460 list.splice(index, 1);
41462 Execution.prototype.close = function () {
41463 console.log("close");
41465 Execution.$inject = ['MyModal', '$scope'];
41468 controllers.Execution = Execution;
41469 })(controllers = app.controllers || (app.controllers = {}));
41470 })(app || (app = {}));
41474 (function (controllers) {
41475 var Workspace = (function () {
41476 function Workspace($scope, APIEndPoint, MyModal) {
41477 this.$scope = $scope;
41478 this.APIEndPoint = APIEndPoint;
41479 this.MyModal = MyModal;
41480 this.directoryList = [];
41481 var controller = this;
41482 var directoryList = this.directoryList;
41484 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
41492 directoryList.push(o);
41494 Workspace.prototype.addDirectory = function (info, directoryList) {
41495 directoryList.push(info);
41497 Workspace.prototype.debug = function () {
41498 this.MyModal.preview();
41500 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
41503 controllers.Workspace = Workspace;
41504 })(controllers = app.controllers || (app.controllers = {}));
41505 })(app || (app = {}));
41509 (function (controllers) {
41510 var History = (function () {
41511 function History($scope) {
41512 this.page = "History";
41514 History.$inject = ['$scope'];
41517 controllers.History = History;
41518 })(controllers = app.controllers || (app.controllers = {}));
41519 })(app || (app = {}));
41523 (function (controllers) {
41524 var SelectCommand = (function () {
41525 function SelectCommand($scope, APIEndPoint, $modalInstance) {
41526 this.APIEndPoint = APIEndPoint;
41527 this.$modalInstance = $modalInstance;
41528 var controller = this;
41531 .$promise.then(function (result) {
41532 controller.tags = result.info;
41536 .$promise.then(function (result) {
41537 controller.commands = result.info;
41539 this.currentTag = 'all';
41541 SelectCommand.prototype.changeTag = function (tag) {
41542 this.currentTag = tag;
41544 SelectCommand.prototype.selectCommand = function (command) {
41545 this.$modalInstance.close(command);
41547 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
41548 return SelectCommand;
41550 controllers.SelectCommand = SelectCommand;
41551 })(controllers = app.controllers || (app.controllers = {}));
41552 })(app || (app = {}));
41556 (function (controllers) {
41557 var Preview = (function () {
41558 function Preview($scope, APIEndPoint, $modalInstance) {
41559 this.APIEndPoint = APIEndPoint;
41560 this.$modalInstance = $modalInstance;
41561 var controller = this;
41562 console.log('preview');
41564 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
41567 controllers.Preview = Preview;
41568 })(controllers = app.controllers || (app.controllers = {}));
41569 })(app || (app = {}));
41571 (function (filters) {
41573 return function (commands, tag) {
41575 angular.forEach(commands, function (command) {
41577 angular.forEach(command.tags, function (value) {
41582 result.push(command);
41588 })(filters || (filters = {}));
41592 var appName = 'zephyr';
41593 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
41594 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
41595 $urlRouterProvider.otherwise('/execution');
41596 $locationProvider.html5Mode({
41601 .state('execution', {
41603 templateUrl: 'templates/execution.html',
41604 controller: 'executionController',
41607 .state('workspace', {
41609 templateUrl: 'templates/workspace.html',
41610 controller: 'workspaceController',
41613 .state('history', {
41615 templateUrl: 'templates/history.html',
41616 controller: 'historyController',
41620 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
41621 app.zephyr.service('MyModal', app.services.MyModal);
41622 app.zephyr.service('WebSocket', app.services.WebSocket);
41623 app.zephyr.filter('Tag', filters.Tag);
41624 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
41625 app.zephyr.controller('previewController', app.controllers.Preview);
41626 app.zephyr.controller('executionController', app.controllers.Execution);
41627 app.zephyr.controller('workspaceController', app.controllers.Workspace);
41628 app.zephyr.controller('historyController', app.controllers.History);
41629 app.zephyr.controller('commandController', app.directives.CommandController);
41630 app.zephyr.controller('optionController', app.directives.OptionController);
41631 app.zephyr.controller('directoryController', app.directives.DirectoryController);
41632 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
41633 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
41634 app.zephyr.directive('command', app.directives.Command.Factory());
41635 app.zephyr.directive('option', app.directives.Option.Factory());
41636 app.zephyr.directive('directory', app.directives.Directory.Factory());
41637 })(app || (app = {}));
41642 /***/ function(module, exports) {
41647 (function (declares) {
41648 var CommandInfo = (function () {
41649 function CommandInfo(name) {
41652 return CommandInfo;
41654 declares.CommandInfo = CommandInfo;
41655 })(declares = app.declares || (app.declares = {}));
41656 })(app || (app = {}));
41660 (function (services) {
41661 var APIEndPoint = (function () {
41662 function APIEndPoint($resource, $http) {
41663 this.$resource = $resource;
41664 this.$http = $http;
41666 APIEndPoint.prototype.resource = function (endPoint, data) {
41667 var customAction = {
41673 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
41675 return this.$resource(endPoint, {}, { execute: execute });
41677 APIEndPoint.prototype.getOptionControlFile = function (command) {
41678 var endPoint = '/api/v1/optionControlFile/' + command;
41679 return this.resource(endPoint, {}).get();
41681 APIEndPoint.prototype.getFiles = function (fileId) {
41682 var endPoint = '/api/v1/workspace';
41684 endPoint += '/' + fileId;
41686 return this.resource(endPoint, {}).get();
41688 APIEndPoint.prototype.getDirectories = function () {
41689 var endPoint = '/api/v1/all/workspace/directory';
41690 return this.resource(endPoint, {}).get();
41692 APIEndPoint.prototype.getTags = function () {
41693 var endPoint = '/api/v1/tagList';
41694 return this.resource(endPoint, {}).get();
41696 APIEndPoint.prototype.getCommands = function () {
41697 var endPoint = '/api/v1/commandList';
41698 return this.resource(endPoint, {}).get();
41700 APIEndPoint.prototype.execute = function (data) {
41701 var endPoint = '/api/v1/execution';
41702 var fd = new FormData();
41703 fd.append('data', data);
41704 return this.$http.post(endPoint, fd, {
41705 headers: { 'Content-Type': undefined },
41706 transformRequest: angular.identity
41709 APIEndPoint.prototype.debug = function () {
41710 var endPoint = '/api/v1/debug';
41711 return this.$http.get(endPoint);
41713 APIEndPoint.prototype.help = function (command) {
41714 var endPoint = '/api/v1/help/' + command;
41715 return this.$http.get(endPoint);
41717 return APIEndPoint;
41719 services.APIEndPoint = APIEndPoint;
41720 })(services = app.services || (app.services = {}));
41721 })(app || (app = {}));
41725 (function (services) {
41726 var MyModal = (function () {
41727 function MyModal($uibModal) {
41728 this.$uibModal = $uibModal;
41729 this.modalOption = {
41736 MyModal.prototype.open = function (modalName) {
41737 if (modalName === 'SelectCommand') {
41738 this.modalOption.templateUrl = 'templates/select-command.html';
41739 this.modalOption.size = 'lg';
41741 return this.$uibModal.open(this.modalOption);
41743 MyModal.prototype.selectCommand = function () {
41744 this.modalOption.templateUrl = 'templates/select-command.html';
41745 this.modalOption.controller = 'selectCommandController';
41746 this.modalOption.controllerAs = 'c';
41747 this.modalOption.size = 'lg';
41748 return this.$uibModal.open(this.modalOption);
41750 MyModal.prototype.preview = function () {
41751 this.modalOption.templateUrl = 'templates/preview.html';
41752 this.modalOption.controller = 'previewController';
41753 this.modalOption.controllerAs = 'c';
41754 this.modalOption.size = 'lg';
41755 return this.$uibModal.open(this.modalOption);
41757 MyModal.$inject = ['$uibModal'];
41760 services.MyModal = MyModal;
41761 })(services = app.services || (app.services = {}));
41762 })(app || (app = {}));
41766 (function (services) {
41767 var WebSocket = (function () {
41768 function WebSocket($rootScope) {
41769 this.$rootScope = $rootScope;
41770 this.socket = io.connect();
41772 WebSocket.prototype.on = function (eventName, callback) {
41773 var socket = this.socket;
41774 var rootScope = this.$rootScope;
41775 socket.on(eventName, function () {
41776 var args = arguments;
41777 rootScope.$apply(function () {
41778 callback.apply(socket, args);
41782 WebSocket.prototype.emit = function (eventName, data, callback) {
41783 var socket = this.socket;
41784 var rootScope = this.$rootScope;
41785 this.socket.emit(eventName, data, function () {
41786 var args = arguments;
41787 rootScope.$apply(function () {
41789 callback.apply(socket, args);
41795 services.WebSocket = WebSocket;
41796 })(services = app.services || (app.services = {}));
41797 })(app || (app = {}));
41801 (function (directives) {
41802 var Command = (function () {
41803 function Command() {
41804 this.restrict = 'E';
41805 this.replace = true;
41807 this.controller = 'commandController';
41808 this.controllerAs = 'ctrl';
41809 this.bindToController = {
41815 this.templateUrl = 'templates/command.html';
41817 Command.Factory = function () {
41818 var directive = function () {
41819 return new Command();
41821 directive.$inject = [];
41826 directives.Command = Command;
41827 var CommandController = (function () {
41828 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
41829 this.APIEndPoint = APIEndPoint;
41830 this.$scope = $scope;
41831 this.MyModal = MyModal;
41832 this.WebSocket = WebSocket;
41833 var controller = this;
41835 .getOptionControlFile(this.name)
41837 .then(function (result) {
41838 controller.options = result.info;
41843 .then(function (result) {
41844 controller.dirs = result.info;
41846 this.heading = "[" + this.index + "]: dcdFilePrint";
41847 this.isOpen = true;
41848 this.$scope.$on('close', function () {
41849 controller.isOpen = false;
41851 this.WebSocket.on('console', function (msg) {
41852 var messages = msg.split('\n');
41853 if (messages[0].substr(0, 6) === 'Usage:') {
41854 controller.messages = msg.split('\n');
41858 CommandController.prototype.submit = function () {
41860 angular.forEach(this.options, function (option) {
41862 name: option.option,
41865 angular.forEach(option.arg, function (arg) {
41867 if (typeof arg.input === 'object') {
41868 obj.arguments.push(arg.input.name);
41871 obj.arguments.push(arg.input);
41875 if (obj.arguments.length > 0) {
41880 command: this.name,
41881 workspace: this.workspace.fileId,
41885 .execute(JSON.stringify(execObj))
41886 .then(function (result) {
41887 console.log(result);
41890 CommandController.prototype.removeMySelf = function (index) {
41891 this.remove()(index, this.list);
41893 CommandController.prototype.reloadFiles = function () {
41895 var fileId = this.workspace.fileId;
41899 .then(function (result) {
41900 var status = result.status;
41901 if (status === 'success') {
41902 _this.files = result.info;
41905 console.log(result.message);
41909 CommandController.prototype.debug = function () {
41912 .then(function (result) {
41913 console.log(result);
41916 CommandController.prototype.help = function () {
41919 .then(function (result) {
41920 console.log(result);
41923 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
41924 return CommandController;
41926 directives.CommandController = CommandController;
41927 })(directives = app.directives || (app.directives = {}));
41928 })(app || (app = {}));
41932 (function (directives) {
41933 var HeaderMenu = (function () {
41934 function HeaderMenu() {
41935 this.restrict = 'E';
41936 this.replace = true;
41937 this.templateUrl = 'templates/header-menu.html';
41938 this.controller = 'HeaderMenuController';
41939 this.controllerAs = 'hmc';
41942 HeaderMenu.Factory = function () {
41943 var directive = function () {
41944 return new HeaderMenu();
41950 directives.HeaderMenu = HeaderMenu;
41951 var HeaderMenuController = (function () {
41952 function HeaderMenuController($state) {
41953 this.$state = $state;
41954 this.isExecution = this.$state.current.name === 'execution';
41955 this.isWorkspace = this.$state.current.name === 'workspace';
41956 this.isHistory = this.$state.current.name === 'history';
41958 HeaderMenuController.prototype.transit = function (state) {
41959 this.$state.go(state);
41961 HeaderMenuController.$inject = ['$state'];
41962 return HeaderMenuController;
41964 directives.HeaderMenuController = HeaderMenuController;
41965 })(directives = app.directives || (app.directives = {}));
41966 })(app || (app = {}));
41970 (function (directives) {
41971 var Option = (function () {
41972 function Option() {
41973 this.restrict = 'E';
41974 this.replace = true;
41975 this.controller = 'optionController';
41976 this.bindToController = {
41981 this.templateUrl = 'templates/option.html';
41982 this.controllerAs = 'ctrl';
41984 Option.Factory = function () {
41985 var directive = function () {
41986 return new Option();
41988 directive.$inject = [];
41993 directives.Option = Option;
41994 var OptionController = (function () {
41995 function OptionController() {
41996 var controller = this;
41997 angular.forEach(controller.info.arg, function (arg) {
41998 if (arg.initialValue) {
41999 if (arg.formType === 'number') {
42000 arg.input = parseInt(arg.initialValue);
42003 arg.input = arg.initialValue;
42008 OptionController.$inject = [];
42009 return OptionController;
42011 directives.OptionController = OptionController;
42012 })(directives = app.directives || (app.directives = {}));
42013 })(app || (app = {}));
42017 (function (directives) {
42018 var Directory = (function () {
42019 function Directory() {
42020 this.restrict = 'E';
42021 this.replace = true;
42022 this.controller = 'directoryController';
42023 this.controllerAs = 'ctrl';
42024 this.bindToController = {
42030 this.templateUrl = 'templates/directory.html';
42032 Directory.Factory = function () {
42033 var directive = function () {
42034 return new Directory();
42040 directives.Directory = Directory;
42041 var DirectoryController = (function () {
42042 function DirectoryController(APIEndPoint, $scope) {
42043 this.APIEndPoint = APIEndPoint;
42044 this.$scope = $scope;
42045 var controller = this;
42047 .getFiles(this.info.fileId)
42049 .then(function (result) {
42050 if (result.status === 'success') {
42051 controller.files = result.info;
42052 angular.forEach(result.info, function (file) {
42053 if (file.fileType === '0') {
42055 if (controller.info.path === '/') {
42056 o.path = '/' + file.name;
42059 o.path = controller.info.path + '/' + file.name;
42061 controller.add()(o, controller.list);
42068 DirectoryController.$inject = ['APIEndPoint', '$scope'];
42069 return DirectoryController;
42071 directives.DirectoryController = DirectoryController;
42072 })(directives = app.directives || (app.directives = {}));
42073 })(app || (app = {}));
42077 (function (controllers) {
42078 var Execution = (function () {
42079 function Execution(MyModal, $scope) {
42080 this.MyModal = MyModal;
42081 this.$scope = $scope;
42082 this.commandInfoList = [];
42085 Execution.prototype.add = function () {
42086 this.$scope.$broadcast('close');
42087 var commandInfoList = this.commandInfoList;
42088 var commandInstance = this.MyModal.selectCommand();
42091 .then(function (command) {
42092 commandInfoList.push(new app.declares.CommandInfo(command));
42095 Execution.prototype.open = function () {
42096 var result = this.MyModal.open('SelectCommand');
42097 console.log(result);
42099 Execution.prototype.remove = function (index, list) {
42100 list.splice(index, 1);
42102 Execution.prototype.close = function () {
42103 console.log("close");
42105 Execution.$inject = ['MyModal', '$scope'];
42108 controllers.Execution = Execution;
42109 })(controllers = app.controllers || (app.controllers = {}));
42110 })(app || (app = {}));
42114 (function (controllers) {
42115 var Workspace = (function () {
42116 function Workspace($scope, APIEndPoint, MyModal) {
42117 this.$scope = $scope;
42118 this.APIEndPoint = APIEndPoint;
42119 this.MyModal = MyModal;
42120 this.directoryList = [];
42121 var controller = this;
42122 var directoryList = this.directoryList;
42124 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
42132 directoryList.push(o);
42134 Workspace.prototype.addDirectory = function (info, directoryList) {
42135 directoryList.push(info);
42137 Workspace.prototype.debug = function () {
42138 this.MyModal.preview();
42140 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
42143 controllers.Workspace = Workspace;
42144 })(controllers = app.controllers || (app.controllers = {}));
42145 })(app || (app = {}));
42149 (function (controllers) {
42150 var History = (function () {
42151 function History($scope) {
42152 this.page = "History";
42154 History.$inject = ['$scope'];
42157 controllers.History = History;
42158 })(controllers = app.controllers || (app.controllers = {}));
42159 })(app || (app = {}));
42163 (function (controllers) {
42164 var SelectCommand = (function () {
42165 function SelectCommand($scope, APIEndPoint, $modalInstance) {
42166 this.APIEndPoint = APIEndPoint;
42167 this.$modalInstance = $modalInstance;
42168 var controller = this;
42171 .$promise.then(function (result) {
42172 controller.tags = result.info;
42176 .$promise.then(function (result) {
42177 controller.commands = result.info;
42179 this.currentTag = 'all';
42181 SelectCommand.prototype.changeTag = function (tag) {
42182 this.currentTag = tag;
42184 SelectCommand.prototype.selectCommand = function (command) {
42185 this.$modalInstance.close(command);
42187 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
42188 return SelectCommand;
42190 controllers.SelectCommand = SelectCommand;
42191 })(controllers = app.controllers || (app.controllers = {}));
42192 })(app || (app = {}));
42196 (function (controllers) {
42197 var Preview = (function () {
42198 function Preview($scope, APIEndPoint, $modalInstance) {
42199 this.APIEndPoint = APIEndPoint;
42200 this.$modalInstance = $modalInstance;
42201 var controller = this;
42202 console.log('preview');
42204 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
42207 controllers.Preview = Preview;
42208 })(controllers = app.controllers || (app.controllers = {}));
42209 })(app || (app = {}));
42211 (function (filters) {
42213 return function (commands, tag) {
42215 angular.forEach(commands, function (command) {
42217 angular.forEach(command.tags, function (value) {
42222 result.push(command);
42228 })(filters || (filters = {}));
42232 var appName = 'zephyr';
42233 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
42234 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
42235 $urlRouterProvider.otherwise('/execution');
42236 $locationProvider.html5Mode({
42241 .state('execution', {
42243 templateUrl: 'templates/execution.html',
42244 controller: 'executionController',
42247 .state('workspace', {
42249 templateUrl: 'templates/workspace.html',
42250 controller: 'workspaceController',
42253 .state('history', {
42255 templateUrl: 'templates/history.html',
42256 controller: 'historyController',
42260 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
42261 app.zephyr.service('MyModal', app.services.MyModal);
42262 app.zephyr.service('WebSocket', app.services.WebSocket);
42263 app.zephyr.filter('Tag', filters.Tag);
42264 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
42265 app.zephyr.controller('previewController', app.controllers.Preview);
42266 app.zephyr.controller('executionController', app.controllers.Execution);
42267 app.zephyr.controller('workspaceController', app.controllers.Workspace);
42268 app.zephyr.controller('historyController', app.controllers.History);
42269 app.zephyr.controller('commandController', app.directives.CommandController);
42270 app.zephyr.controller('optionController', app.directives.OptionController);
42271 app.zephyr.controller('directoryController', app.directives.DirectoryController);
42272 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
42273 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
42274 app.zephyr.directive('command', app.directives.Command.Factory());
42275 app.zephyr.directive('option', app.directives.Option.Factory());
42276 app.zephyr.directive('directory', app.directives.Directory.Factory());
42277 })(app || (app = {}));
42282 /***/ function(module, exports) {
42287 (function (declares) {
42288 var CommandInfo = (function () {
42289 function CommandInfo(name) {
42292 return CommandInfo;
42294 declares.CommandInfo = CommandInfo;
42295 })(declares = app.declares || (app.declares = {}));
42296 })(app || (app = {}));
42300 (function (services) {
42301 var APIEndPoint = (function () {
42302 function APIEndPoint($resource, $http) {
42303 this.$resource = $resource;
42304 this.$http = $http;
42306 APIEndPoint.prototype.resource = function (endPoint, data) {
42307 var customAction = {
42313 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
42315 return this.$resource(endPoint, {}, { execute: execute });
42317 APIEndPoint.prototype.getOptionControlFile = function (command) {
42318 var endPoint = '/api/v1/optionControlFile/' + command;
42319 return this.resource(endPoint, {}).get();
42321 APIEndPoint.prototype.getFiles = function (fileId) {
42322 var endPoint = '/api/v1/workspace';
42324 endPoint += '/' + fileId;
42326 return this.resource(endPoint, {}).get();
42328 APIEndPoint.prototype.getDirectories = function () {
42329 var endPoint = '/api/v1/all/workspace/directory';
42330 return this.resource(endPoint, {}).get();
42332 APIEndPoint.prototype.getTags = function () {
42333 var endPoint = '/api/v1/tagList';
42334 return this.resource(endPoint, {}).get();
42336 APIEndPoint.prototype.getCommands = function () {
42337 var endPoint = '/api/v1/commandList';
42338 return this.resource(endPoint, {}).get();
42340 APIEndPoint.prototype.execute = function (data) {
42341 var endPoint = '/api/v1/execution';
42342 var fd = new FormData();
42343 fd.append('data', data);
42344 return this.$http.post(endPoint, fd, {
42345 headers: { 'Content-Type': undefined },
42346 transformRequest: angular.identity
42349 APIEndPoint.prototype.debug = function () {
42350 var endPoint = '/api/v1/debug';
42351 return this.$http.get(endPoint);
42353 APIEndPoint.prototype.help = function (command) {
42354 var endPoint = '/api/v1/help/' + command;
42355 return this.$http.get(endPoint);
42357 return APIEndPoint;
42359 services.APIEndPoint = APIEndPoint;
42360 })(services = app.services || (app.services = {}));
42361 })(app || (app = {}));
42365 (function (services) {
42366 var MyModal = (function () {
42367 function MyModal($uibModal) {
42368 this.$uibModal = $uibModal;
42369 this.modalOption = {
42376 MyModal.prototype.open = function (modalName) {
42377 if (modalName === 'SelectCommand') {
42378 this.modalOption.templateUrl = 'templates/select-command.html';
42379 this.modalOption.size = 'lg';
42381 return this.$uibModal.open(this.modalOption);
42383 MyModal.prototype.selectCommand = function () {
42384 this.modalOption.templateUrl = 'templates/select-command.html';
42385 this.modalOption.controller = 'selectCommandController';
42386 this.modalOption.controllerAs = 'c';
42387 this.modalOption.size = 'lg';
42388 return this.$uibModal.open(this.modalOption);
42390 MyModal.prototype.preview = function () {
42391 this.modalOption.templateUrl = 'templates/preview.html';
42392 this.modalOption.controller = 'previewController';
42393 this.modalOption.controllerAs = 'c';
42394 this.modalOption.size = 'lg';
42395 return this.$uibModal.open(this.modalOption);
42397 MyModal.$inject = ['$uibModal'];
42400 services.MyModal = MyModal;
42401 })(services = app.services || (app.services = {}));
42402 })(app || (app = {}));
42406 (function (services) {
42407 var WebSocket = (function () {
42408 function WebSocket($rootScope) {
42409 this.$rootScope = $rootScope;
42410 this.socket = io.connect();
42412 WebSocket.prototype.on = function (eventName, callback) {
42413 var socket = this.socket;
42414 var rootScope = this.$rootScope;
42415 socket.on(eventName, function () {
42416 var args = arguments;
42417 rootScope.$apply(function () {
42418 callback.apply(socket, args);
42422 WebSocket.prototype.emit = function (eventName, data, callback) {
42423 var socket = this.socket;
42424 var rootScope = this.$rootScope;
42425 this.socket.emit(eventName, data, function () {
42426 var args = arguments;
42427 rootScope.$apply(function () {
42429 callback.apply(socket, args);
42435 services.WebSocket = WebSocket;
42436 })(services = app.services || (app.services = {}));
42437 })(app || (app = {}));
42441 (function (directives) {
42442 var Command = (function () {
42443 function Command() {
42444 this.restrict = 'E';
42445 this.replace = true;
42447 this.controller = 'commandController';
42448 this.controllerAs = 'ctrl';
42449 this.bindToController = {
42455 this.templateUrl = 'templates/command.html';
42457 Command.Factory = function () {
42458 var directive = function () {
42459 return new Command();
42461 directive.$inject = [];
42466 directives.Command = Command;
42467 var CommandController = (function () {
42468 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
42469 this.APIEndPoint = APIEndPoint;
42470 this.$scope = $scope;
42471 this.MyModal = MyModal;
42472 this.WebSocket = WebSocket;
42473 var controller = this;
42475 .getOptionControlFile(this.name)
42477 .then(function (result) {
42478 controller.options = result.info;
42483 .then(function (result) {
42484 controller.dirs = result.info;
42486 this.heading = "[" + this.index + "]: dcdFilePrint";
42487 this.isOpen = true;
42488 this.$scope.$on('close', function () {
42489 controller.isOpen = false;
42491 this.WebSocket.on('console', function (msg) {
42492 var messages = msg.split('\n');
42493 if (messages[0].substr(0, 6) === 'Usage:') {
42494 controller.messages = msg.split('\n');
42498 CommandController.prototype.submit = function () {
42500 angular.forEach(this.options, function (option) {
42502 name: option.option,
42505 angular.forEach(option.arg, function (arg) {
42507 if (typeof arg.input === 'object') {
42508 obj.arguments.push(arg.input.name);
42511 obj.arguments.push(arg.input);
42515 if (obj.arguments.length > 0) {
42520 command: this.name,
42521 workspace: this.workspace.fileId,
42525 .execute(JSON.stringify(execObj))
42526 .then(function (result) {
42527 console.log(result);
42530 CommandController.prototype.removeMySelf = function (index) {
42531 this.remove()(index, this.list);
42533 CommandController.prototype.reloadFiles = function () {
42535 var fileId = this.workspace.fileId;
42539 .then(function (result) {
42540 var status = result.status;
42541 if (status === 'success') {
42542 _this.files = result.info;
42545 console.log(result.message);
42549 CommandController.prototype.debug = function () {
42552 .then(function (result) {
42553 console.log(result);
42556 CommandController.prototype.help = function () {
42559 .then(function (result) {
42560 console.log(result);
42563 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
42564 return CommandController;
42566 directives.CommandController = CommandController;
42567 })(directives = app.directives || (app.directives = {}));
42568 })(app || (app = {}));
42572 (function (directives) {
42573 var HeaderMenu = (function () {
42574 function HeaderMenu() {
42575 this.restrict = 'E';
42576 this.replace = true;
42577 this.templateUrl = 'templates/header-menu.html';
42578 this.controller = 'HeaderMenuController';
42579 this.controllerAs = 'hmc';
42582 HeaderMenu.Factory = function () {
42583 var directive = function () {
42584 return new HeaderMenu();
42590 directives.HeaderMenu = HeaderMenu;
42591 var HeaderMenuController = (function () {
42592 function HeaderMenuController($state) {
42593 this.$state = $state;
42594 this.isExecution = this.$state.current.name === 'execution';
42595 this.isWorkspace = this.$state.current.name === 'workspace';
42596 this.isHistory = this.$state.current.name === 'history';
42598 HeaderMenuController.prototype.transit = function (state) {
42599 this.$state.go(state);
42601 HeaderMenuController.$inject = ['$state'];
42602 return HeaderMenuController;
42604 directives.HeaderMenuController = HeaderMenuController;
42605 })(directives = app.directives || (app.directives = {}));
42606 })(app || (app = {}));
42610 (function (directives) {
42611 var Option = (function () {
42612 function Option() {
42613 this.restrict = 'E';
42614 this.replace = true;
42615 this.controller = 'optionController';
42616 this.bindToController = {
42621 this.templateUrl = 'templates/option.html';
42622 this.controllerAs = 'ctrl';
42624 Option.Factory = function () {
42625 var directive = function () {
42626 return new Option();
42628 directive.$inject = [];
42633 directives.Option = Option;
42634 var OptionController = (function () {
42635 function OptionController() {
42636 var controller = this;
42637 angular.forEach(controller.info.arg, function (arg) {
42638 if (arg.initialValue) {
42639 if (arg.formType === 'number') {
42640 arg.input = parseInt(arg.initialValue);
42643 arg.input = arg.initialValue;
42648 OptionController.$inject = [];
42649 return OptionController;
42651 directives.OptionController = OptionController;
42652 })(directives = app.directives || (app.directives = {}));
42653 })(app || (app = {}));
42657 (function (directives) {
42658 var Directory = (function () {
42659 function Directory() {
42660 this.restrict = 'E';
42661 this.replace = true;
42662 this.controller = 'directoryController';
42663 this.controllerAs = 'ctrl';
42664 this.bindToController = {
42670 this.templateUrl = 'templates/directory.html';
42672 Directory.Factory = function () {
42673 var directive = function () {
42674 return new Directory();
42680 directives.Directory = Directory;
42681 var DirectoryController = (function () {
42682 function DirectoryController(APIEndPoint, $scope) {
42683 this.APIEndPoint = APIEndPoint;
42684 this.$scope = $scope;
42685 var controller = this;
42687 .getFiles(this.info.fileId)
42689 .then(function (result) {
42690 if (result.status === 'success') {
42691 controller.files = result.info;
42692 angular.forEach(result.info, function (file) {
42693 if (file.fileType === '0') {
42695 if (controller.info.path === '/') {
42696 o.path = '/' + file.name;
42699 o.path = controller.info.path + '/' + file.name;
42701 controller.add()(o, controller.list);
42708 DirectoryController.$inject = ['APIEndPoint', '$scope'];
42709 return DirectoryController;
42711 directives.DirectoryController = DirectoryController;
42712 })(directives = app.directives || (app.directives = {}));
42713 })(app || (app = {}));
42717 (function (controllers) {
42718 var Execution = (function () {
42719 function Execution(MyModal, $scope) {
42720 this.MyModal = MyModal;
42721 this.$scope = $scope;
42722 this.commandInfoList = [];
42725 Execution.prototype.add = function () {
42726 this.$scope.$broadcast('close');
42727 var commandInfoList = this.commandInfoList;
42728 var commandInstance = this.MyModal.selectCommand();
42731 .then(function (command) {
42732 commandInfoList.push(new app.declares.CommandInfo(command));
42735 Execution.prototype.open = function () {
42736 var result = this.MyModal.open('SelectCommand');
42737 console.log(result);
42739 Execution.prototype.remove = function (index, list) {
42740 list.splice(index, 1);
42742 Execution.prototype.close = function () {
42743 console.log("close");
42745 Execution.$inject = ['MyModal', '$scope'];
42748 controllers.Execution = Execution;
42749 })(controllers = app.controllers || (app.controllers = {}));
42750 })(app || (app = {}));
42754 (function (controllers) {
42755 var Workspace = (function () {
42756 function Workspace($scope, APIEndPoint, MyModal) {
42757 this.$scope = $scope;
42758 this.APIEndPoint = APIEndPoint;
42759 this.MyModal = MyModal;
42760 this.directoryList = [];
42761 var controller = this;
42762 var directoryList = this.directoryList;
42764 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
42772 directoryList.push(o);
42774 Workspace.prototype.addDirectory = function (info, directoryList) {
42775 directoryList.push(info);
42777 Workspace.prototype.debug = function () {
42778 this.MyModal.preview();
42780 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
42783 controllers.Workspace = Workspace;
42784 })(controllers = app.controllers || (app.controllers = {}));
42785 })(app || (app = {}));
42789 (function (controllers) {
42790 var History = (function () {
42791 function History($scope) {
42792 this.page = "History";
42794 History.$inject = ['$scope'];
42797 controllers.History = History;
42798 })(controllers = app.controllers || (app.controllers = {}));
42799 })(app || (app = {}));
42803 (function (controllers) {
42804 var SelectCommand = (function () {
42805 function SelectCommand($scope, APIEndPoint, $modalInstance) {
42806 this.APIEndPoint = APIEndPoint;
42807 this.$modalInstance = $modalInstance;
42808 var controller = this;
42811 .$promise.then(function (result) {
42812 controller.tags = result.info;
42816 .$promise.then(function (result) {
42817 controller.commands = result.info;
42819 this.currentTag = 'all';
42821 SelectCommand.prototype.changeTag = function (tag) {
42822 this.currentTag = tag;
42824 SelectCommand.prototype.selectCommand = function (command) {
42825 this.$modalInstance.close(command);
42827 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
42828 return SelectCommand;
42830 controllers.SelectCommand = SelectCommand;
42831 })(controllers = app.controllers || (app.controllers = {}));
42832 })(app || (app = {}));
42836 (function (controllers) {
42837 var Preview = (function () {
42838 function Preview($scope, APIEndPoint, $modalInstance) {
42839 this.APIEndPoint = APIEndPoint;
42840 this.$modalInstance = $modalInstance;
42841 var controller = this;
42842 console.log('preview');
42844 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
42847 controllers.Preview = Preview;
42848 })(controllers = app.controllers || (app.controllers = {}));
42849 })(app || (app = {}));
42851 (function (filters) {
42853 return function (commands, tag) {
42855 angular.forEach(commands, function (command) {
42857 angular.forEach(command.tags, function (value) {
42862 result.push(command);
42868 })(filters || (filters = {}));
42872 var appName = 'zephyr';
42873 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
42874 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
42875 $urlRouterProvider.otherwise('/execution');
42876 $locationProvider.html5Mode({
42881 .state('execution', {
42883 templateUrl: 'templates/execution.html',
42884 controller: 'executionController',
42887 .state('workspace', {
42889 templateUrl: 'templates/workspace.html',
42890 controller: 'workspaceController',
42893 .state('history', {
42895 templateUrl: 'templates/history.html',
42896 controller: 'historyController',
42900 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
42901 app.zephyr.service('MyModal', app.services.MyModal);
42902 app.zephyr.service('WebSocket', app.services.WebSocket);
42903 app.zephyr.filter('Tag', filters.Tag);
42904 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
42905 app.zephyr.controller('previewController', app.controllers.Preview);
42906 app.zephyr.controller('executionController', app.controllers.Execution);
42907 app.zephyr.controller('workspaceController', app.controllers.Workspace);
42908 app.zephyr.controller('historyController', app.controllers.History);
42909 app.zephyr.controller('commandController', app.directives.CommandController);
42910 app.zephyr.controller('optionController', app.directives.OptionController);
42911 app.zephyr.controller('directoryController', app.directives.DirectoryController);
42912 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
42913 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
42914 app.zephyr.directive('command', app.directives.Command.Factory());
42915 app.zephyr.directive('option', app.directives.Option.Factory());
42916 app.zephyr.directive('directory', app.directives.Directory.Factory());
42917 })(app || (app = {}));
42922 /***/ function(module, exports) {
42927 (function (declares) {
42928 var CommandInfo = (function () {
42929 function CommandInfo(name) {
42932 return CommandInfo;
42934 declares.CommandInfo = CommandInfo;
42935 })(declares = app.declares || (app.declares = {}));
42936 })(app || (app = {}));
42940 (function (services) {
42941 var APIEndPoint = (function () {
42942 function APIEndPoint($resource, $http) {
42943 this.$resource = $resource;
42944 this.$http = $http;
42946 APIEndPoint.prototype.resource = function (endPoint, data) {
42947 var customAction = {
42953 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
42955 return this.$resource(endPoint, {}, { execute: execute });
42957 APIEndPoint.prototype.getOptionControlFile = function (command) {
42958 var endPoint = '/api/v1/optionControlFile/' + command;
42959 return this.resource(endPoint, {}).get();
42961 APIEndPoint.prototype.getFiles = function (fileId) {
42962 var endPoint = '/api/v1/workspace';
42964 endPoint += '/' + fileId;
42966 return this.resource(endPoint, {}).get();
42968 APIEndPoint.prototype.getDirectories = function () {
42969 var endPoint = '/api/v1/all/workspace/directory';
42970 return this.resource(endPoint, {}).get();
42972 APIEndPoint.prototype.getTags = function () {
42973 var endPoint = '/api/v1/tagList';
42974 return this.resource(endPoint, {}).get();
42976 APIEndPoint.prototype.getCommands = function () {
42977 var endPoint = '/api/v1/commandList';
42978 return this.resource(endPoint, {}).get();
42980 APIEndPoint.prototype.execute = function (data) {
42981 var endPoint = '/api/v1/execution';
42982 var fd = new FormData();
42983 fd.append('data', data);
42984 return this.$http.post(endPoint, fd, {
42985 headers: { 'Content-Type': undefined },
42986 transformRequest: angular.identity
42989 APIEndPoint.prototype.debug = function () {
42990 var endPoint = '/api/v1/debug';
42991 return this.$http.get(endPoint);
42993 APIEndPoint.prototype.help = function (command) {
42994 var endPoint = '/api/v1/help/' + command;
42995 return this.$http.get(endPoint);
42997 return APIEndPoint;
42999 services.APIEndPoint = APIEndPoint;
43000 })(services = app.services || (app.services = {}));
43001 })(app || (app = {}));
43005 (function (services) {
43006 var MyModal = (function () {
43007 function MyModal($uibModal) {
43008 this.$uibModal = $uibModal;
43009 this.modalOption = {
43016 MyModal.prototype.open = function (modalName) {
43017 if (modalName === 'SelectCommand') {
43018 this.modalOption.templateUrl = 'templates/select-command.html';
43019 this.modalOption.size = 'lg';
43021 return this.$uibModal.open(this.modalOption);
43023 MyModal.prototype.selectCommand = function () {
43024 this.modalOption.templateUrl = 'templates/select-command.html';
43025 this.modalOption.controller = 'selectCommandController';
43026 this.modalOption.controllerAs = 'c';
43027 this.modalOption.size = 'lg';
43028 return this.$uibModal.open(this.modalOption);
43030 MyModal.prototype.preview = function () {
43031 this.modalOption.templateUrl = 'templates/preview.html';
43032 this.modalOption.controller = 'previewController';
43033 this.modalOption.controllerAs = 'c';
43034 this.modalOption.size = 'lg';
43035 return this.$uibModal.open(this.modalOption);
43037 MyModal.$inject = ['$uibModal'];
43040 services.MyModal = MyModal;
43041 })(services = app.services || (app.services = {}));
43042 })(app || (app = {}));
43046 (function (services) {
43047 var WebSocket = (function () {
43048 function WebSocket($rootScope) {
43049 this.$rootScope = $rootScope;
43050 this.socket = io.connect();
43052 WebSocket.prototype.on = function (eventName, callback) {
43053 var socket = this.socket;
43054 var rootScope = this.$rootScope;
43055 socket.on(eventName, function () {
43056 var args = arguments;
43057 rootScope.$apply(function () {
43058 callback.apply(socket, args);
43062 WebSocket.prototype.emit = function (eventName, data, callback) {
43063 var socket = this.socket;
43064 var rootScope = this.$rootScope;
43065 this.socket.emit(eventName, data, function () {
43066 var args = arguments;
43067 rootScope.$apply(function () {
43069 callback.apply(socket, args);
43075 services.WebSocket = WebSocket;
43076 })(services = app.services || (app.services = {}));
43077 })(app || (app = {}));
43081 (function (directives) {
43082 var Command = (function () {
43083 function Command() {
43084 this.restrict = 'E';
43085 this.replace = true;
43087 this.controller = 'commandController';
43088 this.controllerAs = 'ctrl';
43089 this.bindToController = {
43095 this.templateUrl = 'templates/command.html';
43097 Command.Factory = function () {
43098 var directive = function () {
43099 return new Command();
43101 directive.$inject = [];
43106 directives.Command = Command;
43107 var CommandController = (function () {
43108 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
43109 this.APIEndPoint = APIEndPoint;
43110 this.$scope = $scope;
43111 this.MyModal = MyModal;
43112 this.WebSocket = WebSocket;
43113 var controller = this;
43115 .getOptionControlFile(this.name)
43117 .then(function (result) {
43118 controller.options = result.info;
43123 .then(function (result) {
43124 controller.dirs = result.info;
43126 this.heading = "[" + this.index + "]: dcdFilePrint";
43127 this.isOpen = true;
43128 this.$scope.$on('close', function () {
43129 controller.isOpen = false;
43131 this.WebSocket.on('console', function (msg) {
43132 var messages = msg.split('\n');
43133 if (messages[0].substr(0, 6) === 'Usage:') {
43134 controller.messages = msg.split('\n');
43138 CommandController.prototype.submit = function () {
43140 angular.forEach(this.options, function (option) {
43142 name: option.option,
43145 angular.forEach(option.arg, function (arg) {
43147 if (typeof arg.input === 'object') {
43148 obj.arguments.push(arg.input.name);
43151 obj.arguments.push(arg.input);
43155 if (obj.arguments.length > 0) {
43160 command: this.name,
43161 workspace: this.workspace.fileId,
43165 .execute(JSON.stringify(execObj))
43166 .then(function (result) {
43167 console.log(result);
43170 CommandController.prototype.removeMySelf = function (index) {
43171 this.remove()(index, this.list);
43173 CommandController.prototype.reloadFiles = function () {
43175 var fileId = this.workspace.fileId;
43179 .then(function (result) {
43180 var status = result.status;
43181 if (status === 'success') {
43182 _this.files = result.info;
43185 console.log(result.message);
43189 CommandController.prototype.debug = function () {
43192 .then(function (result) {
43193 console.log(result);
43196 CommandController.prototype.help = function () {
43199 .then(function (result) {
43200 console.log(result);
43203 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
43204 return CommandController;
43206 directives.CommandController = CommandController;
43207 })(directives = app.directives || (app.directives = {}));
43208 })(app || (app = {}));
43212 (function (directives) {
43213 var HeaderMenu = (function () {
43214 function HeaderMenu() {
43215 this.restrict = 'E';
43216 this.replace = true;
43217 this.templateUrl = 'templates/header-menu.html';
43218 this.controller = 'HeaderMenuController';
43219 this.controllerAs = 'hmc';
43222 HeaderMenu.Factory = function () {
43223 var directive = function () {
43224 return new HeaderMenu();
43230 directives.HeaderMenu = HeaderMenu;
43231 var HeaderMenuController = (function () {
43232 function HeaderMenuController($state) {
43233 this.$state = $state;
43234 this.isExecution = this.$state.current.name === 'execution';
43235 this.isWorkspace = this.$state.current.name === 'workspace';
43236 this.isHistory = this.$state.current.name === 'history';
43238 HeaderMenuController.prototype.transit = function (state) {
43239 this.$state.go(state);
43241 HeaderMenuController.$inject = ['$state'];
43242 return HeaderMenuController;
43244 directives.HeaderMenuController = HeaderMenuController;
43245 })(directives = app.directives || (app.directives = {}));
43246 })(app || (app = {}));
43250 (function (directives) {
43251 var Option = (function () {
43252 function Option() {
43253 this.restrict = 'E';
43254 this.replace = true;
43255 this.controller = 'optionController';
43256 this.bindToController = {
43261 this.templateUrl = 'templates/option.html';
43262 this.controllerAs = 'ctrl';
43264 Option.Factory = function () {
43265 var directive = function () {
43266 return new Option();
43268 directive.$inject = [];
43273 directives.Option = Option;
43274 var OptionController = (function () {
43275 function OptionController() {
43276 var controller = this;
43277 angular.forEach(controller.info.arg, function (arg) {
43278 if (arg.initialValue) {
43279 if (arg.formType === 'number') {
43280 arg.input = parseInt(arg.initialValue);
43283 arg.input = arg.initialValue;
43288 OptionController.$inject = [];
43289 return OptionController;
43291 directives.OptionController = OptionController;
43292 })(directives = app.directives || (app.directives = {}));
43293 })(app || (app = {}));
43297 (function (directives) {
43298 var Directory = (function () {
43299 function Directory() {
43300 this.restrict = 'E';
43301 this.replace = true;
43302 this.controller = 'directoryController';
43303 this.controllerAs = 'ctrl';
43304 this.bindToController = {
43310 this.templateUrl = 'templates/directory.html';
43312 Directory.Factory = function () {
43313 var directive = function () {
43314 return new Directory();
43320 directives.Directory = Directory;
43321 var DirectoryController = (function () {
43322 function DirectoryController(APIEndPoint, $scope) {
43323 this.APIEndPoint = APIEndPoint;
43324 this.$scope = $scope;
43325 var controller = this;
43327 .getFiles(this.info.fileId)
43329 .then(function (result) {
43330 if (result.status === 'success') {
43331 controller.files = result.info;
43332 angular.forEach(result.info, function (file) {
43333 if (file.fileType === '0') {
43335 if (controller.info.path === '/') {
43336 o.path = '/' + file.name;
43339 o.path = controller.info.path + '/' + file.name;
43341 controller.add()(o, controller.list);
43348 DirectoryController.$inject = ['APIEndPoint', '$scope'];
43349 return DirectoryController;
43351 directives.DirectoryController = DirectoryController;
43352 })(directives = app.directives || (app.directives = {}));
43353 })(app || (app = {}));
43357 (function (controllers) {
43358 var Execution = (function () {
43359 function Execution(MyModal, $scope) {
43360 this.MyModal = MyModal;
43361 this.$scope = $scope;
43362 this.commandInfoList = [];
43365 Execution.prototype.add = function () {
43366 this.$scope.$broadcast('close');
43367 var commandInfoList = this.commandInfoList;
43368 var commandInstance = this.MyModal.selectCommand();
43371 .then(function (command) {
43372 commandInfoList.push(new app.declares.CommandInfo(command));
43375 Execution.prototype.open = function () {
43376 var result = this.MyModal.open('SelectCommand');
43377 console.log(result);
43379 Execution.prototype.remove = function (index, list) {
43380 list.splice(index, 1);
43382 Execution.prototype.close = function () {
43383 console.log("close");
43385 Execution.$inject = ['MyModal', '$scope'];
43388 controllers.Execution = Execution;
43389 })(controllers = app.controllers || (app.controllers = {}));
43390 })(app || (app = {}));
43394 (function (controllers) {
43395 var Workspace = (function () {
43396 function Workspace($scope, APIEndPoint, MyModal) {
43397 this.$scope = $scope;
43398 this.APIEndPoint = APIEndPoint;
43399 this.MyModal = MyModal;
43400 this.directoryList = [];
43401 var controller = this;
43402 var directoryList = this.directoryList;
43404 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
43412 directoryList.push(o);
43414 Workspace.prototype.addDirectory = function (info, directoryList) {
43415 directoryList.push(info);
43417 Workspace.prototype.debug = function () {
43418 this.MyModal.preview();
43420 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
43423 controllers.Workspace = Workspace;
43424 })(controllers = app.controllers || (app.controllers = {}));
43425 })(app || (app = {}));
43429 (function (controllers) {
43430 var History = (function () {
43431 function History($scope) {
43432 this.page = "History";
43434 History.$inject = ['$scope'];
43437 controllers.History = History;
43438 })(controllers = app.controllers || (app.controllers = {}));
43439 })(app || (app = {}));
43443 (function (controllers) {
43444 var SelectCommand = (function () {
43445 function SelectCommand($scope, APIEndPoint, $modalInstance) {
43446 this.APIEndPoint = APIEndPoint;
43447 this.$modalInstance = $modalInstance;
43448 var controller = this;
43451 .$promise.then(function (result) {
43452 controller.tags = result.info;
43456 .$promise.then(function (result) {
43457 controller.commands = result.info;
43459 this.currentTag = 'all';
43461 SelectCommand.prototype.changeTag = function (tag) {
43462 this.currentTag = tag;
43464 SelectCommand.prototype.selectCommand = function (command) {
43465 this.$modalInstance.close(command);
43467 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
43468 return SelectCommand;
43470 controllers.SelectCommand = SelectCommand;
43471 })(controllers = app.controllers || (app.controllers = {}));
43472 })(app || (app = {}));
43476 (function (controllers) {
43477 var Preview = (function () {
43478 function Preview($scope, APIEndPoint, $modalInstance) {
43479 this.APIEndPoint = APIEndPoint;
43480 this.$modalInstance = $modalInstance;
43481 var controller = this;
43482 console.log('preview');
43484 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
43487 controllers.Preview = Preview;
43488 })(controllers = app.controllers || (app.controllers = {}));
43489 })(app || (app = {}));
43491 (function (filters) {
43493 return function (commands, tag) {
43495 angular.forEach(commands, function (command) {
43497 angular.forEach(command.tags, function (value) {
43502 result.push(command);
43508 })(filters || (filters = {}));
43512 var appName = 'zephyr';
43513 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
43514 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
43515 $urlRouterProvider.otherwise('/execution');
43516 $locationProvider.html5Mode({
43521 .state('execution', {
43523 templateUrl: 'templates/execution.html',
43524 controller: 'executionController',
43527 .state('workspace', {
43529 templateUrl: 'templates/workspace.html',
43530 controller: 'workspaceController',
43533 .state('history', {
43535 templateUrl: 'templates/history.html',
43536 controller: 'historyController',
43540 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
43541 app.zephyr.service('MyModal', app.services.MyModal);
43542 app.zephyr.service('WebSocket', app.services.WebSocket);
43543 app.zephyr.filter('Tag', filters.Tag);
43544 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
43545 app.zephyr.controller('previewController', app.controllers.Preview);
43546 app.zephyr.controller('executionController', app.controllers.Execution);
43547 app.zephyr.controller('workspaceController', app.controllers.Workspace);
43548 app.zephyr.controller('historyController', app.controllers.History);
43549 app.zephyr.controller('commandController', app.directives.CommandController);
43550 app.zephyr.controller('optionController', app.directives.OptionController);
43551 app.zephyr.controller('directoryController', app.directives.DirectoryController);
43552 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
43553 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
43554 app.zephyr.directive('command', app.directives.Command.Factory());
43555 app.zephyr.directive('option', app.directives.Option.Factory());
43556 app.zephyr.directive('directory', app.directives.Directory.Factory());
43557 })(app || (app = {}));
43562 /***/ function(module, exports) {
43567 (function (declares) {
43568 var CommandInfo = (function () {
43569 function CommandInfo(name) {
43572 return CommandInfo;
43574 declares.CommandInfo = CommandInfo;
43575 })(declares = app.declares || (app.declares = {}));
43576 })(app || (app = {}));
43580 (function (services) {
43581 var APIEndPoint = (function () {
43582 function APIEndPoint($resource, $http) {
43583 this.$resource = $resource;
43584 this.$http = $http;
43586 APIEndPoint.prototype.resource = function (endPoint, data) {
43587 var customAction = {
43593 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
43595 return this.$resource(endPoint, {}, { execute: execute });
43597 APIEndPoint.prototype.getOptionControlFile = function (command) {
43598 var endPoint = '/api/v1/optionControlFile/' + command;
43599 return this.resource(endPoint, {}).get();
43601 APIEndPoint.prototype.getFiles = function (fileId) {
43602 var endPoint = '/api/v1/workspace';
43604 endPoint += '/' + fileId;
43606 return this.resource(endPoint, {}).get();
43608 APIEndPoint.prototype.getDirectories = function () {
43609 var endPoint = '/api/v1/all/workspace/directory';
43610 return this.resource(endPoint, {}).get();
43612 APIEndPoint.prototype.getTags = function () {
43613 var endPoint = '/api/v1/tagList';
43614 return this.resource(endPoint, {}).get();
43616 APIEndPoint.prototype.getCommands = function () {
43617 var endPoint = '/api/v1/commandList';
43618 return this.resource(endPoint, {}).get();
43620 APIEndPoint.prototype.execute = function (data) {
43621 var endPoint = '/api/v1/execution';
43622 var fd = new FormData();
43623 fd.append('data', data);
43624 return this.$http.post(endPoint, fd, {
43625 headers: { 'Content-Type': undefined },
43626 transformRequest: angular.identity
43629 APIEndPoint.prototype.debug = function () {
43630 var endPoint = '/api/v1/debug';
43631 return this.$http.get(endPoint);
43633 APIEndPoint.prototype.help = function (command) {
43634 var endPoint = '/api/v1/help/' + command;
43635 return this.$http.get(endPoint);
43637 return APIEndPoint;
43639 services.APIEndPoint = APIEndPoint;
43640 })(services = app.services || (app.services = {}));
43641 })(app || (app = {}));
43645 (function (services) {
43646 var MyModal = (function () {
43647 function MyModal($uibModal) {
43648 this.$uibModal = $uibModal;
43649 this.modalOption = {
43656 MyModal.prototype.open = function (modalName) {
43657 if (modalName === 'SelectCommand') {
43658 this.modalOption.templateUrl = 'templates/select-command.html';
43659 this.modalOption.size = 'lg';
43661 return this.$uibModal.open(this.modalOption);
43663 MyModal.prototype.selectCommand = function () {
43664 this.modalOption.templateUrl = 'templates/select-command.html';
43665 this.modalOption.controller = 'selectCommandController';
43666 this.modalOption.controllerAs = 'c';
43667 this.modalOption.size = 'lg';
43668 return this.$uibModal.open(this.modalOption);
43670 MyModal.prototype.preview = function () {
43671 this.modalOption.templateUrl = 'templates/preview.html';
43672 this.modalOption.controller = 'previewController';
43673 this.modalOption.controllerAs = 'c';
43674 this.modalOption.size = 'lg';
43675 return this.$uibModal.open(this.modalOption);
43677 MyModal.$inject = ['$uibModal'];
43680 services.MyModal = MyModal;
43681 })(services = app.services || (app.services = {}));
43682 })(app || (app = {}));
43686 (function (services) {
43687 var WebSocket = (function () {
43688 function WebSocket($rootScope) {
43689 this.$rootScope = $rootScope;
43690 this.socket = io.connect();
43692 WebSocket.prototype.on = function (eventName, callback) {
43693 var socket = this.socket;
43694 var rootScope = this.$rootScope;
43695 socket.on(eventName, function () {
43696 var args = arguments;
43697 rootScope.$apply(function () {
43698 callback.apply(socket, args);
43702 WebSocket.prototype.emit = function (eventName, data, callback) {
43703 var socket = this.socket;
43704 var rootScope = this.$rootScope;
43705 this.socket.emit(eventName, data, function () {
43706 var args = arguments;
43707 rootScope.$apply(function () {
43709 callback.apply(socket, args);
43715 services.WebSocket = WebSocket;
43716 })(services = app.services || (app.services = {}));
43717 })(app || (app = {}));
43721 (function (directives) {
43722 var Command = (function () {
43723 function Command() {
43724 this.restrict = 'E';
43725 this.replace = true;
43727 this.controller = 'commandController';
43728 this.controllerAs = 'ctrl';
43729 this.bindToController = {
43735 this.templateUrl = 'templates/command.html';
43737 Command.Factory = function () {
43738 var directive = function () {
43739 return new Command();
43741 directive.$inject = [];
43746 directives.Command = Command;
43747 var CommandController = (function () {
43748 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
43749 this.APIEndPoint = APIEndPoint;
43750 this.$scope = $scope;
43751 this.MyModal = MyModal;
43752 this.WebSocket = WebSocket;
43753 var controller = this;
43755 .getOptionControlFile(this.name)
43757 .then(function (result) {
43758 controller.options = result.info;
43763 .then(function (result) {
43764 controller.dirs = result.info;
43766 this.heading = "[" + this.index + "]: dcdFilePrint";
43767 this.isOpen = true;
43768 this.$scope.$on('close', function () {
43769 controller.isOpen = false;
43771 this.WebSocket.on('console', function (msg) {
43772 var messages = msg.split('\n');
43773 if (messages[0].substr(0, 6) === 'Usage:') {
43774 controller.messages = msg.split('\n');
43778 CommandController.prototype.submit = function () {
43780 angular.forEach(this.options, function (option) {
43782 name: option.option,
43785 angular.forEach(option.arg, function (arg) {
43787 if (typeof arg.input === 'object') {
43788 obj.arguments.push(arg.input.name);
43791 obj.arguments.push(arg.input);
43795 if (obj.arguments.length > 0) {
43800 command: this.name,
43801 workspace: this.workspace.fileId,
43805 .execute(JSON.stringify(execObj))
43806 .then(function (result) {
43807 console.log(result);
43810 CommandController.prototype.removeMySelf = function (index) {
43811 this.remove()(index, this.list);
43813 CommandController.prototype.reloadFiles = function () {
43815 var fileId = this.workspace.fileId;
43819 .then(function (result) {
43820 var status = result.status;
43821 if (status === 'success') {
43822 _this.files = result.info;
43825 console.log(result.message);
43829 CommandController.prototype.debug = function () {
43832 .then(function (result) {
43833 console.log(result);
43836 CommandController.prototype.help = function () {
43839 .then(function (result) {
43840 console.log(result);
43843 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
43844 return CommandController;
43846 directives.CommandController = CommandController;
43847 })(directives = app.directives || (app.directives = {}));
43848 })(app || (app = {}));
43852 (function (directives) {
43853 var HeaderMenu = (function () {
43854 function HeaderMenu() {
43855 this.restrict = 'E';
43856 this.replace = true;
43857 this.templateUrl = 'templates/header-menu.html';
43858 this.controller = 'HeaderMenuController';
43859 this.controllerAs = 'hmc';
43862 HeaderMenu.Factory = function () {
43863 var directive = function () {
43864 return new HeaderMenu();
43870 directives.HeaderMenu = HeaderMenu;
43871 var HeaderMenuController = (function () {
43872 function HeaderMenuController($state) {
43873 this.$state = $state;
43874 this.isExecution = this.$state.current.name === 'execution';
43875 this.isWorkspace = this.$state.current.name === 'workspace';
43876 this.isHistory = this.$state.current.name === 'history';
43878 HeaderMenuController.prototype.transit = function (state) {
43879 this.$state.go(state);
43881 HeaderMenuController.$inject = ['$state'];
43882 return HeaderMenuController;
43884 directives.HeaderMenuController = HeaderMenuController;
43885 })(directives = app.directives || (app.directives = {}));
43886 })(app || (app = {}));
43890 (function (directives) {
43891 var Option = (function () {
43892 function Option() {
43893 this.restrict = 'E';
43894 this.replace = true;
43895 this.controller = 'optionController';
43896 this.bindToController = {
43901 this.templateUrl = 'templates/option.html';
43902 this.controllerAs = 'ctrl';
43904 Option.Factory = function () {
43905 var directive = function () {
43906 return new Option();
43908 directive.$inject = [];
43913 directives.Option = Option;
43914 var OptionController = (function () {
43915 function OptionController() {
43916 var controller = this;
43917 angular.forEach(controller.info.arg, function (arg) {
43918 if (arg.initialValue) {
43919 if (arg.formType === 'number') {
43920 arg.input = parseInt(arg.initialValue);
43923 arg.input = arg.initialValue;
43928 OptionController.$inject = [];
43929 return OptionController;
43931 directives.OptionController = OptionController;
43932 })(directives = app.directives || (app.directives = {}));
43933 })(app || (app = {}));
43937 (function (directives) {
43938 var Directory = (function () {
43939 function Directory() {
43940 this.restrict = 'E';
43941 this.replace = true;
43942 this.controller = 'directoryController';
43943 this.controllerAs = 'ctrl';
43944 this.bindToController = {
43950 this.templateUrl = 'templates/directory.html';
43952 Directory.Factory = function () {
43953 var directive = function () {
43954 return new Directory();
43960 directives.Directory = Directory;
43961 var DirectoryController = (function () {
43962 function DirectoryController(APIEndPoint, $scope) {
43963 this.APIEndPoint = APIEndPoint;
43964 this.$scope = $scope;
43965 var controller = this;
43967 .getFiles(this.info.fileId)
43969 .then(function (result) {
43970 if (result.status === 'success') {
43971 controller.files = result.info;
43972 angular.forEach(result.info, function (file) {
43973 if (file.fileType === '0') {
43975 if (controller.info.path === '/') {
43976 o.path = '/' + file.name;
43979 o.path = controller.info.path + '/' + file.name;
43981 controller.add()(o, controller.list);
43988 DirectoryController.$inject = ['APIEndPoint', '$scope'];
43989 return DirectoryController;
43991 directives.DirectoryController = DirectoryController;
43992 })(directives = app.directives || (app.directives = {}));
43993 })(app || (app = {}));
43997 (function (controllers) {
43998 var Execution = (function () {
43999 function Execution(MyModal, $scope) {
44000 this.MyModal = MyModal;
44001 this.$scope = $scope;
44002 this.commandInfoList = [];
44005 Execution.prototype.add = function () {
44006 this.$scope.$broadcast('close');
44007 var commandInfoList = this.commandInfoList;
44008 var commandInstance = this.MyModal.selectCommand();
44011 .then(function (command) {
44012 commandInfoList.push(new app.declares.CommandInfo(command));
44015 Execution.prototype.open = function () {
44016 var result = this.MyModal.open('SelectCommand');
44017 console.log(result);
44019 Execution.prototype.remove = function (index, list) {
44020 list.splice(index, 1);
44022 Execution.prototype.close = function () {
44023 console.log("close");
44025 Execution.$inject = ['MyModal', '$scope'];
44028 controllers.Execution = Execution;
44029 })(controllers = app.controllers || (app.controllers = {}));
44030 })(app || (app = {}));
44034 (function (controllers) {
44035 var Workspace = (function () {
44036 function Workspace($scope, APIEndPoint, MyModal) {
44037 this.$scope = $scope;
44038 this.APIEndPoint = APIEndPoint;
44039 this.MyModal = MyModal;
44040 this.directoryList = [];
44041 var controller = this;
44042 var directoryList = this.directoryList;
44044 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
44052 directoryList.push(o);
44054 Workspace.prototype.addDirectory = function (info, directoryList) {
44055 directoryList.push(info);
44057 Workspace.prototype.debug = function () {
44058 this.MyModal.preview();
44060 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
44063 controllers.Workspace = Workspace;
44064 })(controllers = app.controllers || (app.controllers = {}));
44065 })(app || (app = {}));
44069 (function (controllers) {
44070 var History = (function () {
44071 function History($scope) {
44072 this.page = "History";
44074 History.$inject = ['$scope'];
44077 controllers.History = History;
44078 })(controllers = app.controllers || (app.controllers = {}));
44079 })(app || (app = {}));
44083 (function (controllers) {
44084 var SelectCommand = (function () {
44085 function SelectCommand($scope, APIEndPoint, $modalInstance) {
44086 this.APIEndPoint = APIEndPoint;
44087 this.$modalInstance = $modalInstance;
44088 var controller = this;
44091 .$promise.then(function (result) {
44092 controller.tags = result.info;
44096 .$promise.then(function (result) {
44097 controller.commands = result.info;
44099 this.currentTag = 'all';
44101 SelectCommand.prototype.changeTag = function (tag) {
44102 this.currentTag = tag;
44104 SelectCommand.prototype.selectCommand = function (command) {
44105 this.$modalInstance.close(command);
44107 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
44108 return SelectCommand;
44110 controllers.SelectCommand = SelectCommand;
44111 })(controllers = app.controllers || (app.controllers = {}));
44112 })(app || (app = {}));
44116 (function (controllers) {
44117 var Preview = (function () {
44118 function Preview($scope, APIEndPoint, $modalInstance) {
44119 this.APIEndPoint = APIEndPoint;
44120 this.$modalInstance = $modalInstance;
44121 var controller = this;
44122 console.log('preview');
44124 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
44127 controllers.Preview = Preview;
44128 })(controllers = app.controllers || (app.controllers = {}));
44129 })(app || (app = {}));
44131 (function (filters) {
44133 return function (commands, tag) {
44135 angular.forEach(commands, function (command) {
44137 angular.forEach(command.tags, function (value) {
44142 result.push(command);
44148 })(filters || (filters = {}));
44152 var appName = 'zephyr';
44153 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
44154 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
44155 $urlRouterProvider.otherwise('/execution');
44156 $locationProvider.html5Mode({
44161 .state('execution', {
44163 templateUrl: 'templates/execution.html',
44164 controller: 'executionController',
44167 .state('workspace', {
44169 templateUrl: 'templates/workspace.html',
44170 controller: 'workspaceController',
44173 .state('history', {
44175 templateUrl: 'templates/history.html',
44176 controller: 'historyController',
44180 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
44181 app.zephyr.service('MyModal', app.services.MyModal);
44182 app.zephyr.service('WebSocket', app.services.WebSocket);
44183 app.zephyr.filter('Tag', filters.Tag);
44184 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
44185 app.zephyr.controller('previewController', app.controllers.Preview);
44186 app.zephyr.controller('executionController', app.controllers.Execution);
44187 app.zephyr.controller('workspaceController', app.controllers.Workspace);
44188 app.zephyr.controller('historyController', app.controllers.History);
44189 app.zephyr.controller('commandController', app.directives.CommandController);
44190 app.zephyr.controller('optionController', app.directives.OptionController);
44191 app.zephyr.controller('directoryController', app.directives.DirectoryController);
44192 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
44193 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
44194 app.zephyr.directive('command', app.directives.Command.Factory());
44195 app.zephyr.directive('option', app.directives.Option.Factory());
44196 app.zephyr.directive('directory', app.directives.Directory.Factory());
44197 })(app || (app = {}));
44202 /***/ function(module, exports) {
44207 (function (declares) {
44208 var CommandInfo = (function () {
44209 function CommandInfo(name) {
44212 return CommandInfo;
44214 declares.CommandInfo = CommandInfo;
44215 })(declares = app.declares || (app.declares = {}));
44216 })(app || (app = {}));
44220 (function (services) {
44221 var APIEndPoint = (function () {
44222 function APIEndPoint($resource, $http) {
44223 this.$resource = $resource;
44224 this.$http = $http;
44226 APIEndPoint.prototype.resource = function (endPoint, data) {
44227 var customAction = {
44233 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
44235 return this.$resource(endPoint, {}, { execute: execute });
44237 APIEndPoint.prototype.getOptionControlFile = function (command) {
44238 var endPoint = '/api/v1/optionControlFile/' + command;
44239 return this.resource(endPoint, {}).get();
44241 APIEndPoint.prototype.getFiles = function (fileId) {
44242 var endPoint = '/api/v1/workspace';
44244 endPoint += '/' + fileId;
44246 return this.resource(endPoint, {}).get();
44248 APIEndPoint.prototype.getDirectories = function () {
44249 var endPoint = '/api/v1/all/workspace/directory';
44250 return this.resource(endPoint, {}).get();
44252 APIEndPoint.prototype.getTags = function () {
44253 var endPoint = '/api/v1/tagList';
44254 return this.resource(endPoint, {}).get();
44256 APIEndPoint.prototype.getCommands = function () {
44257 var endPoint = '/api/v1/commandList';
44258 return this.resource(endPoint, {}).get();
44260 APIEndPoint.prototype.execute = function (data) {
44261 var endPoint = '/api/v1/execution';
44262 var fd = new FormData();
44263 fd.append('data', data);
44264 return this.$http.post(endPoint, fd, {
44265 headers: { 'Content-Type': undefined },
44266 transformRequest: angular.identity
44269 APIEndPoint.prototype.debug = function () {
44270 var endPoint = '/api/v1/debug';
44271 return this.$http.get(endPoint);
44273 APIEndPoint.prototype.help = function (command) {
44274 var endPoint = '/api/v1/help/' + command;
44275 return this.$http.get(endPoint);
44277 return APIEndPoint;
44279 services.APIEndPoint = APIEndPoint;
44280 })(services = app.services || (app.services = {}));
44281 })(app || (app = {}));
44285 (function (services) {
44286 var MyModal = (function () {
44287 function MyModal($uibModal) {
44288 this.$uibModal = $uibModal;
44289 this.modalOption = {
44296 MyModal.prototype.open = function (modalName) {
44297 if (modalName === 'SelectCommand') {
44298 this.modalOption.templateUrl = 'templates/select-command.html';
44299 this.modalOption.size = 'lg';
44301 return this.$uibModal.open(this.modalOption);
44303 MyModal.prototype.selectCommand = function () {
44304 this.modalOption.templateUrl = 'templates/select-command.html';
44305 this.modalOption.controller = 'selectCommandController';
44306 this.modalOption.controllerAs = 'c';
44307 this.modalOption.size = 'lg';
44308 return this.$uibModal.open(this.modalOption);
44310 MyModal.prototype.preview = function () {
44311 this.modalOption.templateUrl = 'templates/preview.html';
44312 this.modalOption.controller = 'previewController';
44313 this.modalOption.controllerAs = 'c';
44314 this.modalOption.size = 'lg';
44315 return this.$uibModal.open(this.modalOption);
44317 MyModal.$inject = ['$uibModal'];
44320 services.MyModal = MyModal;
44321 })(services = app.services || (app.services = {}));
44322 })(app || (app = {}));
44326 (function (services) {
44327 var WebSocket = (function () {
44328 function WebSocket($rootScope) {
44329 this.$rootScope = $rootScope;
44330 this.socket = io.connect();
44332 WebSocket.prototype.on = function (eventName, callback) {
44333 var socket = this.socket;
44334 var rootScope = this.$rootScope;
44335 socket.on(eventName, function () {
44336 var args = arguments;
44337 rootScope.$apply(function () {
44338 callback.apply(socket, args);
44342 WebSocket.prototype.emit = function (eventName, data, callback) {
44343 var socket = this.socket;
44344 var rootScope = this.$rootScope;
44345 this.socket.emit(eventName, data, function () {
44346 var args = arguments;
44347 rootScope.$apply(function () {
44349 callback.apply(socket, args);
44355 services.WebSocket = WebSocket;
44356 })(services = app.services || (app.services = {}));
44357 })(app || (app = {}));
44361 (function (directives) {
44362 var Command = (function () {
44363 function Command() {
44364 this.restrict = 'E';
44365 this.replace = true;
44367 this.controller = 'commandController';
44368 this.controllerAs = 'ctrl';
44369 this.bindToController = {
44375 this.templateUrl = 'templates/command.html';
44377 Command.Factory = function () {
44378 var directive = function () {
44379 return new Command();
44381 directive.$inject = [];
44386 directives.Command = Command;
44387 var CommandController = (function () {
44388 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
44389 this.APIEndPoint = APIEndPoint;
44390 this.$scope = $scope;
44391 this.MyModal = MyModal;
44392 this.WebSocket = WebSocket;
44393 var controller = this;
44395 .getOptionControlFile(this.name)
44397 .then(function (result) {
44398 controller.options = result.info;
44403 .then(function (result) {
44404 controller.dirs = result.info;
44406 this.heading = "[" + this.index + "]: dcdFilePrint";
44407 this.isOpen = true;
44408 this.$scope.$on('close', function () {
44409 controller.isOpen = false;
44411 this.WebSocket.on('console', function (msg) {
44412 var messages = msg.split('\n');
44413 if (messages[0].substr(0, 6) === 'Usage:') {
44414 controller.messages = msg.split('\n');
44418 CommandController.prototype.submit = function () {
44420 angular.forEach(this.options, function (option) {
44422 name: option.option,
44425 angular.forEach(option.arg, function (arg) {
44427 if (typeof arg.input === 'object') {
44428 obj.arguments.push(arg.input.name);
44431 obj.arguments.push(arg.input);
44435 if (obj.arguments.length > 0) {
44440 command: this.name,
44441 workspace: this.workspace.fileId,
44445 .execute(JSON.stringify(execObj))
44446 .then(function (result) {
44447 console.log(result);
44450 CommandController.prototype.removeMySelf = function (index) {
44451 this.remove()(index, this.list);
44453 CommandController.prototype.reloadFiles = function () {
44455 var fileId = this.workspace.fileId;
44459 .then(function (result) {
44460 var status = result.status;
44461 if (status === 'success') {
44462 _this.files = result.info;
44465 console.log(result.message);
44469 CommandController.prototype.debug = function () {
44472 .then(function (result) {
44473 console.log(result);
44476 CommandController.prototype.help = function () {
44479 .then(function (result) {
44480 console.log(result);
44483 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
44484 return CommandController;
44486 directives.CommandController = CommandController;
44487 })(directives = app.directives || (app.directives = {}));
44488 })(app || (app = {}));
44492 (function (directives) {
44493 var HeaderMenu = (function () {
44494 function HeaderMenu() {
44495 this.restrict = 'E';
44496 this.replace = true;
44497 this.templateUrl = 'templates/header-menu.html';
44498 this.controller = 'HeaderMenuController';
44499 this.controllerAs = 'hmc';
44502 HeaderMenu.Factory = function () {
44503 var directive = function () {
44504 return new HeaderMenu();
44510 directives.HeaderMenu = HeaderMenu;
44511 var HeaderMenuController = (function () {
44512 function HeaderMenuController($state) {
44513 this.$state = $state;
44514 this.isExecution = this.$state.current.name === 'execution';
44515 this.isWorkspace = this.$state.current.name === 'workspace';
44516 this.isHistory = this.$state.current.name === 'history';
44518 HeaderMenuController.prototype.transit = function (state) {
44519 this.$state.go(state);
44521 HeaderMenuController.$inject = ['$state'];
44522 return HeaderMenuController;
44524 directives.HeaderMenuController = HeaderMenuController;
44525 })(directives = app.directives || (app.directives = {}));
44526 })(app || (app = {}));
44530 (function (directives) {
44531 var Option = (function () {
44532 function Option() {
44533 this.restrict = 'E';
44534 this.replace = true;
44535 this.controller = 'optionController';
44536 this.bindToController = {
44541 this.templateUrl = 'templates/option.html';
44542 this.controllerAs = 'ctrl';
44544 Option.Factory = function () {
44545 var directive = function () {
44546 return new Option();
44548 directive.$inject = [];
44553 directives.Option = Option;
44554 var OptionController = (function () {
44555 function OptionController() {
44556 var controller = this;
44557 angular.forEach(controller.info.arg, function (arg) {
44558 if (arg.initialValue) {
44559 if (arg.formType === 'number') {
44560 arg.input = parseInt(arg.initialValue);
44563 arg.input = arg.initialValue;
44568 OptionController.$inject = [];
44569 return OptionController;
44571 directives.OptionController = OptionController;
44572 })(directives = app.directives || (app.directives = {}));
44573 })(app || (app = {}));
44577 (function (directives) {
44578 var Directory = (function () {
44579 function Directory() {
44580 this.restrict = 'E';
44581 this.replace = true;
44582 this.controller = 'directoryController';
44583 this.controllerAs = 'ctrl';
44584 this.bindToController = {
44590 this.templateUrl = 'templates/directory.html';
44592 Directory.Factory = function () {
44593 var directive = function () {
44594 return new Directory();
44600 directives.Directory = Directory;
44601 var DirectoryController = (function () {
44602 function DirectoryController(APIEndPoint, $scope) {
44603 this.APIEndPoint = APIEndPoint;
44604 this.$scope = $scope;
44605 var controller = this;
44607 .getFiles(this.info.fileId)
44609 .then(function (result) {
44610 if (result.status === 'success') {
44611 controller.files = result.info;
44612 angular.forEach(result.info, function (file) {
44613 if (file.fileType === '0') {
44615 if (controller.info.path === '/') {
44616 o.path = '/' + file.name;
44619 o.path = controller.info.path + '/' + file.name;
44621 controller.add()(o, controller.list);
44628 DirectoryController.$inject = ['APIEndPoint', '$scope'];
44629 return DirectoryController;
44631 directives.DirectoryController = DirectoryController;
44632 })(directives = app.directives || (app.directives = {}));
44633 })(app || (app = {}));
44637 (function (controllers) {
44638 var Execution = (function () {
44639 function Execution(MyModal, $scope) {
44640 this.MyModal = MyModal;
44641 this.$scope = $scope;
44642 this.commandInfoList = [];
44645 Execution.prototype.add = function () {
44646 this.$scope.$broadcast('close');
44647 var commandInfoList = this.commandInfoList;
44648 var commandInstance = this.MyModal.selectCommand();
44651 .then(function (command) {
44652 commandInfoList.push(new app.declares.CommandInfo(command));
44655 Execution.prototype.open = function () {
44656 var result = this.MyModal.open('SelectCommand');
44657 console.log(result);
44659 Execution.prototype.remove = function (index, list) {
44660 list.splice(index, 1);
44662 Execution.prototype.close = function () {
44663 console.log("close");
44665 Execution.$inject = ['MyModal', '$scope'];
44668 controllers.Execution = Execution;
44669 })(controllers = app.controllers || (app.controllers = {}));
44670 })(app || (app = {}));
44674 (function (controllers) {
44675 var Workspace = (function () {
44676 function Workspace($scope, APIEndPoint, MyModal) {
44677 this.$scope = $scope;
44678 this.APIEndPoint = APIEndPoint;
44679 this.MyModal = MyModal;
44680 this.directoryList = [];
44681 var controller = this;
44682 var directoryList = this.directoryList;
44684 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
44692 directoryList.push(o);
44694 Workspace.prototype.addDirectory = function (info, directoryList) {
44695 directoryList.push(info);
44697 Workspace.prototype.debug = function () {
44698 this.MyModal.preview();
44700 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
44703 controllers.Workspace = Workspace;
44704 })(controllers = app.controllers || (app.controllers = {}));
44705 })(app || (app = {}));
44709 (function (controllers) {
44710 var History = (function () {
44711 function History($scope) {
44712 this.page = "History";
44714 History.$inject = ['$scope'];
44717 controllers.History = History;
44718 })(controllers = app.controllers || (app.controllers = {}));
44719 })(app || (app = {}));
44723 (function (controllers) {
44724 var SelectCommand = (function () {
44725 function SelectCommand($scope, APIEndPoint, $modalInstance) {
44726 this.APIEndPoint = APIEndPoint;
44727 this.$modalInstance = $modalInstance;
44728 var controller = this;
44731 .$promise.then(function (result) {
44732 controller.tags = result.info;
44736 .$promise.then(function (result) {
44737 controller.commands = result.info;
44739 this.currentTag = 'all';
44741 SelectCommand.prototype.changeTag = function (tag) {
44742 this.currentTag = tag;
44744 SelectCommand.prototype.selectCommand = function (command) {
44745 this.$modalInstance.close(command);
44747 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
44748 return SelectCommand;
44750 controllers.SelectCommand = SelectCommand;
44751 })(controllers = app.controllers || (app.controllers = {}));
44752 })(app || (app = {}));
44756 (function (controllers) {
44757 var Preview = (function () {
44758 function Preview($scope, APIEndPoint, $modalInstance) {
44759 this.APIEndPoint = APIEndPoint;
44760 this.$modalInstance = $modalInstance;
44761 var controller = this;
44762 console.log('preview');
44764 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
44767 controllers.Preview = Preview;
44768 })(controllers = app.controllers || (app.controllers = {}));
44769 })(app || (app = {}));
44771 (function (filters) {
44773 return function (commands, tag) {
44775 angular.forEach(commands, function (command) {
44777 angular.forEach(command.tags, function (value) {
44782 result.push(command);
44788 })(filters || (filters = {}));
44792 var appName = 'zephyr';
44793 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
44794 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
44795 $urlRouterProvider.otherwise('/execution');
44796 $locationProvider.html5Mode({
44801 .state('execution', {
44803 templateUrl: 'templates/execution.html',
44804 controller: 'executionController',
44807 .state('workspace', {
44809 templateUrl: 'templates/workspace.html',
44810 controller: 'workspaceController',
44813 .state('history', {
44815 templateUrl: 'templates/history.html',
44816 controller: 'historyController',
44820 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
44821 app.zephyr.service('MyModal', app.services.MyModal);
44822 app.zephyr.service('WebSocket', app.services.WebSocket);
44823 app.zephyr.filter('Tag', filters.Tag);
44824 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
44825 app.zephyr.controller('previewController', app.controllers.Preview);
44826 app.zephyr.controller('executionController', app.controllers.Execution);
44827 app.zephyr.controller('workspaceController', app.controllers.Workspace);
44828 app.zephyr.controller('historyController', app.controllers.History);
44829 app.zephyr.controller('commandController', app.directives.CommandController);
44830 app.zephyr.controller('optionController', app.directives.OptionController);
44831 app.zephyr.controller('directoryController', app.directives.DirectoryController);
44832 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
44833 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
44834 app.zephyr.directive('command', app.directives.Command.Factory());
44835 app.zephyr.directive('option', app.directives.Option.Factory());
44836 app.zephyr.directive('directory', app.directives.Directory.Factory());
44837 })(app || (app = {}));
44842 /***/ function(module, exports) {
44847 (function (declares) {
44848 var CommandInfo = (function () {
44849 function CommandInfo(name) {
44852 return CommandInfo;
44854 declares.CommandInfo = CommandInfo;
44855 })(declares = app.declares || (app.declares = {}));
44856 })(app || (app = {}));
44860 (function (services) {
44861 var APIEndPoint = (function () {
44862 function APIEndPoint($resource, $http) {
44863 this.$resource = $resource;
44864 this.$http = $http;
44866 APIEndPoint.prototype.resource = function (endPoint, data) {
44867 var customAction = {
44873 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
44875 return this.$resource(endPoint, {}, { execute: execute });
44877 APIEndPoint.prototype.getOptionControlFile = function (command) {
44878 var endPoint = '/api/v1/optionControlFile/' + command;
44879 return this.resource(endPoint, {}).get();
44881 APIEndPoint.prototype.getFiles = function (fileId) {
44882 var endPoint = '/api/v1/workspace';
44884 endPoint += '/' + fileId;
44886 return this.resource(endPoint, {}).get();
44888 APIEndPoint.prototype.getDirectories = function () {
44889 var endPoint = '/api/v1/all/workspace/directory';
44890 return this.resource(endPoint, {}).get();
44892 APIEndPoint.prototype.getTags = function () {
44893 var endPoint = '/api/v1/tagList';
44894 return this.resource(endPoint, {}).get();
44896 APIEndPoint.prototype.getCommands = function () {
44897 var endPoint = '/api/v1/commandList';
44898 return this.resource(endPoint, {}).get();
44900 APIEndPoint.prototype.execute = function (data) {
44901 var endPoint = '/api/v1/execution';
44902 var fd = new FormData();
44903 fd.append('data', data);
44904 return this.$http.post(endPoint, fd, {
44905 headers: { 'Content-Type': undefined },
44906 transformRequest: angular.identity
44909 APIEndPoint.prototype.debug = function () {
44910 var endPoint = '/api/v1/debug';
44911 return this.$http.get(endPoint);
44913 APIEndPoint.prototype.help = function (command) {
44914 var endPoint = '/api/v1/help/' + command;
44915 return this.$http.get(endPoint);
44917 return APIEndPoint;
44919 services.APIEndPoint = APIEndPoint;
44920 })(services = app.services || (app.services = {}));
44921 })(app || (app = {}));
44925 (function (services) {
44926 var MyModal = (function () {
44927 function MyModal($uibModal) {
44928 this.$uibModal = $uibModal;
44929 this.modalOption = {
44936 MyModal.prototype.open = function (modalName) {
44937 if (modalName === 'SelectCommand') {
44938 this.modalOption.templateUrl = 'templates/select-command.html';
44939 this.modalOption.size = 'lg';
44941 return this.$uibModal.open(this.modalOption);
44943 MyModal.prototype.selectCommand = function () {
44944 this.modalOption.templateUrl = 'templates/select-command.html';
44945 this.modalOption.controller = 'selectCommandController';
44946 this.modalOption.controllerAs = 'c';
44947 this.modalOption.size = 'lg';
44948 return this.$uibModal.open(this.modalOption);
44950 MyModal.prototype.preview = function () {
44951 this.modalOption.templateUrl = 'templates/preview.html';
44952 this.modalOption.controller = 'previewController';
44953 this.modalOption.controllerAs = 'c';
44954 this.modalOption.size = 'lg';
44955 return this.$uibModal.open(this.modalOption);
44957 MyModal.$inject = ['$uibModal'];
44960 services.MyModal = MyModal;
44961 })(services = app.services || (app.services = {}));
44962 })(app || (app = {}));
44966 (function (services) {
44967 var WebSocket = (function () {
44968 function WebSocket($rootScope) {
44969 this.$rootScope = $rootScope;
44970 this.socket = io.connect();
44972 WebSocket.prototype.on = function (eventName, callback) {
44973 var socket = this.socket;
44974 var rootScope = this.$rootScope;
44975 socket.on(eventName, function () {
44976 var args = arguments;
44977 rootScope.$apply(function () {
44978 callback.apply(socket, args);
44982 WebSocket.prototype.emit = function (eventName, data, callback) {
44983 var socket = this.socket;
44984 var rootScope = this.$rootScope;
44985 this.socket.emit(eventName, data, function () {
44986 var args = arguments;
44987 rootScope.$apply(function () {
44989 callback.apply(socket, args);
44995 services.WebSocket = WebSocket;
44996 })(services = app.services || (app.services = {}));
44997 })(app || (app = {}));
45001 (function (directives) {
45002 var Command = (function () {
45003 function Command() {
45004 this.restrict = 'E';
45005 this.replace = true;
45007 this.controller = 'commandController';
45008 this.controllerAs = 'ctrl';
45009 this.bindToController = {
45015 this.templateUrl = 'templates/command.html';
45017 Command.Factory = function () {
45018 var directive = function () {
45019 return new Command();
45021 directive.$inject = [];
45026 directives.Command = Command;
45027 var CommandController = (function () {
45028 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
45029 this.APIEndPoint = APIEndPoint;
45030 this.$scope = $scope;
45031 this.MyModal = MyModal;
45032 this.WebSocket = WebSocket;
45033 var controller = this;
45035 .getOptionControlFile(this.name)
45037 .then(function (result) {
45038 controller.options = result.info;
45043 .then(function (result) {
45044 controller.dirs = result.info;
45046 this.heading = "[" + this.index + "]: dcdFilePrint";
45047 this.isOpen = true;
45048 this.$scope.$on('close', function () {
45049 controller.isOpen = false;
45051 this.WebSocket.on('console', function (msg) {
45052 var messages = msg.split('\n');
45053 if (messages[0].substr(0, 6) === 'Usage:') {
45054 controller.messages = msg.split('\n');
45058 CommandController.prototype.submit = function () {
45060 angular.forEach(this.options, function (option) {
45062 name: option.option,
45065 angular.forEach(option.arg, function (arg) {
45067 if (typeof arg.input === 'object') {
45068 obj.arguments.push(arg.input.name);
45071 obj.arguments.push(arg.input);
45075 if (obj.arguments.length > 0) {
45080 command: this.name,
45081 workspace: this.workspace.fileId,
45085 .execute(JSON.stringify(execObj))
45086 .then(function (result) {
45087 console.log(result);
45090 CommandController.prototype.removeMySelf = function (index) {
45091 this.remove()(index, this.list);
45093 CommandController.prototype.reloadFiles = function () {
45095 var fileId = this.workspace.fileId;
45099 .then(function (result) {
45100 var status = result.status;
45101 if (status === 'success') {
45102 _this.files = result.info;
45105 console.log(result.message);
45109 CommandController.prototype.debug = function () {
45112 .then(function (result) {
45113 console.log(result);
45116 CommandController.prototype.help = function () {
45119 .then(function (result) {
45120 console.log(result);
45123 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
45124 return CommandController;
45126 directives.CommandController = CommandController;
45127 })(directives = app.directives || (app.directives = {}));
45128 })(app || (app = {}));
45132 (function (directives) {
45133 var HeaderMenu = (function () {
45134 function HeaderMenu() {
45135 this.restrict = 'E';
45136 this.replace = true;
45137 this.templateUrl = 'templates/header-menu.html';
45138 this.controller = 'HeaderMenuController';
45139 this.controllerAs = 'hmc';
45142 HeaderMenu.Factory = function () {
45143 var directive = function () {
45144 return new HeaderMenu();
45150 directives.HeaderMenu = HeaderMenu;
45151 var HeaderMenuController = (function () {
45152 function HeaderMenuController($state) {
45153 this.$state = $state;
45154 this.isExecution = this.$state.current.name === 'execution';
45155 this.isWorkspace = this.$state.current.name === 'workspace';
45156 this.isHistory = this.$state.current.name === 'history';
45158 HeaderMenuController.prototype.transit = function (state) {
45159 this.$state.go(state);
45161 HeaderMenuController.$inject = ['$state'];
45162 return HeaderMenuController;
45164 directives.HeaderMenuController = HeaderMenuController;
45165 })(directives = app.directives || (app.directives = {}));
45166 })(app || (app = {}));
45170 (function (directives) {
45171 var Option = (function () {
45172 function Option() {
45173 this.restrict = 'E';
45174 this.replace = true;
45175 this.controller = 'optionController';
45176 this.bindToController = {
45181 this.templateUrl = 'templates/option.html';
45182 this.controllerAs = 'ctrl';
45184 Option.Factory = function () {
45185 var directive = function () {
45186 return new Option();
45188 directive.$inject = [];
45193 directives.Option = Option;
45194 var OptionController = (function () {
45195 function OptionController() {
45196 var controller = this;
45197 angular.forEach(controller.info.arg, function (arg) {
45198 if (arg.initialValue) {
45199 if (arg.formType === 'number') {
45200 arg.input = parseInt(arg.initialValue);
45203 arg.input = arg.initialValue;
45208 OptionController.$inject = [];
45209 return OptionController;
45211 directives.OptionController = OptionController;
45212 })(directives = app.directives || (app.directives = {}));
45213 })(app || (app = {}));
45217 (function (directives) {
45218 var Directory = (function () {
45219 function Directory() {
45220 this.restrict = 'E';
45221 this.replace = true;
45222 this.controller = 'directoryController';
45223 this.controllerAs = 'ctrl';
45224 this.bindToController = {
45230 this.templateUrl = 'templates/directory.html';
45232 Directory.Factory = function () {
45233 var directive = function () {
45234 return new Directory();
45240 directives.Directory = Directory;
45241 var DirectoryController = (function () {
45242 function DirectoryController(APIEndPoint, $scope) {
45243 this.APIEndPoint = APIEndPoint;
45244 this.$scope = $scope;
45245 var controller = this;
45247 .getFiles(this.info.fileId)
45249 .then(function (result) {
45250 if (result.status === 'success') {
45251 controller.files = result.info;
45252 angular.forEach(result.info, function (file) {
45253 if (file.fileType === '0') {
45255 if (controller.info.path === '/') {
45256 o.path = '/' + file.name;
45259 o.path = controller.info.path + '/' + file.name;
45261 controller.add()(o, controller.list);
45268 DirectoryController.$inject = ['APIEndPoint', '$scope'];
45269 return DirectoryController;
45271 directives.DirectoryController = DirectoryController;
45272 })(directives = app.directives || (app.directives = {}));
45273 })(app || (app = {}));
45277 (function (controllers) {
45278 var Execution = (function () {
45279 function Execution(MyModal, $scope) {
45280 this.MyModal = MyModal;
45281 this.$scope = $scope;
45282 this.commandInfoList = [];
45285 Execution.prototype.add = function () {
45286 this.$scope.$broadcast('close');
45287 var commandInfoList = this.commandInfoList;
45288 var commandInstance = this.MyModal.selectCommand();
45291 .then(function (command) {
45292 commandInfoList.push(new app.declares.CommandInfo(command));
45295 Execution.prototype.open = function () {
45296 var result = this.MyModal.open('SelectCommand');
45297 console.log(result);
45299 Execution.prototype.remove = function (index, list) {
45300 list.splice(index, 1);
45302 Execution.prototype.close = function () {
45303 console.log("close");
45305 Execution.$inject = ['MyModal', '$scope'];
45308 controllers.Execution = Execution;
45309 })(controllers = app.controllers || (app.controllers = {}));
45310 })(app || (app = {}));
45314 (function (controllers) {
45315 var Workspace = (function () {
45316 function Workspace($scope, APIEndPoint, MyModal) {
45317 this.$scope = $scope;
45318 this.APIEndPoint = APIEndPoint;
45319 this.MyModal = MyModal;
45320 this.directoryList = [];
45321 var controller = this;
45322 var directoryList = this.directoryList;
45324 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
45332 directoryList.push(o);
45334 Workspace.prototype.addDirectory = function (info, directoryList) {
45335 directoryList.push(info);
45337 Workspace.prototype.debug = function () {
45338 this.MyModal.preview();
45340 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
45343 controllers.Workspace = Workspace;
45344 })(controllers = app.controllers || (app.controllers = {}));
45345 })(app || (app = {}));
45349 (function (controllers) {
45350 var History = (function () {
45351 function History($scope) {
45352 this.page = "History";
45354 History.$inject = ['$scope'];
45357 controllers.History = History;
45358 })(controllers = app.controllers || (app.controllers = {}));
45359 })(app || (app = {}));
45363 (function (controllers) {
45364 var SelectCommand = (function () {
45365 function SelectCommand($scope, APIEndPoint, $modalInstance) {
45366 this.APIEndPoint = APIEndPoint;
45367 this.$modalInstance = $modalInstance;
45368 var controller = this;
45371 .$promise.then(function (result) {
45372 controller.tags = result.info;
45376 .$promise.then(function (result) {
45377 controller.commands = result.info;
45379 this.currentTag = 'all';
45381 SelectCommand.prototype.changeTag = function (tag) {
45382 this.currentTag = tag;
45384 SelectCommand.prototype.selectCommand = function (command) {
45385 this.$modalInstance.close(command);
45387 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
45388 return SelectCommand;
45390 controllers.SelectCommand = SelectCommand;
45391 })(controllers = app.controllers || (app.controllers = {}));
45392 })(app || (app = {}));
45396 (function (controllers) {
45397 var Preview = (function () {
45398 function Preview($scope, APIEndPoint, $modalInstance) {
45399 this.APIEndPoint = APIEndPoint;
45400 this.$modalInstance = $modalInstance;
45401 var controller = this;
45402 console.log('preview');
45404 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
45407 controllers.Preview = Preview;
45408 })(controllers = app.controllers || (app.controllers = {}));
45409 })(app || (app = {}));
45411 (function (filters) {
45413 return function (commands, tag) {
45415 angular.forEach(commands, function (command) {
45417 angular.forEach(command.tags, function (value) {
45422 result.push(command);
45428 })(filters || (filters = {}));
45432 var appName = 'zephyr';
45433 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
45434 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
45435 $urlRouterProvider.otherwise('/execution');
45436 $locationProvider.html5Mode({
45441 .state('execution', {
45443 templateUrl: 'templates/execution.html',
45444 controller: 'executionController',
45447 .state('workspace', {
45449 templateUrl: 'templates/workspace.html',
45450 controller: 'workspaceController',
45453 .state('history', {
45455 templateUrl: 'templates/history.html',
45456 controller: 'historyController',
45460 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
45461 app.zephyr.service('MyModal', app.services.MyModal);
45462 app.zephyr.service('WebSocket', app.services.WebSocket);
45463 app.zephyr.filter('Tag', filters.Tag);
45464 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
45465 app.zephyr.controller('previewController', app.controllers.Preview);
45466 app.zephyr.controller('executionController', app.controllers.Execution);
45467 app.zephyr.controller('workspaceController', app.controllers.Workspace);
45468 app.zephyr.controller('historyController', app.controllers.History);
45469 app.zephyr.controller('commandController', app.directives.CommandController);
45470 app.zephyr.controller('optionController', app.directives.OptionController);
45471 app.zephyr.controller('directoryController', app.directives.DirectoryController);
45472 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
45473 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
45474 app.zephyr.directive('command', app.directives.Command.Factory());
45475 app.zephyr.directive('option', app.directives.Option.Factory());
45476 app.zephyr.directive('directory', app.directives.Directory.Factory());
45477 })(app || (app = {}));
45482 /***/ function(module, exports) {
45487 (function (declares) {
45488 var CommandInfo = (function () {
45489 function CommandInfo(name) {
45492 return CommandInfo;
45494 declares.CommandInfo = CommandInfo;
45495 })(declares = app.declares || (app.declares = {}));
45496 })(app || (app = {}));
45500 (function (services) {
45501 var APIEndPoint = (function () {
45502 function APIEndPoint($resource, $http) {
45503 this.$resource = $resource;
45504 this.$http = $http;
45506 APIEndPoint.prototype.resource = function (endPoint, data) {
45507 var customAction = {
45513 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
45515 return this.$resource(endPoint, {}, { execute: execute });
45517 APIEndPoint.prototype.getOptionControlFile = function (command) {
45518 var endPoint = '/api/v1/optionControlFile/' + command;
45519 return this.resource(endPoint, {}).get();
45521 APIEndPoint.prototype.getFiles = function (fileId) {
45522 var endPoint = '/api/v1/workspace';
45524 endPoint += '/' + fileId;
45526 return this.resource(endPoint, {}).get();
45528 APIEndPoint.prototype.getDirectories = function () {
45529 var endPoint = '/api/v1/all/workspace/directory';
45530 return this.resource(endPoint, {}).get();
45532 APIEndPoint.prototype.getTags = function () {
45533 var endPoint = '/api/v1/tagList';
45534 return this.resource(endPoint, {}).get();
45536 APIEndPoint.prototype.getCommands = function () {
45537 var endPoint = '/api/v1/commandList';
45538 return this.resource(endPoint, {}).get();
45540 APIEndPoint.prototype.execute = function (data) {
45541 var endPoint = '/api/v1/execution';
45542 var fd = new FormData();
45543 fd.append('data', data);
45544 return this.$http.post(endPoint, fd, {
45545 headers: { 'Content-Type': undefined },
45546 transformRequest: angular.identity
45549 APIEndPoint.prototype.debug = function () {
45550 var endPoint = '/api/v1/debug';
45551 return this.$http.get(endPoint);
45553 APIEndPoint.prototype.help = function (command) {
45554 var endPoint = '/api/v1/help/' + command;
45555 return this.$http.get(endPoint);
45557 return APIEndPoint;
45559 services.APIEndPoint = APIEndPoint;
45560 })(services = app.services || (app.services = {}));
45561 })(app || (app = {}));
45565 (function (services) {
45566 var MyModal = (function () {
45567 function MyModal($uibModal) {
45568 this.$uibModal = $uibModal;
45569 this.modalOption = {
45576 MyModal.prototype.open = function (modalName) {
45577 if (modalName === 'SelectCommand') {
45578 this.modalOption.templateUrl = 'templates/select-command.html';
45579 this.modalOption.size = 'lg';
45581 return this.$uibModal.open(this.modalOption);
45583 MyModal.prototype.selectCommand = function () {
45584 this.modalOption.templateUrl = 'templates/select-command.html';
45585 this.modalOption.controller = 'selectCommandController';
45586 this.modalOption.controllerAs = 'c';
45587 this.modalOption.size = 'lg';
45588 return this.$uibModal.open(this.modalOption);
45590 MyModal.prototype.preview = function () {
45591 this.modalOption.templateUrl = 'templates/preview.html';
45592 this.modalOption.controller = 'previewController';
45593 this.modalOption.controllerAs = 'c';
45594 this.modalOption.size = 'lg';
45595 return this.$uibModal.open(this.modalOption);
45597 MyModal.$inject = ['$uibModal'];
45600 services.MyModal = MyModal;
45601 })(services = app.services || (app.services = {}));
45602 })(app || (app = {}));
45606 (function (services) {
45607 var WebSocket = (function () {
45608 function WebSocket($rootScope) {
45609 this.$rootScope = $rootScope;
45610 this.socket = io.connect();
45612 WebSocket.prototype.on = function (eventName, callback) {
45613 var socket = this.socket;
45614 var rootScope = this.$rootScope;
45615 socket.on(eventName, function () {
45616 var args = arguments;
45617 rootScope.$apply(function () {
45618 callback.apply(socket, args);
45622 WebSocket.prototype.emit = function (eventName, data, callback) {
45623 var socket = this.socket;
45624 var rootScope = this.$rootScope;
45625 this.socket.emit(eventName, data, function () {
45626 var args = arguments;
45627 rootScope.$apply(function () {
45629 callback.apply(socket, args);
45635 services.WebSocket = WebSocket;
45636 })(services = app.services || (app.services = {}));
45637 })(app || (app = {}));
45641 (function (directives) {
45642 var Command = (function () {
45643 function Command() {
45644 this.restrict = 'E';
45645 this.replace = true;
45647 this.controller = 'commandController';
45648 this.controllerAs = 'ctrl';
45649 this.bindToController = {
45655 this.templateUrl = 'templates/command.html';
45657 Command.Factory = function () {
45658 var directive = function () {
45659 return new Command();
45661 directive.$inject = [];
45666 directives.Command = Command;
45667 var CommandController = (function () {
45668 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
45669 this.APIEndPoint = APIEndPoint;
45670 this.$scope = $scope;
45671 this.MyModal = MyModal;
45672 this.WebSocket = WebSocket;
45673 var controller = this;
45675 .getOptionControlFile(this.name)
45677 .then(function (result) {
45678 controller.options = result.info;
45683 .then(function (result) {
45684 controller.dirs = result.info;
45686 this.heading = "[" + this.index + "]: dcdFilePrint";
45687 this.isOpen = true;
45688 this.$scope.$on('close', function () {
45689 controller.isOpen = false;
45691 this.WebSocket.on('console', function (msg) {
45692 var messages = msg.split('\n');
45693 if (messages[0].substr(0, 6) === 'Usage:') {
45694 controller.messages = msg.split('\n');
45698 CommandController.prototype.submit = function () {
45700 angular.forEach(this.options, function (option) {
45702 name: option.option,
45705 angular.forEach(option.arg, function (arg) {
45707 if (typeof arg.input === 'object') {
45708 obj.arguments.push(arg.input.name);
45711 obj.arguments.push(arg.input);
45715 if (obj.arguments.length > 0) {
45720 command: this.name,
45721 workspace: this.workspace.fileId,
45725 .execute(JSON.stringify(execObj))
45726 .then(function (result) {
45727 console.log(result);
45730 CommandController.prototype.removeMySelf = function (index) {
45731 this.remove()(index, this.list);
45733 CommandController.prototype.reloadFiles = function () {
45735 var fileId = this.workspace.fileId;
45739 .then(function (result) {
45740 var status = result.status;
45741 if (status === 'success') {
45742 _this.files = result.info;
45745 console.log(result.message);
45749 CommandController.prototype.debug = function () {
45752 .then(function (result) {
45753 console.log(result);
45756 CommandController.prototype.help = function () {
45759 .then(function (result) {
45760 console.log(result);
45763 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
45764 return CommandController;
45766 directives.CommandController = CommandController;
45767 })(directives = app.directives || (app.directives = {}));
45768 })(app || (app = {}));
45772 (function (directives) {
45773 var HeaderMenu = (function () {
45774 function HeaderMenu() {
45775 this.restrict = 'E';
45776 this.replace = true;
45777 this.templateUrl = 'templates/header-menu.html';
45778 this.controller = 'HeaderMenuController';
45779 this.controllerAs = 'hmc';
45782 HeaderMenu.Factory = function () {
45783 var directive = function () {
45784 return new HeaderMenu();
45790 directives.HeaderMenu = HeaderMenu;
45791 var HeaderMenuController = (function () {
45792 function HeaderMenuController($state) {
45793 this.$state = $state;
45794 this.isExecution = this.$state.current.name === 'execution';
45795 this.isWorkspace = this.$state.current.name === 'workspace';
45796 this.isHistory = this.$state.current.name === 'history';
45798 HeaderMenuController.prototype.transit = function (state) {
45799 this.$state.go(state);
45801 HeaderMenuController.$inject = ['$state'];
45802 return HeaderMenuController;
45804 directives.HeaderMenuController = HeaderMenuController;
45805 })(directives = app.directives || (app.directives = {}));
45806 })(app || (app = {}));
45810 (function (directives) {
45811 var Option = (function () {
45812 function Option() {
45813 this.restrict = 'E';
45814 this.replace = true;
45815 this.controller = 'optionController';
45816 this.bindToController = {
45821 this.templateUrl = 'templates/option.html';
45822 this.controllerAs = 'ctrl';
45824 Option.Factory = function () {
45825 var directive = function () {
45826 return new Option();
45828 directive.$inject = [];
45833 directives.Option = Option;
45834 var OptionController = (function () {
45835 function OptionController() {
45836 var controller = this;
45837 angular.forEach(controller.info.arg, function (arg) {
45838 if (arg.initialValue) {
45839 if (arg.formType === 'number') {
45840 arg.input = parseInt(arg.initialValue);
45843 arg.input = arg.initialValue;
45848 OptionController.$inject = [];
45849 return OptionController;
45851 directives.OptionController = OptionController;
45852 })(directives = app.directives || (app.directives = {}));
45853 })(app || (app = {}));
45857 (function (directives) {
45858 var Directory = (function () {
45859 function Directory() {
45860 this.restrict = 'E';
45861 this.replace = true;
45862 this.controller = 'directoryController';
45863 this.controllerAs = 'ctrl';
45864 this.bindToController = {
45870 this.templateUrl = 'templates/directory.html';
45872 Directory.Factory = function () {
45873 var directive = function () {
45874 return new Directory();
45880 directives.Directory = Directory;
45881 var DirectoryController = (function () {
45882 function DirectoryController(APIEndPoint, $scope) {
45883 this.APIEndPoint = APIEndPoint;
45884 this.$scope = $scope;
45885 var controller = this;
45887 .getFiles(this.info.fileId)
45889 .then(function (result) {
45890 if (result.status === 'success') {
45891 controller.files = result.info;
45892 angular.forEach(result.info, function (file) {
45893 if (file.fileType === '0') {
45895 if (controller.info.path === '/') {
45896 o.path = '/' + file.name;
45899 o.path = controller.info.path + '/' + file.name;
45901 controller.add()(o, controller.list);
45908 DirectoryController.$inject = ['APIEndPoint', '$scope'];
45909 return DirectoryController;
45911 directives.DirectoryController = DirectoryController;
45912 })(directives = app.directives || (app.directives = {}));
45913 })(app || (app = {}));
45917 (function (controllers) {
45918 var Execution = (function () {
45919 function Execution(MyModal, $scope) {
45920 this.MyModal = MyModal;
45921 this.$scope = $scope;
45922 this.commandInfoList = [];
45925 Execution.prototype.add = function () {
45926 this.$scope.$broadcast('close');
45927 var commandInfoList = this.commandInfoList;
45928 var commandInstance = this.MyModal.selectCommand();
45931 .then(function (command) {
45932 commandInfoList.push(new app.declares.CommandInfo(command));
45935 Execution.prototype.open = function () {
45936 var result = this.MyModal.open('SelectCommand');
45937 console.log(result);
45939 Execution.prototype.remove = function (index, list) {
45940 list.splice(index, 1);
45942 Execution.prototype.close = function () {
45943 console.log("close");
45945 Execution.$inject = ['MyModal', '$scope'];
45948 controllers.Execution = Execution;
45949 })(controllers = app.controllers || (app.controllers = {}));
45950 })(app || (app = {}));
45954 (function (controllers) {
45955 var Workspace = (function () {
45956 function Workspace($scope, APIEndPoint, MyModal) {
45957 this.$scope = $scope;
45958 this.APIEndPoint = APIEndPoint;
45959 this.MyModal = MyModal;
45960 this.directoryList = [];
45961 var controller = this;
45962 var directoryList = this.directoryList;
45964 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
45972 directoryList.push(o);
45974 Workspace.prototype.addDirectory = function (info, directoryList) {
45975 directoryList.push(info);
45977 Workspace.prototype.debug = function () {
45978 this.MyModal.preview();
45980 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
45983 controllers.Workspace = Workspace;
45984 })(controllers = app.controllers || (app.controllers = {}));
45985 })(app || (app = {}));
45989 (function (controllers) {
45990 var History = (function () {
45991 function History($scope) {
45992 this.page = "History";
45994 History.$inject = ['$scope'];
45997 controllers.History = History;
45998 })(controllers = app.controllers || (app.controllers = {}));
45999 })(app || (app = {}));
46003 (function (controllers) {
46004 var SelectCommand = (function () {
46005 function SelectCommand($scope, APIEndPoint, $modalInstance) {
46006 this.APIEndPoint = APIEndPoint;
46007 this.$modalInstance = $modalInstance;
46008 var controller = this;
46011 .$promise.then(function (result) {
46012 controller.tags = result.info;
46016 .$promise.then(function (result) {
46017 controller.commands = result.info;
46019 this.currentTag = 'all';
46021 SelectCommand.prototype.changeTag = function (tag) {
46022 this.currentTag = tag;
46024 SelectCommand.prototype.selectCommand = function (command) {
46025 this.$modalInstance.close(command);
46027 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
46028 return SelectCommand;
46030 controllers.SelectCommand = SelectCommand;
46031 })(controllers = app.controllers || (app.controllers = {}));
46032 })(app || (app = {}));
46036 (function (controllers) {
46037 var Preview = (function () {
46038 function Preview($scope, APIEndPoint, $modalInstance) {
46039 this.APIEndPoint = APIEndPoint;
46040 this.$modalInstance = $modalInstance;
46041 var controller = this;
46042 console.log('preview');
46044 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
46047 controllers.Preview = Preview;
46048 })(controllers = app.controllers || (app.controllers = {}));
46049 })(app || (app = {}));
46051 (function (filters) {
46053 return function (commands, tag) {
46055 angular.forEach(commands, function (command) {
46057 angular.forEach(command.tags, function (value) {
46062 result.push(command);
46068 })(filters || (filters = {}));
46072 var appName = 'zephyr';
46073 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
46074 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
46075 $urlRouterProvider.otherwise('/execution');
46076 $locationProvider.html5Mode({
46081 .state('execution', {
46083 templateUrl: 'templates/execution.html',
46084 controller: 'executionController',
46087 .state('workspace', {
46089 templateUrl: 'templates/workspace.html',
46090 controller: 'workspaceController',
46093 .state('history', {
46095 templateUrl: 'templates/history.html',
46096 controller: 'historyController',
46100 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
46101 app.zephyr.service('MyModal', app.services.MyModal);
46102 app.zephyr.service('WebSocket', app.services.WebSocket);
46103 app.zephyr.filter('Tag', filters.Tag);
46104 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
46105 app.zephyr.controller('previewController', app.controllers.Preview);
46106 app.zephyr.controller('executionController', app.controllers.Execution);
46107 app.zephyr.controller('workspaceController', app.controllers.Workspace);
46108 app.zephyr.controller('historyController', app.controllers.History);
46109 app.zephyr.controller('commandController', app.directives.CommandController);
46110 app.zephyr.controller('optionController', app.directives.OptionController);
46111 app.zephyr.controller('directoryController', app.directives.DirectoryController);
46112 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
46113 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
46114 app.zephyr.directive('command', app.directives.Command.Factory());
46115 app.zephyr.directive('option', app.directives.Option.Factory());
46116 app.zephyr.directive('directory', app.directives.Directory.Factory());
46117 })(app || (app = {}));
46122 /***/ function(module, exports) {
46127 (function (declares) {
46128 var CommandInfo = (function () {
46129 function CommandInfo(name) {
46132 return CommandInfo;
46134 declares.CommandInfo = CommandInfo;
46135 })(declares = app.declares || (app.declares = {}));
46136 })(app || (app = {}));
46140 (function (services) {
46141 var APIEndPoint = (function () {
46142 function APIEndPoint($resource, $http) {
46143 this.$resource = $resource;
46144 this.$http = $http;
46146 APIEndPoint.prototype.resource = function (endPoint, data) {
46147 var customAction = {
46153 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
46155 return this.$resource(endPoint, {}, { execute: execute });
46157 APIEndPoint.prototype.getOptionControlFile = function (command) {
46158 var endPoint = '/api/v1/optionControlFile/' + command;
46159 return this.resource(endPoint, {}).get();
46161 APIEndPoint.prototype.getFiles = function (fileId) {
46162 var endPoint = '/api/v1/workspace';
46164 endPoint += '/' + fileId;
46166 return this.resource(endPoint, {}).get();
46168 APIEndPoint.prototype.getDirectories = function () {
46169 var endPoint = '/api/v1/all/workspace/directory';
46170 return this.resource(endPoint, {}).get();
46172 APIEndPoint.prototype.getTags = function () {
46173 var endPoint = '/api/v1/tagList';
46174 return this.resource(endPoint, {}).get();
46176 APIEndPoint.prototype.getCommands = function () {
46177 var endPoint = '/api/v1/commandList';
46178 return this.resource(endPoint, {}).get();
46180 APIEndPoint.prototype.execute = function (data) {
46181 var endPoint = '/api/v1/execution';
46182 var fd = new FormData();
46183 fd.append('data', data);
46184 return this.$http.post(endPoint, fd, {
46185 headers: { 'Content-Type': undefined },
46186 transformRequest: angular.identity
46189 APIEndPoint.prototype.debug = function () {
46190 var endPoint = '/api/v1/debug';
46191 return this.$http.get(endPoint);
46193 APIEndPoint.prototype.help = function (command) {
46194 var endPoint = '/api/v1/help/' + command;
46195 return this.$http.get(endPoint);
46197 return APIEndPoint;
46199 services.APIEndPoint = APIEndPoint;
46200 })(services = app.services || (app.services = {}));
46201 })(app || (app = {}));
46205 (function (services) {
46206 var MyModal = (function () {
46207 function MyModal($uibModal) {
46208 this.$uibModal = $uibModal;
46209 this.modalOption = {
46216 MyModal.prototype.open = function (modalName) {
46217 if (modalName === 'SelectCommand') {
46218 this.modalOption.templateUrl = 'templates/select-command.html';
46219 this.modalOption.size = 'lg';
46221 return this.$uibModal.open(this.modalOption);
46223 MyModal.prototype.selectCommand = function () {
46224 this.modalOption.templateUrl = 'templates/select-command.html';
46225 this.modalOption.controller = 'selectCommandController';
46226 this.modalOption.controllerAs = 'c';
46227 this.modalOption.size = 'lg';
46228 return this.$uibModal.open(this.modalOption);
46230 MyModal.prototype.preview = function () {
46231 this.modalOption.templateUrl = 'templates/preview.html';
46232 this.modalOption.controller = 'previewController';
46233 this.modalOption.controllerAs = 'c';
46234 this.modalOption.size = 'lg';
46235 return this.$uibModal.open(this.modalOption);
46237 MyModal.$inject = ['$uibModal'];
46240 services.MyModal = MyModal;
46241 })(services = app.services || (app.services = {}));
46242 })(app || (app = {}));
46246 (function (services) {
46247 var WebSocket = (function () {
46248 function WebSocket($rootScope) {
46249 this.$rootScope = $rootScope;
46250 this.socket = io.connect();
46252 WebSocket.prototype.on = function (eventName, callback) {
46253 var socket = this.socket;
46254 var rootScope = this.$rootScope;
46255 socket.on(eventName, function () {
46256 var args = arguments;
46257 rootScope.$apply(function () {
46258 callback.apply(socket, args);
46262 WebSocket.prototype.emit = function (eventName, data, callback) {
46263 var socket = this.socket;
46264 var rootScope = this.$rootScope;
46265 this.socket.emit(eventName, data, function () {
46266 var args = arguments;
46267 rootScope.$apply(function () {
46269 callback.apply(socket, args);
46275 services.WebSocket = WebSocket;
46276 })(services = app.services || (app.services = {}));
46277 })(app || (app = {}));
46281 (function (directives) {
46282 var Command = (function () {
46283 function Command() {
46284 this.restrict = 'E';
46285 this.replace = true;
46287 this.controller = 'commandController';
46288 this.controllerAs = 'ctrl';
46289 this.bindToController = {
46295 this.templateUrl = 'templates/command.html';
46297 Command.Factory = function () {
46298 var directive = function () {
46299 return new Command();
46301 directive.$inject = [];
46306 directives.Command = Command;
46307 var CommandController = (function () {
46308 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
46309 this.APIEndPoint = APIEndPoint;
46310 this.$scope = $scope;
46311 this.MyModal = MyModal;
46312 this.WebSocket = WebSocket;
46313 var controller = this;
46315 .getOptionControlFile(this.name)
46317 .then(function (result) {
46318 controller.options = result.info;
46323 .then(function (result) {
46324 controller.dirs = result.info;
46326 this.heading = "[" + this.index + "]: dcdFilePrint";
46327 this.isOpen = true;
46328 this.$scope.$on('close', function () {
46329 controller.isOpen = false;
46331 this.WebSocket.on('console', function (msg) {
46332 var messages = msg.split('\n');
46333 if (messages[0].substr(0, 6) === 'Usage:') {
46334 controller.messages = msg.split('\n');
46338 CommandController.prototype.submit = function () {
46340 angular.forEach(this.options, function (option) {
46342 name: option.option,
46345 angular.forEach(option.arg, function (arg) {
46347 if (typeof arg.input === 'object') {
46348 obj.arguments.push(arg.input.name);
46351 obj.arguments.push(arg.input);
46355 if (obj.arguments.length > 0) {
46360 command: this.name,
46361 workspace: this.workspace.fileId,
46365 .execute(JSON.stringify(execObj))
46366 .then(function (result) {
46367 console.log(result);
46370 CommandController.prototype.removeMySelf = function (index) {
46371 this.remove()(index, this.list);
46373 CommandController.prototype.reloadFiles = function () {
46375 var fileId = this.workspace.fileId;
46379 .then(function (result) {
46380 var status = result.status;
46381 if (status === 'success') {
46382 _this.files = result.info;
46385 console.log(result.message);
46389 CommandController.prototype.debug = function () {
46392 .then(function (result) {
46393 console.log(result);
46396 CommandController.prototype.help = function () {
46399 .then(function (result) {
46400 console.log(result);
46403 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
46404 return CommandController;
46406 directives.CommandController = CommandController;
46407 })(directives = app.directives || (app.directives = {}));
46408 })(app || (app = {}));
46412 (function (directives) {
46413 var HeaderMenu = (function () {
46414 function HeaderMenu() {
46415 this.restrict = 'E';
46416 this.replace = true;
46417 this.templateUrl = 'templates/header-menu.html';
46418 this.controller = 'HeaderMenuController';
46419 this.controllerAs = 'hmc';
46422 HeaderMenu.Factory = function () {
46423 var directive = function () {
46424 return new HeaderMenu();
46430 directives.HeaderMenu = HeaderMenu;
46431 var HeaderMenuController = (function () {
46432 function HeaderMenuController($state) {
46433 this.$state = $state;
46434 this.isExecution = this.$state.current.name === 'execution';
46435 this.isWorkspace = this.$state.current.name === 'workspace';
46436 this.isHistory = this.$state.current.name === 'history';
46438 HeaderMenuController.prototype.transit = function (state) {
46439 this.$state.go(state);
46441 HeaderMenuController.$inject = ['$state'];
46442 return HeaderMenuController;
46444 directives.HeaderMenuController = HeaderMenuController;
46445 })(directives = app.directives || (app.directives = {}));
46446 })(app || (app = {}));
46450 (function (directives) {
46451 var Option = (function () {
46452 function Option() {
46453 this.restrict = 'E';
46454 this.replace = true;
46455 this.controller = 'optionController';
46456 this.bindToController = {
46461 this.templateUrl = 'templates/option.html';
46462 this.controllerAs = 'ctrl';
46464 Option.Factory = function () {
46465 var directive = function () {
46466 return new Option();
46468 directive.$inject = [];
46473 directives.Option = Option;
46474 var OptionController = (function () {
46475 function OptionController() {
46476 var controller = this;
46477 angular.forEach(controller.info.arg, function (arg) {
46478 if (arg.initialValue) {
46479 if (arg.formType === 'number') {
46480 arg.input = parseInt(arg.initialValue);
46483 arg.input = arg.initialValue;
46488 OptionController.$inject = [];
46489 return OptionController;
46491 directives.OptionController = OptionController;
46492 })(directives = app.directives || (app.directives = {}));
46493 })(app || (app = {}));
46497 (function (directives) {
46498 var Directory = (function () {
46499 function Directory() {
46500 this.restrict = 'E';
46501 this.replace = true;
46502 this.controller = 'directoryController';
46503 this.controllerAs = 'ctrl';
46504 this.bindToController = {
46510 this.templateUrl = 'templates/directory.html';
46512 Directory.Factory = function () {
46513 var directive = function () {
46514 return new Directory();
46520 directives.Directory = Directory;
46521 var DirectoryController = (function () {
46522 function DirectoryController(APIEndPoint, $scope) {
46523 this.APIEndPoint = APIEndPoint;
46524 this.$scope = $scope;
46525 var controller = this;
46527 .getFiles(this.info.fileId)
46529 .then(function (result) {
46530 if (result.status === 'success') {
46531 controller.files = result.info;
46532 angular.forEach(result.info, function (file) {
46533 if (file.fileType === '0') {
46535 if (controller.info.path === '/') {
46536 o.path = '/' + file.name;
46539 o.path = controller.info.path + '/' + file.name;
46541 controller.add()(o, controller.list);
46548 DirectoryController.$inject = ['APIEndPoint', '$scope'];
46549 return DirectoryController;
46551 directives.DirectoryController = DirectoryController;
46552 })(directives = app.directives || (app.directives = {}));
46553 })(app || (app = {}));
46557 (function (controllers) {
46558 var Execution = (function () {
46559 function Execution(MyModal, $scope) {
46560 this.MyModal = MyModal;
46561 this.$scope = $scope;
46562 this.commandInfoList = [];
46565 Execution.prototype.add = function () {
46566 this.$scope.$broadcast('close');
46567 var commandInfoList = this.commandInfoList;
46568 var commandInstance = this.MyModal.selectCommand();
46571 .then(function (command) {
46572 commandInfoList.push(new app.declares.CommandInfo(command));
46575 Execution.prototype.open = function () {
46576 var result = this.MyModal.open('SelectCommand');
46577 console.log(result);
46579 Execution.prototype.remove = function (index, list) {
46580 list.splice(index, 1);
46582 Execution.prototype.close = function () {
46583 console.log("close");
46585 Execution.$inject = ['MyModal', '$scope'];
46588 controllers.Execution = Execution;
46589 })(controllers = app.controllers || (app.controllers = {}));
46590 })(app || (app = {}));
46594 (function (controllers) {
46595 var Workspace = (function () {
46596 function Workspace($scope, APIEndPoint, MyModal) {
46597 this.$scope = $scope;
46598 this.APIEndPoint = APIEndPoint;
46599 this.MyModal = MyModal;
46600 this.directoryList = [];
46601 var controller = this;
46602 var directoryList = this.directoryList;
46604 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
46612 directoryList.push(o);
46614 Workspace.prototype.addDirectory = function (info, directoryList) {
46615 directoryList.push(info);
46617 Workspace.prototype.debug = function () {
46618 this.MyModal.preview();
46620 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
46623 controllers.Workspace = Workspace;
46624 })(controllers = app.controllers || (app.controllers = {}));
46625 })(app || (app = {}));
46629 (function (controllers) {
46630 var History = (function () {
46631 function History($scope) {
46632 this.page = "History";
46634 History.$inject = ['$scope'];
46637 controllers.History = History;
46638 })(controllers = app.controllers || (app.controllers = {}));
46639 })(app || (app = {}));
46643 (function (controllers) {
46644 var SelectCommand = (function () {
46645 function SelectCommand($scope, APIEndPoint, $modalInstance) {
46646 this.APIEndPoint = APIEndPoint;
46647 this.$modalInstance = $modalInstance;
46648 var controller = this;
46651 .$promise.then(function (result) {
46652 controller.tags = result.info;
46656 .$promise.then(function (result) {
46657 controller.commands = result.info;
46659 this.currentTag = 'all';
46661 SelectCommand.prototype.changeTag = function (tag) {
46662 this.currentTag = tag;
46664 SelectCommand.prototype.selectCommand = function (command) {
46665 this.$modalInstance.close(command);
46667 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
46668 return SelectCommand;
46670 controllers.SelectCommand = SelectCommand;
46671 })(controllers = app.controllers || (app.controllers = {}));
46672 })(app || (app = {}));
46676 (function (controllers) {
46677 var Preview = (function () {
46678 function Preview($scope, APIEndPoint, $modalInstance) {
46679 this.APIEndPoint = APIEndPoint;
46680 this.$modalInstance = $modalInstance;
46681 var controller = this;
46682 console.log('preview');
46684 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
46687 controllers.Preview = Preview;
46688 })(controllers = app.controllers || (app.controllers = {}));
46689 })(app || (app = {}));
46691 (function (filters) {
46693 return function (commands, tag) {
46695 angular.forEach(commands, function (command) {
46697 angular.forEach(command.tags, function (value) {
46702 result.push(command);
46708 })(filters || (filters = {}));
46712 var appName = 'zephyr';
46713 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
46714 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
46715 $urlRouterProvider.otherwise('/execution');
46716 $locationProvider.html5Mode({
46721 .state('execution', {
46723 templateUrl: 'templates/execution.html',
46724 controller: 'executionController',
46727 .state('workspace', {
46729 templateUrl: 'templates/workspace.html',
46730 controller: 'workspaceController',
46733 .state('history', {
46735 templateUrl: 'templates/history.html',
46736 controller: 'historyController',
46740 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
46741 app.zephyr.service('MyModal', app.services.MyModal);
46742 app.zephyr.service('WebSocket', app.services.WebSocket);
46743 app.zephyr.filter('Tag', filters.Tag);
46744 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
46745 app.zephyr.controller('previewController', app.controllers.Preview);
46746 app.zephyr.controller('executionController', app.controllers.Execution);
46747 app.zephyr.controller('workspaceController', app.controllers.Workspace);
46748 app.zephyr.controller('historyController', app.controllers.History);
46749 app.zephyr.controller('commandController', app.directives.CommandController);
46750 app.zephyr.controller('optionController', app.directives.OptionController);
46751 app.zephyr.controller('directoryController', app.directives.DirectoryController);
46752 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
46753 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
46754 app.zephyr.directive('command', app.directives.Command.Factory());
46755 app.zephyr.directive('option', app.directives.Option.Factory());
46756 app.zephyr.directive('directory', app.directives.Directory.Factory());
46757 })(app || (app = {}));
46762 /***/ function(module, exports) {
46767 (function (declares) {
46768 var CommandInfo = (function () {
46769 function CommandInfo(name) {
46772 return CommandInfo;
46774 declares.CommandInfo = CommandInfo;
46775 })(declares = app.declares || (app.declares = {}));
46776 })(app || (app = {}));
46780 (function (services) {
46781 var APIEndPoint = (function () {
46782 function APIEndPoint($resource, $http) {
46783 this.$resource = $resource;
46784 this.$http = $http;
46786 APIEndPoint.prototype.resource = function (endPoint, data) {
46787 var customAction = {
46793 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
46795 return this.$resource(endPoint, {}, { execute: execute });
46797 APIEndPoint.prototype.getOptionControlFile = function (command) {
46798 var endPoint = '/api/v1/optionControlFile/' + command;
46799 return this.resource(endPoint, {}).get();
46801 APIEndPoint.prototype.getFiles = function (fileId) {
46802 var endPoint = '/api/v1/workspace';
46804 endPoint += '/' + fileId;
46806 return this.resource(endPoint, {}).get();
46808 APIEndPoint.prototype.getDirectories = function () {
46809 var endPoint = '/api/v1/all/workspace/directory';
46810 return this.resource(endPoint, {}).get();
46812 APIEndPoint.prototype.getTags = function () {
46813 var endPoint = '/api/v1/tagList';
46814 return this.resource(endPoint, {}).get();
46816 APIEndPoint.prototype.getCommands = function () {
46817 var endPoint = '/api/v1/commandList';
46818 return this.resource(endPoint, {}).get();
46820 APIEndPoint.prototype.execute = function (data) {
46821 var endPoint = '/api/v1/execution';
46822 var fd = new FormData();
46823 fd.append('data', data);
46824 return this.$http.post(endPoint, fd, {
46825 headers: { 'Content-Type': undefined },
46826 transformRequest: angular.identity
46829 APIEndPoint.prototype.debug = function () {
46830 var endPoint = '/api/v1/debug';
46831 return this.$http.get(endPoint);
46833 APIEndPoint.prototype.help = function (command) {
46834 var endPoint = '/api/v1/help/' + command;
46835 return this.$http.get(endPoint);
46837 return APIEndPoint;
46839 services.APIEndPoint = APIEndPoint;
46840 })(services = app.services || (app.services = {}));
46841 })(app || (app = {}));
46845 (function (services) {
46846 var MyModal = (function () {
46847 function MyModal($uibModal) {
46848 this.$uibModal = $uibModal;
46849 this.modalOption = {
46856 MyModal.prototype.open = function (modalName) {
46857 if (modalName === 'SelectCommand') {
46858 this.modalOption.templateUrl = 'templates/select-command.html';
46859 this.modalOption.size = 'lg';
46861 return this.$uibModal.open(this.modalOption);
46863 MyModal.prototype.selectCommand = function () {
46864 this.modalOption.templateUrl = 'templates/select-command.html';
46865 this.modalOption.controller = 'selectCommandController';
46866 this.modalOption.controllerAs = 'c';
46867 this.modalOption.size = 'lg';
46868 return this.$uibModal.open(this.modalOption);
46870 MyModal.prototype.preview = function () {
46871 this.modalOption.templateUrl = 'templates/preview.html';
46872 this.modalOption.controller = 'previewController';
46873 this.modalOption.controllerAs = 'c';
46874 this.modalOption.size = 'lg';
46875 return this.$uibModal.open(this.modalOption);
46877 MyModal.$inject = ['$uibModal'];
46880 services.MyModal = MyModal;
46881 })(services = app.services || (app.services = {}));
46882 })(app || (app = {}));
46886 (function (services) {
46887 var WebSocket = (function () {
46888 function WebSocket($rootScope) {
46889 this.$rootScope = $rootScope;
46890 this.socket = io.connect();
46892 WebSocket.prototype.on = function (eventName, callback) {
46893 var socket = this.socket;
46894 var rootScope = this.$rootScope;
46895 socket.on(eventName, function () {
46896 var args = arguments;
46897 rootScope.$apply(function () {
46898 callback.apply(socket, args);
46902 WebSocket.prototype.emit = function (eventName, data, callback) {
46903 var socket = this.socket;
46904 var rootScope = this.$rootScope;
46905 this.socket.emit(eventName, data, function () {
46906 var args = arguments;
46907 rootScope.$apply(function () {
46909 callback.apply(socket, args);
46915 services.WebSocket = WebSocket;
46916 })(services = app.services || (app.services = {}));
46917 })(app || (app = {}));
46921 (function (directives) {
46922 var Command = (function () {
46923 function Command() {
46924 this.restrict = 'E';
46925 this.replace = true;
46927 this.controller = 'commandController';
46928 this.controllerAs = 'ctrl';
46929 this.bindToController = {
46935 this.templateUrl = 'templates/command.html';
46937 Command.Factory = function () {
46938 var directive = function () {
46939 return new Command();
46941 directive.$inject = [];
46946 directives.Command = Command;
46947 var CommandController = (function () {
46948 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
46949 this.APIEndPoint = APIEndPoint;
46950 this.$scope = $scope;
46951 this.MyModal = MyModal;
46952 this.WebSocket = WebSocket;
46953 var controller = this;
46955 .getOptionControlFile(this.name)
46957 .then(function (result) {
46958 controller.options = result.info;
46963 .then(function (result) {
46964 controller.dirs = result.info;
46966 this.heading = "[" + this.index + "]: dcdFilePrint";
46967 this.isOpen = true;
46968 this.$scope.$on('close', function () {
46969 controller.isOpen = false;
46971 this.WebSocket.on('console', function (msg) {
46972 var messages = msg.split('\n');
46973 if (messages[0].substr(0, 6) === 'Usage:') {
46974 controller.messages = msg.split('\n');
46978 CommandController.prototype.submit = function () {
46980 angular.forEach(this.options, function (option) {
46982 name: option.option,
46985 angular.forEach(option.arg, function (arg) {
46987 if (typeof arg.input === 'object') {
46988 obj.arguments.push(arg.input.name);
46991 obj.arguments.push(arg.input);
46995 if (obj.arguments.length > 0) {
47000 command: this.name,
47001 workspace: this.workspace.fileId,
47005 .execute(JSON.stringify(execObj))
47006 .then(function (result) {
47007 console.log(result);
47010 CommandController.prototype.removeMySelf = function (index) {
47011 this.remove()(index, this.list);
47013 CommandController.prototype.reloadFiles = function () {
47015 var fileId = this.workspace.fileId;
47019 .then(function (result) {
47020 var status = result.status;
47021 if (status === 'success') {
47022 _this.files = result.info;
47025 console.log(result.message);
47029 CommandController.prototype.debug = function () {
47032 .then(function (result) {
47033 console.log(result);
47036 CommandController.prototype.help = function () {
47039 .then(function (result) {
47040 console.log(result);
47043 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
47044 return CommandController;
47046 directives.CommandController = CommandController;
47047 })(directives = app.directives || (app.directives = {}));
47048 })(app || (app = {}));
47052 (function (directives) {
47053 var HeaderMenu = (function () {
47054 function HeaderMenu() {
47055 this.restrict = 'E';
47056 this.replace = true;
47057 this.templateUrl = 'templates/header-menu.html';
47058 this.controller = 'HeaderMenuController';
47059 this.controllerAs = 'hmc';
47062 HeaderMenu.Factory = function () {
47063 var directive = function () {
47064 return new HeaderMenu();
47070 directives.HeaderMenu = HeaderMenu;
47071 var HeaderMenuController = (function () {
47072 function HeaderMenuController($state) {
47073 this.$state = $state;
47074 this.isExecution = this.$state.current.name === 'execution';
47075 this.isWorkspace = this.$state.current.name === 'workspace';
47076 this.isHistory = this.$state.current.name === 'history';
47078 HeaderMenuController.prototype.transit = function (state) {
47079 this.$state.go(state);
47081 HeaderMenuController.$inject = ['$state'];
47082 return HeaderMenuController;
47084 directives.HeaderMenuController = HeaderMenuController;
47085 })(directives = app.directives || (app.directives = {}));
47086 })(app || (app = {}));
47090 (function (directives) {
47091 var Option = (function () {
47092 function Option() {
47093 this.restrict = 'E';
47094 this.replace = true;
47095 this.controller = 'optionController';
47096 this.bindToController = {
47101 this.templateUrl = 'templates/option.html';
47102 this.controllerAs = 'ctrl';
47104 Option.Factory = function () {
47105 var directive = function () {
47106 return new Option();
47108 directive.$inject = [];
47113 directives.Option = Option;
47114 var OptionController = (function () {
47115 function OptionController() {
47116 var controller = this;
47117 angular.forEach(controller.info.arg, function (arg) {
47118 if (arg.initialValue) {
47119 if (arg.formType === 'number') {
47120 arg.input = parseInt(arg.initialValue);
47123 arg.input = arg.initialValue;
47128 OptionController.$inject = [];
47129 return OptionController;
47131 directives.OptionController = OptionController;
47132 })(directives = app.directives || (app.directives = {}));
47133 })(app || (app = {}));
47137 (function (directives) {
47138 var Directory = (function () {
47139 function Directory() {
47140 this.restrict = 'E';
47141 this.replace = true;
47142 this.controller = 'directoryController';
47143 this.controllerAs = 'ctrl';
47144 this.bindToController = {
47150 this.templateUrl = 'templates/directory.html';
47152 Directory.Factory = function () {
47153 var directive = function () {
47154 return new Directory();
47160 directives.Directory = Directory;
47161 var DirectoryController = (function () {
47162 function DirectoryController(APIEndPoint, $scope) {
47163 this.APIEndPoint = APIEndPoint;
47164 this.$scope = $scope;
47165 var controller = this;
47167 .getFiles(this.info.fileId)
47169 .then(function (result) {
47170 if (result.status === 'success') {
47171 controller.files = result.info;
47172 angular.forEach(result.info, function (file) {
47173 if (file.fileType === '0') {
47175 if (controller.info.path === '/') {
47176 o.path = '/' + file.name;
47179 o.path = controller.info.path + '/' + file.name;
47181 controller.add()(o, controller.list);
47188 DirectoryController.$inject = ['APIEndPoint', '$scope'];
47189 return DirectoryController;
47191 directives.DirectoryController = DirectoryController;
47192 })(directives = app.directives || (app.directives = {}));
47193 })(app || (app = {}));
47197 (function (controllers) {
47198 var Execution = (function () {
47199 function Execution(MyModal, $scope) {
47200 this.MyModal = MyModal;
47201 this.$scope = $scope;
47202 this.commandInfoList = [];
47205 Execution.prototype.add = function () {
47206 this.$scope.$broadcast('close');
47207 var commandInfoList = this.commandInfoList;
47208 var commandInstance = this.MyModal.selectCommand();
47211 .then(function (command) {
47212 commandInfoList.push(new app.declares.CommandInfo(command));
47215 Execution.prototype.open = function () {
47216 var result = this.MyModal.open('SelectCommand');
47217 console.log(result);
47219 Execution.prototype.remove = function (index, list) {
47220 list.splice(index, 1);
47222 Execution.prototype.close = function () {
47223 console.log("close");
47225 Execution.$inject = ['MyModal', '$scope'];
47228 controllers.Execution = Execution;
47229 })(controllers = app.controllers || (app.controllers = {}));
47230 })(app || (app = {}));
47234 (function (controllers) {
47235 var Workspace = (function () {
47236 function Workspace($scope, APIEndPoint, MyModal) {
47237 this.$scope = $scope;
47238 this.APIEndPoint = APIEndPoint;
47239 this.MyModal = MyModal;
47240 this.directoryList = [];
47241 var controller = this;
47242 var directoryList = this.directoryList;
47244 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
47252 directoryList.push(o);
47254 Workspace.prototype.addDirectory = function (info, directoryList) {
47255 directoryList.push(info);
47257 Workspace.prototype.debug = function () {
47258 this.MyModal.preview();
47260 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
47263 controllers.Workspace = Workspace;
47264 })(controllers = app.controllers || (app.controllers = {}));
47265 })(app || (app = {}));
47269 (function (controllers) {
47270 var History = (function () {
47271 function History($scope) {
47272 this.page = "History";
47274 History.$inject = ['$scope'];
47277 controllers.History = History;
47278 })(controllers = app.controllers || (app.controllers = {}));
47279 })(app || (app = {}));
47283 (function (controllers) {
47284 var SelectCommand = (function () {
47285 function SelectCommand($scope, APIEndPoint, $modalInstance) {
47286 this.APIEndPoint = APIEndPoint;
47287 this.$modalInstance = $modalInstance;
47288 var controller = this;
47291 .$promise.then(function (result) {
47292 controller.tags = result.info;
47296 .$promise.then(function (result) {
47297 controller.commands = result.info;
47299 this.currentTag = 'all';
47301 SelectCommand.prototype.changeTag = function (tag) {
47302 this.currentTag = tag;
47304 SelectCommand.prototype.selectCommand = function (command) {
47305 this.$modalInstance.close(command);
47307 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
47308 return SelectCommand;
47310 controllers.SelectCommand = SelectCommand;
47311 })(controllers = app.controllers || (app.controllers = {}));
47312 })(app || (app = {}));
47316 (function (controllers) {
47317 var Preview = (function () {
47318 function Preview($scope, APIEndPoint, $modalInstance) {
47319 this.APIEndPoint = APIEndPoint;
47320 this.$modalInstance = $modalInstance;
47321 var controller = this;
47322 console.log('preview');
47324 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
47327 controllers.Preview = Preview;
47328 })(controllers = app.controllers || (app.controllers = {}));
47329 })(app || (app = {}));
47331 (function (filters) {
47333 return function (commands, tag) {
47335 angular.forEach(commands, function (command) {
47337 angular.forEach(command.tags, function (value) {
47342 result.push(command);
47348 })(filters || (filters = {}));
47352 var appName = 'zephyr';
47353 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
47354 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
47355 $urlRouterProvider.otherwise('/execution');
47356 $locationProvider.html5Mode({
47361 .state('execution', {
47363 templateUrl: 'templates/execution.html',
47364 controller: 'executionController',
47367 .state('workspace', {
47369 templateUrl: 'templates/workspace.html',
47370 controller: 'workspaceController',
47373 .state('history', {
47375 templateUrl: 'templates/history.html',
47376 controller: 'historyController',
47380 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
47381 app.zephyr.service('MyModal', app.services.MyModal);
47382 app.zephyr.service('WebSocket', app.services.WebSocket);
47383 app.zephyr.filter('Tag', filters.Tag);
47384 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
47385 app.zephyr.controller('previewController', app.controllers.Preview);
47386 app.zephyr.controller('executionController', app.controllers.Execution);
47387 app.zephyr.controller('workspaceController', app.controllers.Workspace);
47388 app.zephyr.controller('historyController', app.controllers.History);
47389 app.zephyr.controller('commandController', app.directives.CommandController);
47390 app.zephyr.controller('optionController', app.directives.OptionController);
47391 app.zephyr.controller('directoryController', app.directives.DirectoryController);
47392 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
47393 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
47394 app.zephyr.directive('command', app.directives.Command.Factory());
47395 app.zephyr.directive('option', app.directives.Option.Factory());
47396 app.zephyr.directive('directory', app.directives.Directory.Factory());
47397 })(app || (app = {}));
47402 /***/ function(module, exports) {
47407 (function (declares) {
47408 var CommandInfo = (function () {
47409 function CommandInfo(name) {
47412 return CommandInfo;
47414 declares.CommandInfo = CommandInfo;
47415 })(declares = app.declares || (app.declares = {}));
47416 })(app || (app = {}));
47420 (function (services) {
47421 var APIEndPoint = (function () {
47422 function APIEndPoint($resource, $http) {
47423 this.$resource = $resource;
47424 this.$http = $http;
47426 APIEndPoint.prototype.resource = function (endPoint, data) {
47427 var customAction = {
47433 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
47435 return this.$resource(endPoint, {}, { execute: execute });
47437 APIEndPoint.prototype.getOptionControlFile = function (command) {
47438 var endPoint = '/api/v1/optionControlFile/' + command;
47439 return this.resource(endPoint, {}).get();
47441 APIEndPoint.prototype.getFiles = function (fileId) {
47442 var endPoint = '/api/v1/workspace';
47444 endPoint += '/' + fileId;
47446 return this.resource(endPoint, {}).get();
47448 APIEndPoint.prototype.getDirectories = function () {
47449 var endPoint = '/api/v1/all/workspace/directory';
47450 return this.resource(endPoint, {}).get();
47452 APIEndPoint.prototype.getTags = function () {
47453 var endPoint = '/api/v1/tagList';
47454 return this.resource(endPoint, {}).get();
47456 APIEndPoint.prototype.getCommands = function () {
47457 var endPoint = '/api/v1/commandList';
47458 return this.resource(endPoint, {}).get();
47460 APIEndPoint.prototype.execute = function (data) {
47461 var endPoint = '/api/v1/execution';
47462 var fd = new FormData();
47463 fd.append('data', data);
47464 return this.$http.post(endPoint, fd, {
47465 headers: { 'Content-Type': undefined },
47466 transformRequest: angular.identity
47469 APIEndPoint.prototype.debug = function () {
47470 var endPoint = '/api/v1/debug';
47471 return this.$http.get(endPoint);
47473 APIEndPoint.prototype.help = function (command) {
47474 var endPoint = '/api/v1/help/' + command;
47475 return this.$http.get(endPoint);
47477 return APIEndPoint;
47479 services.APIEndPoint = APIEndPoint;
47480 })(services = app.services || (app.services = {}));
47481 })(app || (app = {}));
47485 (function (services) {
47486 var MyModal = (function () {
47487 function MyModal($uibModal) {
47488 this.$uibModal = $uibModal;
47489 this.modalOption = {
47496 MyModal.prototype.open = function (modalName) {
47497 if (modalName === 'SelectCommand') {
47498 this.modalOption.templateUrl = 'templates/select-command.html';
47499 this.modalOption.size = 'lg';
47501 return this.$uibModal.open(this.modalOption);
47503 MyModal.prototype.selectCommand = function () {
47504 this.modalOption.templateUrl = 'templates/select-command.html';
47505 this.modalOption.controller = 'selectCommandController';
47506 this.modalOption.controllerAs = 'c';
47507 this.modalOption.size = 'lg';
47508 return this.$uibModal.open(this.modalOption);
47510 MyModal.prototype.preview = function () {
47511 this.modalOption.templateUrl = 'templates/preview.html';
47512 this.modalOption.controller = 'previewController';
47513 this.modalOption.controllerAs = 'c';
47514 this.modalOption.size = 'lg';
47515 return this.$uibModal.open(this.modalOption);
47517 MyModal.$inject = ['$uibModal'];
47520 services.MyModal = MyModal;
47521 })(services = app.services || (app.services = {}));
47522 })(app || (app = {}));
47526 (function (services) {
47527 var WebSocket = (function () {
47528 function WebSocket($rootScope) {
47529 this.$rootScope = $rootScope;
47530 this.socket = io.connect();
47532 WebSocket.prototype.on = function (eventName, callback) {
47533 var socket = this.socket;
47534 var rootScope = this.$rootScope;
47535 socket.on(eventName, function () {
47536 var args = arguments;
47537 rootScope.$apply(function () {
47538 callback.apply(socket, args);
47542 WebSocket.prototype.emit = function (eventName, data, callback) {
47543 var socket = this.socket;
47544 var rootScope = this.$rootScope;
47545 this.socket.emit(eventName, data, function () {
47546 var args = arguments;
47547 rootScope.$apply(function () {
47549 callback.apply(socket, args);
47555 services.WebSocket = WebSocket;
47556 })(services = app.services || (app.services = {}));
47557 })(app || (app = {}));
47561 (function (directives) {
47562 var Command = (function () {
47563 function Command() {
47564 this.restrict = 'E';
47565 this.replace = true;
47567 this.controller = 'commandController';
47568 this.controllerAs = 'ctrl';
47569 this.bindToController = {
47575 this.templateUrl = 'templates/command.html';
47577 Command.Factory = function () {
47578 var directive = function () {
47579 return new Command();
47581 directive.$inject = [];
47586 directives.Command = Command;
47587 var CommandController = (function () {
47588 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
47589 this.APIEndPoint = APIEndPoint;
47590 this.$scope = $scope;
47591 this.MyModal = MyModal;
47592 this.WebSocket = WebSocket;
47593 var controller = this;
47595 .getOptionControlFile(this.name)
47597 .then(function (result) {
47598 controller.options = result.info;
47603 .then(function (result) {
47604 controller.dirs = result.info;
47606 this.heading = "[" + this.index + "]: dcdFilePrint";
47607 this.isOpen = true;
47608 this.$scope.$on('close', function () {
47609 controller.isOpen = false;
47611 this.WebSocket.on('console', function (msg) {
47612 var messages = msg.split('\n');
47613 if (messages[0].substr(0, 6) === 'Usage:') {
47614 controller.messages = msg.split('\n');
47618 CommandController.prototype.submit = function () {
47620 angular.forEach(this.options, function (option) {
47622 name: option.option,
47625 angular.forEach(option.arg, function (arg) {
47627 if (typeof arg.input === 'object') {
47628 obj.arguments.push(arg.input.name);
47631 obj.arguments.push(arg.input);
47635 if (obj.arguments.length > 0) {
47640 command: this.name,
47641 workspace: this.workspace.fileId,
47645 .execute(JSON.stringify(execObj))
47646 .then(function (result) {
47647 console.log(result);
47650 CommandController.prototype.removeMySelf = function (index) {
47651 this.remove()(index, this.list);
47653 CommandController.prototype.reloadFiles = function () {
47655 var fileId = this.workspace.fileId;
47659 .then(function (result) {
47660 var status = result.status;
47661 if (status === 'success') {
47662 _this.files = result.info;
47665 console.log(result.message);
47669 CommandController.prototype.debug = function () {
47672 .then(function (result) {
47673 console.log(result);
47676 CommandController.prototype.help = function () {
47679 .then(function (result) {
47680 console.log(result);
47683 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
47684 return CommandController;
47686 directives.CommandController = CommandController;
47687 })(directives = app.directives || (app.directives = {}));
47688 })(app || (app = {}));
47692 (function (directives) {
47693 var HeaderMenu = (function () {
47694 function HeaderMenu() {
47695 this.restrict = 'E';
47696 this.replace = true;
47697 this.templateUrl = 'templates/header-menu.html';
47698 this.controller = 'HeaderMenuController';
47699 this.controllerAs = 'hmc';
47702 HeaderMenu.Factory = function () {
47703 var directive = function () {
47704 return new HeaderMenu();
47710 directives.HeaderMenu = HeaderMenu;
47711 var HeaderMenuController = (function () {
47712 function HeaderMenuController($state) {
47713 this.$state = $state;
47714 this.isExecution = this.$state.current.name === 'execution';
47715 this.isWorkspace = this.$state.current.name === 'workspace';
47716 this.isHistory = this.$state.current.name === 'history';
47718 HeaderMenuController.prototype.transit = function (state) {
47719 this.$state.go(state);
47721 HeaderMenuController.$inject = ['$state'];
47722 return HeaderMenuController;
47724 directives.HeaderMenuController = HeaderMenuController;
47725 })(directives = app.directives || (app.directives = {}));
47726 })(app || (app = {}));
47730 (function (directives) {
47731 var Option = (function () {
47732 function Option() {
47733 this.restrict = 'E';
47734 this.replace = true;
47735 this.controller = 'optionController';
47736 this.bindToController = {
47741 this.templateUrl = 'templates/option.html';
47742 this.controllerAs = 'ctrl';
47744 Option.Factory = function () {
47745 var directive = function () {
47746 return new Option();
47748 directive.$inject = [];
47753 directives.Option = Option;
47754 var OptionController = (function () {
47755 function OptionController() {
47756 var controller = this;
47757 angular.forEach(controller.info.arg, function (arg) {
47758 if (arg.initialValue) {
47759 if (arg.formType === 'number') {
47760 arg.input = parseInt(arg.initialValue);
47763 arg.input = arg.initialValue;
47768 OptionController.$inject = [];
47769 return OptionController;
47771 directives.OptionController = OptionController;
47772 })(directives = app.directives || (app.directives = {}));
47773 })(app || (app = {}));
47777 (function (directives) {
47778 var Directory = (function () {
47779 function Directory() {
47780 this.restrict = 'E';
47781 this.replace = true;
47782 this.controller = 'directoryController';
47783 this.controllerAs = 'ctrl';
47784 this.bindToController = {
47790 this.templateUrl = 'templates/directory.html';
47792 Directory.Factory = function () {
47793 var directive = function () {
47794 return new Directory();
47800 directives.Directory = Directory;
47801 var DirectoryController = (function () {
47802 function DirectoryController(APIEndPoint, $scope) {
47803 this.APIEndPoint = APIEndPoint;
47804 this.$scope = $scope;
47805 var controller = this;
47807 .getFiles(this.info.fileId)
47809 .then(function (result) {
47810 if (result.status === 'success') {
47811 controller.files = result.info;
47812 angular.forEach(result.info, function (file) {
47813 if (file.fileType === '0') {
47815 if (controller.info.path === '/') {
47816 o.path = '/' + file.name;
47819 o.path = controller.info.path + '/' + file.name;
47821 controller.add()(o, controller.list);
47828 DirectoryController.$inject = ['APIEndPoint', '$scope'];
47829 return DirectoryController;
47831 directives.DirectoryController = DirectoryController;
47832 })(directives = app.directives || (app.directives = {}));
47833 })(app || (app = {}));
47837 (function (controllers) {
47838 var Execution = (function () {
47839 function Execution(MyModal, $scope) {
47840 this.MyModal = MyModal;
47841 this.$scope = $scope;
47842 this.commandInfoList = [];
47845 Execution.prototype.add = function () {
47846 this.$scope.$broadcast('close');
47847 var commandInfoList = this.commandInfoList;
47848 var commandInstance = this.MyModal.selectCommand();
47851 .then(function (command) {
47852 commandInfoList.push(new app.declares.CommandInfo(command));
47855 Execution.prototype.open = function () {
47856 var result = this.MyModal.open('SelectCommand');
47857 console.log(result);
47859 Execution.prototype.remove = function (index, list) {
47860 list.splice(index, 1);
47862 Execution.prototype.close = function () {
47863 console.log("close");
47865 Execution.$inject = ['MyModal', '$scope'];
47868 controllers.Execution = Execution;
47869 })(controllers = app.controllers || (app.controllers = {}));
47870 })(app || (app = {}));
47874 (function (controllers) {
47875 var Workspace = (function () {
47876 function Workspace($scope, APIEndPoint, MyModal) {
47877 this.$scope = $scope;
47878 this.APIEndPoint = APIEndPoint;
47879 this.MyModal = MyModal;
47880 this.directoryList = [];
47881 var controller = this;
47882 var directoryList = this.directoryList;
47884 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
47892 directoryList.push(o);
47894 Workspace.prototype.addDirectory = function (info, directoryList) {
47895 directoryList.push(info);
47897 Workspace.prototype.debug = function () {
47898 this.MyModal.preview();
47900 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
47903 controllers.Workspace = Workspace;
47904 })(controllers = app.controllers || (app.controllers = {}));
47905 })(app || (app = {}));
47909 (function (controllers) {
47910 var History = (function () {
47911 function History($scope) {
47912 this.page = "History";
47914 History.$inject = ['$scope'];
47917 controllers.History = History;
47918 })(controllers = app.controllers || (app.controllers = {}));
47919 })(app || (app = {}));
47923 (function (controllers) {
47924 var SelectCommand = (function () {
47925 function SelectCommand($scope, APIEndPoint, $modalInstance) {
47926 this.APIEndPoint = APIEndPoint;
47927 this.$modalInstance = $modalInstance;
47928 var controller = this;
47931 .$promise.then(function (result) {
47932 controller.tags = result.info;
47936 .$promise.then(function (result) {
47937 controller.commands = result.info;
47939 this.currentTag = 'all';
47941 SelectCommand.prototype.changeTag = function (tag) {
47942 this.currentTag = tag;
47944 SelectCommand.prototype.selectCommand = function (command) {
47945 this.$modalInstance.close(command);
47947 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
47948 return SelectCommand;
47950 controllers.SelectCommand = SelectCommand;
47951 })(controllers = app.controllers || (app.controllers = {}));
47952 })(app || (app = {}));
47956 (function (controllers) {
47957 var Preview = (function () {
47958 function Preview($scope, APIEndPoint, $modalInstance) {
47959 this.APIEndPoint = APIEndPoint;
47960 this.$modalInstance = $modalInstance;
47961 var controller = this;
47962 console.log('preview');
47964 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
47967 controllers.Preview = Preview;
47968 })(controllers = app.controllers || (app.controllers = {}));
47969 })(app || (app = {}));
47971 (function (filters) {
47973 return function (commands, tag) {
47975 angular.forEach(commands, function (command) {
47977 angular.forEach(command.tags, function (value) {
47982 result.push(command);
47988 })(filters || (filters = {}));
47992 var appName = 'zephyr';
47993 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
47994 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
47995 $urlRouterProvider.otherwise('/execution');
47996 $locationProvider.html5Mode({
48001 .state('execution', {
48003 templateUrl: 'templates/execution.html',
48004 controller: 'executionController',
48007 .state('workspace', {
48009 templateUrl: 'templates/workspace.html',
48010 controller: 'workspaceController',
48013 .state('history', {
48015 templateUrl: 'templates/history.html',
48016 controller: 'historyController',
48020 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
48021 app.zephyr.service('MyModal', app.services.MyModal);
48022 app.zephyr.service('WebSocket', app.services.WebSocket);
48023 app.zephyr.filter('Tag', filters.Tag);
48024 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
48025 app.zephyr.controller('previewController', app.controllers.Preview);
48026 app.zephyr.controller('executionController', app.controllers.Execution);
48027 app.zephyr.controller('workspaceController', app.controllers.Workspace);
48028 app.zephyr.controller('historyController', app.controllers.History);
48029 app.zephyr.controller('commandController', app.directives.CommandController);
48030 app.zephyr.controller('optionController', app.directives.OptionController);
48031 app.zephyr.controller('directoryController', app.directives.DirectoryController);
48032 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
48033 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
48034 app.zephyr.directive('command', app.directives.Command.Factory());
48035 app.zephyr.directive('option', app.directives.Option.Factory());
48036 app.zephyr.directive('directory', app.directives.Directory.Factory());
48037 })(app || (app = {}));
48042 /***/ function(module, exports) {
48047 (function (declares) {
48048 var CommandInfo = (function () {
48049 function CommandInfo(name) {
48052 return CommandInfo;
48054 declares.CommandInfo = CommandInfo;
48055 })(declares = app.declares || (app.declares = {}));
48056 })(app || (app = {}));
48060 (function (services) {
48061 var APIEndPoint = (function () {
48062 function APIEndPoint($resource, $http) {
48063 this.$resource = $resource;
48064 this.$http = $http;
48066 APIEndPoint.prototype.resource = function (endPoint, data) {
48067 var customAction = {
48073 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
48075 return this.$resource(endPoint, {}, { execute: execute });
48077 APIEndPoint.prototype.getOptionControlFile = function (command) {
48078 var endPoint = '/api/v1/optionControlFile/' + command;
48079 return this.resource(endPoint, {}).get();
48081 APIEndPoint.prototype.getFiles = function (fileId) {
48082 var endPoint = '/api/v1/workspace';
48084 endPoint += '/' + fileId;
48086 return this.resource(endPoint, {}).get();
48088 APIEndPoint.prototype.getDirectories = function () {
48089 var endPoint = '/api/v1/all/workspace/directory';
48090 return this.resource(endPoint, {}).get();
48092 APIEndPoint.prototype.getTags = function () {
48093 var endPoint = '/api/v1/tagList';
48094 return this.resource(endPoint, {}).get();
48096 APIEndPoint.prototype.getCommands = function () {
48097 var endPoint = '/api/v1/commandList';
48098 return this.resource(endPoint, {}).get();
48100 APIEndPoint.prototype.execute = function (data) {
48101 var endPoint = '/api/v1/execution';
48102 var fd = new FormData();
48103 fd.append('data', data);
48104 return this.$http.post(endPoint, fd, {
48105 headers: { 'Content-Type': undefined },
48106 transformRequest: angular.identity
48109 APIEndPoint.prototype.debug = function () {
48110 var endPoint = '/api/v1/debug';
48111 return this.$http.get(endPoint);
48113 APIEndPoint.prototype.help = function (command) {
48114 var endPoint = '/api/v1/help/' + command;
48115 return this.$http.get(endPoint);
48117 return APIEndPoint;
48119 services.APIEndPoint = APIEndPoint;
48120 })(services = app.services || (app.services = {}));
48121 })(app || (app = {}));
48125 (function (services) {
48126 var MyModal = (function () {
48127 function MyModal($uibModal) {
48128 this.$uibModal = $uibModal;
48129 this.modalOption = {
48136 MyModal.prototype.open = function (modalName) {
48137 if (modalName === 'SelectCommand') {
48138 this.modalOption.templateUrl = 'templates/select-command.html';
48139 this.modalOption.size = 'lg';
48141 return this.$uibModal.open(this.modalOption);
48143 MyModal.prototype.selectCommand = function () {
48144 this.modalOption.templateUrl = 'templates/select-command.html';
48145 this.modalOption.controller = 'selectCommandController';
48146 this.modalOption.controllerAs = 'c';
48147 this.modalOption.size = 'lg';
48148 return this.$uibModal.open(this.modalOption);
48150 MyModal.prototype.preview = function () {
48151 this.modalOption.templateUrl = 'templates/preview.html';
48152 this.modalOption.controller = 'previewController';
48153 this.modalOption.controllerAs = 'c';
48154 this.modalOption.size = 'lg';
48155 return this.$uibModal.open(this.modalOption);
48157 MyModal.$inject = ['$uibModal'];
48160 services.MyModal = MyModal;
48161 })(services = app.services || (app.services = {}));
48162 })(app || (app = {}));
48166 (function (services) {
48167 var WebSocket = (function () {
48168 function WebSocket($rootScope) {
48169 this.$rootScope = $rootScope;
48170 this.socket = io.connect();
48172 WebSocket.prototype.on = function (eventName, callback) {
48173 var socket = this.socket;
48174 var rootScope = this.$rootScope;
48175 socket.on(eventName, function () {
48176 var args = arguments;
48177 rootScope.$apply(function () {
48178 callback.apply(socket, args);
48182 WebSocket.prototype.emit = function (eventName, data, callback) {
48183 var socket = this.socket;
48184 var rootScope = this.$rootScope;
48185 this.socket.emit(eventName, data, function () {
48186 var args = arguments;
48187 rootScope.$apply(function () {
48189 callback.apply(socket, args);
48195 services.WebSocket = WebSocket;
48196 })(services = app.services || (app.services = {}));
48197 })(app || (app = {}));
48201 (function (directives) {
48202 var Command = (function () {
48203 function Command() {
48204 this.restrict = 'E';
48205 this.replace = true;
48207 this.controller = 'commandController';
48208 this.controllerAs = 'ctrl';
48209 this.bindToController = {
48215 this.templateUrl = 'templates/command.html';
48217 Command.Factory = function () {
48218 var directive = function () {
48219 return new Command();
48221 directive.$inject = [];
48226 directives.Command = Command;
48227 var CommandController = (function () {
48228 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
48229 this.APIEndPoint = APIEndPoint;
48230 this.$scope = $scope;
48231 this.MyModal = MyModal;
48232 this.WebSocket = WebSocket;
48233 var controller = this;
48235 .getOptionControlFile(this.name)
48237 .then(function (result) {
48238 controller.options = result.info;
48243 .then(function (result) {
48244 controller.dirs = result.info;
48246 this.heading = "[" + this.index + "]: dcdFilePrint";
48247 this.isOpen = true;
48248 this.$scope.$on('close', function () {
48249 controller.isOpen = false;
48251 this.WebSocket.on('console', function (msg) {
48252 var messages = msg.split('\n');
48253 if (messages[0].substr(0, 6) === 'Usage:') {
48254 controller.messages = msg.split('\n');
48258 CommandController.prototype.submit = function () {
48260 angular.forEach(this.options, function (option) {
48262 name: option.option,
48265 angular.forEach(option.arg, function (arg) {
48267 if (typeof arg.input === 'object') {
48268 obj.arguments.push(arg.input.name);
48271 obj.arguments.push(arg.input);
48275 if (obj.arguments.length > 0) {
48280 command: this.name,
48281 workspace: this.workspace.fileId,
48285 .execute(JSON.stringify(execObj))
48286 .then(function (result) {
48287 console.log(result);
48290 CommandController.prototype.removeMySelf = function (index) {
48291 this.remove()(index, this.list);
48293 CommandController.prototype.reloadFiles = function () {
48295 var fileId = this.workspace.fileId;
48299 .then(function (result) {
48300 var status = result.status;
48301 if (status === 'success') {
48302 _this.files = result.info;
48305 console.log(result.message);
48309 CommandController.prototype.debug = function () {
48312 .then(function (result) {
48313 console.log(result);
48316 CommandController.prototype.help = function () {
48319 .then(function (result) {
48320 console.log(result);
48323 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
48324 return CommandController;
48326 directives.CommandController = CommandController;
48327 })(directives = app.directives || (app.directives = {}));
48328 })(app || (app = {}));
48332 (function (directives) {
48333 var HeaderMenu = (function () {
48334 function HeaderMenu() {
48335 this.restrict = 'E';
48336 this.replace = true;
48337 this.templateUrl = 'templates/header-menu.html';
48338 this.controller = 'HeaderMenuController';
48339 this.controllerAs = 'hmc';
48342 HeaderMenu.Factory = function () {
48343 var directive = function () {
48344 return new HeaderMenu();
48350 directives.HeaderMenu = HeaderMenu;
48351 var HeaderMenuController = (function () {
48352 function HeaderMenuController($state) {
48353 this.$state = $state;
48354 this.isExecution = this.$state.current.name === 'execution';
48355 this.isWorkspace = this.$state.current.name === 'workspace';
48356 this.isHistory = this.$state.current.name === 'history';
48358 HeaderMenuController.prototype.transit = function (state) {
48359 this.$state.go(state);
48361 HeaderMenuController.$inject = ['$state'];
48362 return HeaderMenuController;
48364 directives.HeaderMenuController = HeaderMenuController;
48365 })(directives = app.directives || (app.directives = {}));
48366 })(app || (app = {}));
48370 (function (directives) {
48371 var Option = (function () {
48372 function Option() {
48373 this.restrict = 'E';
48374 this.replace = true;
48375 this.controller = 'optionController';
48376 this.bindToController = {
48381 this.templateUrl = 'templates/option.html';
48382 this.controllerAs = 'ctrl';
48384 Option.Factory = function () {
48385 var directive = function () {
48386 return new Option();
48388 directive.$inject = [];
48393 directives.Option = Option;
48394 var OptionController = (function () {
48395 function OptionController() {
48396 var controller = this;
48397 angular.forEach(controller.info.arg, function (arg) {
48398 if (arg.initialValue) {
48399 if (arg.formType === 'number') {
48400 arg.input = parseInt(arg.initialValue);
48403 arg.input = arg.initialValue;
48408 OptionController.$inject = [];
48409 return OptionController;
48411 directives.OptionController = OptionController;
48412 })(directives = app.directives || (app.directives = {}));
48413 })(app || (app = {}));
48417 (function (directives) {
48418 var Directory = (function () {
48419 function Directory() {
48420 this.restrict = 'E';
48421 this.replace = true;
48422 this.controller = 'directoryController';
48423 this.controllerAs = 'ctrl';
48424 this.bindToController = {
48430 this.templateUrl = 'templates/directory.html';
48432 Directory.Factory = function () {
48433 var directive = function () {
48434 return new Directory();
48440 directives.Directory = Directory;
48441 var DirectoryController = (function () {
48442 function DirectoryController(APIEndPoint, $scope) {
48443 this.APIEndPoint = APIEndPoint;
48444 this.$scope = $scope;
48445 var controller = this;
48447 .getFiles(this.info.fileId)
48449 .then(function (result) {
48450 if (result.status === 'success') {
48451 controller.files = result.info;
48452 angular.forEach(result.info, function (file) {
48453 if (file.fileType === '0') {
48455 if (controller.info.path === '/') {
48456 o.path = '/' + file.name;
48459 o.path = controller.info.path + '/' + file.name;
48461 controller.add()(o, controller.list);
48468 DirectoryController.$inject = ['APIEndPoint', '$scope'];
48469 return DirectoryController;
48471 directives.DirectoryController = DirectoryController;
48472 })(directives = app.directives || (app.directives = {}));
48473 })(app || (app = {}));
48477 (function (controllers) {
48478 var Execution = (function () {
48479 function Execution(MyModal, $scope) {
48480 this.MyModal = MyModal;
48481 this.$scope = $scope;
48482 this.commandInfoList = [];
48485 Execution.prototype.add = function () {
48486 this.$scope.$broadcast('close');
48487 var commandInfoList = this.commandInfoList;
48488 var commandInstance = this.MyModal.selectCommand();
48491 .then(function (command) {
48492 commandInfoList.push(new app.declares.CommandInfo(command));
48495 Execution.prototype.open = function () {
48496 var result = this.MyModal.open('SelectCommand');
48497 console.log(result);
48499 Execution.prototype.remove = function (index, list) {
48500 list.splice(index, 1);
48502 Execution.prototype.close = function () {
48503 console.log("close");
48505 Execution.$inject = ['MyModal', '$scope'];
48508 controllers.Execution = Execution;
48509 })(controllers = app.controllers || (app.controllers = {}));
48510 })(app || (app = {}));
48514 (function (controllers) {
48515 var Workspace = (function () {
48516 function Workspace($scope, APIEndPoint, MyModal) {
48517 this.$scope = $scope;
48518 this.APIEndPoint = APIEndPoint;
48519 this.MyModal = MyModal;
48520 this.directoryList = [];
48521 var controller = this;
48522 var directoryList = this.directoryList;
48524 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
48532 directoryList.push(o);
48534 Workspace.prototype.addDirectory = function (info, directoryList) {
48535 directoryList.push(info);
48537 Workspace.prototype.debug = function () {
48538 this.MyModal.preview();
48540 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
48543 controllers.Workspace = Workspace;
48544 })(controllers = app.controllers || (app.controllers = {}));
48545 })(app || (app = {}));
48549 (function (controllers) {
48550 var History = (function () {
48551 function History($scope) {
48552 this.page = "History";
48554 History.$inject = ['$scope'];
48557 controllers.History = History;
48558 })(controllers = app.controllers || (app.controllers = {}));
48559 })(app || (app = {}));
48563 (function (controllers) {
48564 var SelectCommand = (function () {
48565 function SelectCommand($scope, APIEndPoint, $modalInstance) {
48566 this.APIEndPoint = APIEndPoint;
48567 this.$modalInstance = $modalInstance;
48568 var controller = this;
48571 .$promise.then(function (result) {
48572 controller.tags = result.info;
48576 .$promise.then(function (result) {
48577 controller.commands = result.info;
48579 this.currentTag = 'all';
48581 SelectCommand.prototype.changeTag = function (tag) {
48582 this.currentTag = tag;
48584 SelectCommand.prototype.selectCommand = function (command) {
48585 this.$modalInstance.close(command);
48587 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
48588 return SelectCommand;
48590 controllers.SelectCommand = SelectCommand;
48591 })(controllers = app.controllers || (app.controllers = {}));
48592 })(app || (app = {}));
48596 (function (controllers) {
48597 var Preview = (function () {
48598 function Preview($scope, APIEndPoint, $modalInstance) {
48599 this.APIEndPoint = APIEndPoint;
48600 this.$modalInstance = $modalInstance;
48601 var controller = this;
48602 console.log('preview');
48604 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
48607 controllers.Preview = Preview;
48608 })(controllers = app.controllers || (app.controllers = {}));
48609 })(app || (app = {}));
48611 (function (filters) {
48613 return function (commands, tag) {
48615 angular.forEach(commands, function (command) {
48617 angular.forEach(command.tags, function (value) {
48622 result.push(command);
48628 })(filters || (filters = {}));
48632 var appName = 'zephyr';
48633 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
48634 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
48635 $urlRouterProvider.otherwise('/execution');
48636 $locationProvider.html5Mode({
48641 .state('execution', {
48643 templateUrl: 'templates/execution.html',
48644 controller: 'executionController',
48647 .state('workspace', {
48649 templateUrl: 'templates/workspace.html',
48650 controller: 'workspaceController',
48653 .state('history', {
48655 templateUrl: 'templates/history.html',
48656 controller: 'historyController',
48660 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
48661 app.zephyr.service('MyModal', app.services.MyModal);
48662 app.zephyr.service('WebSocket', app.services.WebSocket);
48663 app.zephyr.filter('Tag', filters.Tag);
48664 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
48665 app.zephyr.controller('previewController', app.controllers.Preview);
48666 app.zephyr.controller('executionController', app.controllers.Execution);
48667 app.zephyr.controller('workspaceController', app.controllers.Workspace);
48668 app.zephyr.controller('historyController', app.controllers.History);
48669 app.zephyr.controller('commandController', app.directives.CommandController);
48670 app.zephyr.controller('optionController', app.directives.OptionController);
48671 app.zephyr.controller('directoryController', app.directives.DirectoryController);
48672 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
48673 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
48674 app.zephyr.directive('command', app.directives.Command.Factory());
48675 app.zephyr.directive('option', app.directives.Option.Factory());
48676 app.zephyr.directive('directory', app.directives.Directory.Factory());
48677 })(app || (app = {}));
48682 /***/ function(module, exports) {
48687 (function (declares) {
48688 var CommandInfo = (function () {
48689 function CommandInfo(name) {
48692 return CommandInfo;
48694 declares.CommandInfo = CommandInfo;
48695 })(declares = app.declares || (app.declares = {}));
48696 })(app || (app = {}));
48700 (function (services) {
48701 var APIEndPoint = (function () {
48702 function APIEndPoint($resource, $http) {
48703 this.$resource = $resource;
48704 this.$http = $http;
48706 APIEndPoint.prototype.resource = function (endPoint, data) {
48707 var customAction = {
48713 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
48715 return this.$resource(endPoint, {}, { execute: execute });
48717 APIEndPoint.prototype.getOptionControlFile = function (command) {
48718 var endPoint = '/api/v1/optionControlFile/' + command;
48719 return this.resource(endPoint, {}).get();
48721 APIEndPoint.prototype.getFiles = function (fileId) {
48722 var endPoint = '/api/v1/workspace';
48724 endPoint += '/' + fileId;
48726 return this.resource(endPoint, {}).get();
48728 APIEndPoint.prototype.getDirectories = function () {
48729 var endPoint = '/api/v1/all/workspace/directory';
48730 return this.resource(endPoint, {}).get();
48732 APIEndPoint.prototype.getTags = function () {
48733 var endPoint = '/api/v1/tagList';
48734 return this.resource(endPoint, {}).get();
48736 APIEndPoint.prototype.getCommands = function () {
48737 var endPoint = '/api/v1/commandList';
48738 return this.resource(endPoint, {}).get();
48740 APIEndPoint.prototype.execute = function (data) {
48741 var endPoint = '/api/v1/execution';
48742 var fd = new FormData();
48743 fd.append('data', data);
48744 return this.$http.post(endPoint, fd, {
48745 headers: { 'Content-Type': undefined },
48746 transformRequest: angular.identity
48749 APIEndPoint.prototype.debug = function () {
48750 var endPoint = '/api/v1/debug';
48751 return this.$http.get(endPoint);
48753 APIEndPoint.prototype.help = function (command) {
48754 var endPoint = '/api/v1/help/' + command;
48755 return this.$http.get(endPoint);
48757 return APIEndPoint;
48759 services.APIEndPoint = APIEndPoint;
48760 })(services = app.services || (app.services = {}));
48761 })(app || (app = {}));
48765 (function (services) {
48766 var MyModal = (function () {
48767 function MyModal($uibModal) {
48768 this.$uibModal = $uibModal;
48769 this.modalOption = {
48776 MyModal.prototype.open = function (modalName) {
48777 if (modalName === 'SelectCommand') {
48778 this.modalOption.templateUrl = 'templates/select-command.html';
48779 this.modalOption.size = 'lg';
48781 return this.$uibModal.open(this.modalOption);
48783 MyModal.prototype.selectCommand = function () {
48784 this.modalOption.templateUrl = 'templates/select-command.html';
48785 this.modalOption.controller = 'selectCommandController';
48786 this.modalOption.controllerAs = 'c';
48787 this.modalOption.size = 'lg';
48788 return this.$uibModal.open(this.modalOption);
48790 MyModal.prototype.preview = function () {
48791 this.modalOption.templateUrl = 'templates/preview.html';
48792 this.modalOption.controller = 'previewController';
48793 this.modalOption.controllerAs = 'c';
48794 this.modalOption.size = 'lg';
48795 return this.$uibModal.open(this.modalOption);
48797 MyModal.$inject = ['$uibModal'];
48800 services.MyModal = MyModal;
48801 })(services = app.services || (app.services = {}));
48802 })(app || (app = {}));
48806 (function (services) {
48807 var WebSocket = (function () {
48808 function WebSocket($rootScope) {
48809 this.$rootScope = $rootScope;
48810 this.socket = io.connect();
48812 WebSocket.prototype.on = function (eventName, callback) {
48813 var socket = this.socket;
48814 var rootScope = this.$rootScope;
48815 socket.on(eventName, function () {
48816 var args = arguments;
48817 rootScope.$apply(function () {
48818 callback.apply(socket, args);
48822 WebSocket.prototype.emit = function (eventName, data, callback) {
48823 var socket = this.socket;
48824 var rootScope = this.$rootScope;
48825 this.socket.emit(eventName, data, function () {
48826 var args = arguments;
48827 rootScope.$apply(function () {
48829 callback.apply(socket, args);
48835 services.WebSocket = WebSocket;
48836 })(services = app.services || (app.services = {}));
48837 })(app || (app = {}));
48841 (function (directives) {
48842 var Command = (function () {
48843 function Command() {
48844 this.restrict = 'E';
48845 this.replace = true;
48847 this.controller = 'commandController';
48848 this.controllerAs = 'ctrl';
48849 this.bindToController = {
48855 this.templateUrl = 'templates/command.html';
48857 Command.Factory = function () {
48858 var directive = function () {
48859 return new Command();
48861 directive.$inject = [];
48866 directives.Command = Command;
48867 var CommandController = (function () {
48868 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
48869 this.APIEndPoint = APIEndPoint;
48870 this.$scope = $scope;
48871 this.MyModal = MyModal;
48872 this.WebSocket = WebSocket;
48873 var controller = this;
48875 .getOptionControlFile(this.name)
48877 .then(function (result) {
48878 controller.options = result.info;
48883 .then(function (result) {
48884 controller.dirs = result.info;
48886 this.heading = "[" + this.index + "]: dcdFilePrint";
48887 this.isOpen = true;
48888 this.$scope.$on('close', function () {
48889 controller.isOpen = false;
48891 this.WebSocket.on('console', function (msg) {
48892 var messages = msg.split('\n');
48893 if (messages[0].substr(0, 6) === 'Usage:') {
48894 controller.messages = msg.split('\n');
48898 CommandController.prototype.submit = function () {
48900 angular.forEach(this.options, function (option) {
48902 name: option.option,
48905 angular.forEach(option.arg, function (arg) {
48907 if (typeof arg.input === 'object') {
48908 obj.arguments.push(arg.input.name);
48911 obj.arguments.push(arg.input);
48915 if (obj.arguments.length > 0) {
48920 command: this.name,
48921 workspace: this.workspace.fileId,
48925 .execute(JSON.stringify(execObj))
48926 .then(function (result) {
48927 console.log(result);
48930 CommandController.prototype.removeMySelf = function (index) {
48931 this.remove()(index, this.list);
48933 CommandController.prototype.reloadFiles = function () {
48935 var fileId = this.workspace.fileId;
48939 .then(function (result) {
48940 var status = result.status;
48941 if (status === 'success') {
48942 _this.files = result.info;
48945 console.log(result.message);
48949 CommandController.prototype.debug = function () {
48952 .then(function (result) {
48953 console.log(result);
48956 CommandController.prototype.help = function () {
48959 .then(function (result) {
48960 console.log(result);
48963 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
48964 return CommandController;
48966 directives.CommandController = CommandController;
48967 })(directives = app.directives || (app.directives = {}));
48968 })(app || (app = {}));
48972 (function (directives) {
48973 var HeaderMenu = (function () {
48974 function HeaderMenu() {
48975 this.restrict = 'E';
48976 this.replace = true;
48977 this.templateUrl = 'templates/header-menu.html';
48978 this.controller = 'HeaderMenuController';
48979 this.controllerAs = 'hmc';
48982 HeaderMenu.Factory = function () {
48983 var directive = function () {
48984 return new HeaderMenu();
48990 directives.HeaderMenu = HeaderMenu;
48991 var HeaderMenuController = (function () {
48992 function HeaderMenuController($state) {
48993 this.$state = $state;
48994 this.isExecution = this.$state.current.name === 'execution';
48995 this.isWorkspace = this.$state.current.name === 'workspace';
48996 this.isHistory = this.$state.current.name === 'history';
48998 HeaderMenuController.prototype.transit = function (state) {
48999 this.$state.go(state);
49001 HeaderMenuController.$inject = ['$state'];
49002 return HeaderMenuController;
49004 directives.HeaderMenuController = HeaderMenuController;
49005 })(directives = app.directives || (app.directives = {}));
49006 })(app || (app = {}));
49010 (function (directives) {
49011 var Option = (function () {
49012 function Option() {
49013 this.restrict = 'E';
49014 this.replace = true;
49015 this.controller = 'optionController';
49016 this.bindToController = {
49021 this.templateUrl = 'templates/option.html';
49022 this.controllerAs = 'ctrl';
49024 Option.Factory = function () {
49025 var directive = function () {
49026 return new Option();
49028 directive.$inject = [];
49033 directives.Option = Option;
49034 var OptionController = (function () {
49035 function OptionController() {
49036 var controller = this;
49037 angular.forEach(controller.info.arg, function (arg) {
49038 if (arg.initialValue) {
49039 if (arg.formType === 'number') {
49040 arg.input = parseInt(arg.initialValue);
49043 arg.input = arg.initialValue;
49048 OptionController.$inject = [];
49049 return OptionController;
49051 directives.OptionController = OptionController;
49052 })(directives = app.directives || (app.directives = {}));
49053 })(app || (app = {}));
49057 (function (directives) {
49058 var Directory = (function () {
49059 function Directory() {
49060 this.restrict = 'E';
49061 this.replace = true;
49062 this.controller = 'directoryController';
49063 this.controllerAs = 'ctrl';
49064 this.bindToController = {
49070 this.templateUrl = 'templates/directory.html';
49072 Directory.Factory = function () {
49073 var directive = function () {
49074 return new Directory();
49080 directives.Directory = Directory;
49081 var DirectoryController = (function () {
49082 function DirectoryController(APIEndPoint, $scope) {
49083 this.APIEndPoint = APIEndPoint;
49084 this.$scope = $scope;
49085 var controller = this;
49087 .getFiles(this.info.fileId)
49089 .then(function (result) {
49090 if (result.status === 'success') {
49091 controller.files = result.info;
49092 angular.forEach(result.info, function (file) {
49093 if (file.fileType === '0') {
49095 if (controller.info.path === '/') {
49096 o.path = '/' + file.name;
49099 o.path = controller.info.path + '/' + file.name;
49101 controller.add()(o, controller.list);
49108 DirectoryController.$inject = ['APIEndPoint', '$scope'];
49109 return DirectoryController;
49111 directives.DirectoryController = DirectoryController;
49112 })(directives = app.directives || (app.directives = {}));
49113 })(app || (app = {}));
49117 (function (controllers) {
49118 var Execution = (function () {
49119 function Execution(MyModal, $scope) {
49120 this.MyModal = MyModal;
49121 this.$scope = $scope;
49122 this.commandInfoList = [];
49125 Execution.prototype.add = function () {
49126 this.$scope.$broadcast('close');
49127 var commandInfoList = this.commandInfoList;
49128 var commandInstance = this.MyModal.selectCommand();
49131 .then(function (command) {
49132 commandInfoList.push(new app.declares.CommandInfo(command));
49135 Execution.prototype.open = function () {
49136 var result = this.MyModal.open('SelectCommand');
49137 console.log(result);
49139 Execution.prototype.remove = function (index, list) {
49140 list.splice(index, 1);
49142 Execution.prototype.close = function () {
49143 console.log("close");
49145 Execution.$inject = ['MyModal', '$scope'];
49148 controllers.Execution = Execution;
49149 })(controllers = app.controllers || (app.controllers = {}));
49150 })(app || (app = {}));
49154 (function (controllers) {
49155 var Workspace = (function () {
49156 function Workspace($scope, APIEndPoint, MyModal) {
49157 this.$scope = $scope;
49158 this.APIEndPoint = APIEndPoint;
49159 this.MyModal = MyModal;
49160 this.directoryList = [];
49161 var controller = this;
49162 var directoryList = this.directoryList;
49164 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
49172 directoryList.push(o);
49174 Workspace.prototype.addDirectory = function (info, directoryList) {
49175 directoryList.push(info);
49177 Workspace.prototype.debug = function () {
49178 this.MyModal.preview();
49180 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
49183 controllers.Workspace = Workspace;
49184 })(controllers = app.controllers || (app.controllers = {}));
49185 })(app || (app = {}));
49189 (function (controllers) {
49190 var History = (function () {
49191 function History($scope) {
49192 this.page = "History";
49194 History.$inject = ['$scope'];
49197 controllers.History = History;
49198 })(controllers = app.controllers || (app.controllers = {}));
49199 })(app || (app = {}));
49203 (function (controllers) {
49204 var SelectCommand = (function () {
49205 function SelectCommand($scope, APIEndPoint, $modalInstance) {
49206 this.APIEndPoint = APIEndPoint;
49207 this.$modalInstance = $modalInstance;
49208 var controller = this;
49211 .$promise.then(function (result) {
49212 controller.tags = result.info;
49216 .$promise.then(function (result) {
49217 controller.commands = result.info;
49219 this.currentTag = 'all';
49221 SelectCommand.prototype.changeTag = function (tag) {
49222 this.currentTag = tag;
49224 SelectCommand.prototype.selectCommand = function (command) {
49225 this.$modalInstance.close(command);
49227 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
49228 return SelectCommand;
49230 controllers.SelectCommand = SelectCommand;
49231 })(controllers = app.controllers || (app.controllers = {}));
49232 })(app || (app = {}));
49236 (function (controllers) {
49237 var Preview = (function () {
49238 function Preview($scope, APIEndPoint, $modalInstance) {
49239 this.APIEndPoint = APIEndPoint;
49240 this.$modalInstance = $modalInstance;
49241 var controller = this;
49242 console.log('preview');
49244 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
49247 controllers.Preview = Preview;
49248 })(controllers = app.controllers || (app.controllers = {}));
49249 })(app || (app = {}));
49251 (function (filters) {
49253 return function (commands, tag) {
49255 angular.forEach(commands, function (command) {
49257 angular.forEach(command.tags, function (value) {
49262 result.push(command);
49268 })(filters || (filters = {}));
49272 var appName = 'zephyr';
49273 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
49274 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
49275 $urlRouterProvider.otherwise('/execution');
49276 $locationProvider.html5Mode({
49281 .state('execution', {
49283 templateUrl: 'templates/execution.html',
49284 controller: 'executionController',
49287 .state('workspace', {
49289 templateUrl: 'templates/workspace.html',
49290 controller: 'workspaceController',
49293 .state('history', {
49295 templateUrl: 'templates/history.html',
49296 controller: 'historyController',
49300 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
49301 app.zephyr.service('MyModal', app.services.MyModal);
49302 app.zephyr.service('WebSocket', app.services.WebSocket);
49303 app.zephyr.filter('Tag', filters.Tag);
49304 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
49305 app.zephyr.controller('previewController', app.controllers.Preview);
49306 app.zephyr.controller('executionController', app.controllers.Execution);
49307 app.zephyr.controller('workspaceController', app.controllers.Workspace);
49308 app.zephyr.controller('historyController', app.controllers.History);
49309 app.zephyr.controller('commandController', app.directives.CommandController);
49310 app.zephyr.controller('optionController', app.directives.OptionController);
49311 app.zephyr.controller('directoryController', app.directives.DirectoryController);
49312 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
49313 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
49314 app.zephyr.directive('command', app.directives.Command.Factory());
49315 app.zephyr.directive('option', app.directives.Option.Factory());
49316 app.zephyr.directive('directory', app.directives.Directory.Factory());
49317 })(app || (app = {}));
49322 /***/ function(module, exports) {
49327 (function (declares) {
49328 var CommandInfo = (function () {
49329 function CommandInfo(name) {
49332 return CommandInfo;
49334 declares.CommandInfo = CommandInfo;
49335 })(declares = app.declares || (app.declares = {}));
49336 })(app || (app = {}));
49340 (function (services) {
49341 var APIEndPoint = (function () {
49342 function APIEndPoint($resource, $http) {
49343 this.$resource = $resource;
49344 this.$http = $http;
49346 APIEndPoint.prototype.resource = function (endPoint, data) {
49347 var customAction = {
49353 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
49355 return this.$resource(endPoint, {}, { execute: execute });
49357 APIEndPoint.prototype.getOptionControlFile = function (command) {
49358 var endPoint = '/api/v1/optionControlFile/' + command;
49359 return this.resource(endPoint, {}).get();
49361 APIEndPoint.prototype.getFiles = function (fileId) {
49362 var endPoint = '/api/v1/workspace';
49364 endPoint += '/' + fileId;
49366 return this.resource(endPoint, {}).get();
49368 APIEndPoint.prototype.getDirectories = function () {
49369 var endPoint = '/api/v1/all/workspace/directory';
49370 return this.resource(endPoint, {}).get();
49372 APIEndPoint.prototype.getTags = function () {
49373 var endPoint = '/api/v1/tagList';
49374 return this.resource(endPoint, {}).get();
49376 APIEndPoint.prototype.getCommands = function () {
49377 var endPoint = '/api/v1/commandList';
49378 return this.resource(endPoint, {}).get();
49380 APIEndPoint.prototype.execute = function (data) {
49381 var endPoint = '/api/v1/execution';
49382 var fd = new FormData();
49383 fd.append('data', data);
49384 return this.$http.post(endPoint, fd, {
49385 headers: { 'Content-Type': undefined },
49386 transformRequest: angular.identity
49389 APIEndPoint.prototype.debug = function () {
49390 var endPoint = '/api/v1/debug';
49391 return this.$http.get(endPoint);
49393 APIEndPoint.prototype.help = function (command) {
49394 var endPoint = '/api/v1/help/' + command;
49395 return this.$http.get(endPoint);
49397 return APIEndPoint;
49399 services.APIEndPoint = APIEndPoint;
49400 })(services = app.services || (app.services = {}));
49401 })(app || (app = {}));
49405 (function (services) {
49406 var MyModal = (function () {
49407 function MyModal($uibModal) {
49408 this.$uibModal = $uibModal;
49409 this.modalOption = {
49416 MyModal.prototype.open = function (modalName) {
49417 if (modalName === 'SelectCommand') {
49418 this.modalOption.templateUrl = 'templates/select-command.html';
49419 this.modalOption.size = 'lg';
49421 return this.$uibModal.open(this.modalOption);
49423 MyModal.prototype.selectCommand = function () {
49424 this.modalOption.templateUrl = 'templates/select-command.html';
49425 this.modalOption.controller = 'selectCommandController';
49426 this.modalOption.controllerAs = 'c';
49427 this.modalOption.size = 'lg';
49428 return this.$uibModal.open(this.modalOption);
49430 MyModal.prototype.preview = function () {
49431 this.modalOption.templateUrl = 'templates/preview.html';
49432 this.modalOption.controller = 'previewController';
49433 this.modalOption.controllerAs = 'c';
49434 this.modalOption.size = 'lg';
49435 return this.$uibModal.open(this.modalOption);
49437 MyModal.$inject = ['$uibModal'];
49440 services.MyModal = MyModal;
49441 })(services = app.services || (app.services = {}));
49442 })(app || (app = {}));
49446 (function (services) {
49447 var WebSocket = (function () {
49448 function WebSocket($rootScope) {
49449 this.$rootScope = $rootScope;
49450 this.socket = io.connect();
49452 WebSocket.prototype.on = function (eventName, callback) {
49453 var socket = this.socket;
49454 var rootScope = this.$rootScope;
49455 socket.on(eventName, function () {
49456 var args = arguments;
49457 rootScope.$apply(function () {
49458 callback.apply(socket, args);
49462 WebSocket.prototype.emit = function (eventName, data, callback) {
49463 var socket = this.socket;
49464 var rootScope = this.$rootScope;
49465 this.socket.emit(eventName, data, function () {
49466 var args = arguments;
49467 rootScope.$apply(function () {
49469 callback.apply(socket, args);
49475 services.WebSocket = WebSocket;
49476 })(services = app.services || (app.services = {}));
49477 })(app || (app = {}));
49481 (function (directives) {
49482 var Command = (function () {
49483 function Command() {
49484 this.restrict = 'E';
49485 this.replace = true;
49487 this.controller = 'commandController';
49488 this.controllerAs = 'ctrl';
49489 this.bindToController = {
49495 this.templateUrl = 'templates/command.html';
49497 Command.Factory = function () {
49498 var directive = function () {
49499 return new Command();
49501 directive.$inject = [];
49506 directives.Command = Command;
49507 var CommandController = (function () {
49508 function CommandController(APIEndPoint, $scope, MyModal, WebSocket) {
49509 this.APIEndPoint = APIEndPoint;
49510 this.$scope = $scope;
49511 this.MyModal = MyModal;
49512 this.WebSocket = WebSocket;
49513 var controller = this;
49515 .getOptionControlFile(this.name)
49517 .then(function (result) {
49518 controller.options = result.info;
49523 .then(function (result) {
49524 controller.dirs = result.info;
49526 this.heading = "[" + this.index + "]: dcdFilePrint";
49527 this.isOpen = true;
49528 this.$scope.$on('close', function () {
49529 controller.isOpen = false;
49531 this.WebSocket.on('console', function (msg) {
49532 var messages = msg.split('\n');
49533 if (messages[0].substr(0, 6) === 'Usage:') {
49534 controller.messages = msg.split('\n');
49538 CommandController.prototype.submit = function () {
49540 angular.forEach(this.options, function (option) {
49542 name: option.option,
49545 angular.forEach(option.arg, function (arg) {
49547 if (typeof arg.input === 'object') {
49548 obj.arguments.push(arg.input.name);
49551 obj.arguments.push(arg.input);
49555 if (obj.arguments.length > 0) {
49560 command: this.name,
49561 workspace: this.workspace.fileId,
49565 .execute(JSON.stringify(execObj))
49566 .then(function (result) {
49567 console.log(result);
49570 CommandController.prototype.removeMySelf = function (index) {
49571 this.remove()(index, this.list);
49573 CommandController.prototype.reloadFiles = function () {
49575 var fileId = this.workspace.fileId;
49579 .then(function (result) {
49580 var status = result.status;
49581 if (status === 'success') {
49582 _this.files = result.info;
49585 console.log(result.message);
49589 CommandController.prototype.debug = function () {
49592 .then(function (result) {
49593 console.log(result);
49596 CommandController.prototype.help = function () {
49599 .then(function (result) {
49600 console.log(result);
49603 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket'];
49604 return CommandController;
49606 directives.CommandController = CommandController;
49607 })(directives = app.directives || (app.directives = {}));
49608 })(app || (app = {}));
49612 (function (directives) {
49613 var HeaderMenu = (function () {
49614 function HeaderMenu() {
49615 this.restrict = 'E';
49616 this.replace = true;
49617 this.templateUrl = 'templates/header-menu.html';
49618 this.controller = 'HeaderMenuController';
49619 this.controllerAs = 'hmc';
49622 HeaderMenu.Factory = function () {
49623 var directive = function () {
49624 return new HeaderMenu();
49630 directives.HeaderMenu = HeaderMenu;
49631 var HeaderMenuController = (function () {
49632 function HeaderMenuController($state) {
49633 this.$state = $state;
49634 this.isExecution = this.$state.current.name === 'execution';
49635 this.isWorkspace = this.$state.current.name === 'workspace';
49636 this.isHistory = this.$state.current.name === 'history';
49638 HeaderMenuController.prototype.transit = function (state) {
49639 this.$state.go(state);
49641 HeaderMenuController.$inject = ['$state'];
49642 return HeaderMenuController;
49644 directives.HeaderMenuController = HeaderMenuController;
49645 })(directives = app.directives || (app.directives = {}));
49646 })(app || (app = {}));
49650 (function (directives) {
49651 var Option = (function () {
49652 function Option() {
49653 this.restrict = 'E';
49654 this.replace = true;
49655 this.controller = 'optionController';
49656 this.bindToController = {
49661 this.templateUrl = 'templates/option.html';
49662 this.controllerAs = 'ctrl';
49664 Option.Factory = function () {
49665 var directive = function () {
49666 return new Option();
49668 directive.$inject = [];
49673 directives.Option = Option;
49674 var OptionController = (function () {
49675 function OptionController() {
49676 var controller = this;
49677 angular.forEach(controller.info.arg, function (arg) {
49678 if (arg.initialValue) {
49679 if (arg.formType === 'number') {
49680 arg.input = parseInt(arg.initialValue);
49683 arg.input = arg.initialValue;
49688 OptionController.$inject = [];
49689 return OptionController;
49691 directives.OptionController = OptionController;
49692 })(directives = app.directives || (app.directives = {}));
49693 })(app || (app = {}));
49697 (function (directives) {
49698 var Directory = (function () {
49699 function Directory() {
49700 this.restrict = 'E';
49701 this.replace = true;
49702 this.controller = 'directoryController';
49703 this.controllerAs = 'ctrl';
49704 this.bindToController = {
49710 this.templateUrl = 'templates/directory.html';
49712 Directory.Factory = function () {
49713 var directive = function () {
49714 return new Directory();
49720 directives.Directory = Directory;
49721 var DirectoryController = (function () {
49722 function DirectoryController(APIEndPoint, $scope) {
49723 this.APIEndPoint = APIEndPoint;
49724 this.$scope = $scope;
49725 var controller = this;
49727 .getFiles(this.info.fileId)
49729 .then(function (result) {
49730 if (result.status === 'success') {
49731 controller.files = result.info;
49732 angular.forEach(result.info, function (file) {
49733 if (file.fileType === '0') {
49735 if (controller.info.path === '/') {
49736 o.path = '/' + file.name;
49739 o.path = controller.info.path + '/' + file.name;
49741 controller.add()(o, controller.list);
49748 DirectoryController.$inject = ['APIEndPoint', '$scope'];
49749 return DirectoryController;
49751 directives.DirectoryController = DirectoryController;
49752 })(directives = app.directives || (app.directives = {}));
49753 })(app || (app = {}));
49757 (function (controllers) {
49758 var Execution = (function () {
49759 function Execution(MyModal, $scope) {
49760 this.MyModal = MyModal;
49761 this.$scope = $scope;
49762 this.commandInfoList = [];
49765 Execution.prototype.add = function () {
49766 this.$scope.$broadcast('close');
49767 var commandInfoList = this.commandInfoList;
49768 var commandInstance = this.MyModal.selectCommand();
49771 .then(function (command) {
49772 commandInfoList.push(new app.declares.CommandInfo(command));
49775 Execution.prototype.open = function () {
49776 var result = this.MyModal.open('SelectCommand');
49777 console.log(result);
49779 Execution.prototype.remove = function (index, list) {
49780 list.splice(index, 1);
49782 Execution.prototype.close = function () {
49783 console.log("close");
49785 Execution.$inject = ['MyModal', '$scope'];
49788 controllers.Execution = Execution;
49789 })(controllers = app.controllers || (app.controllers = {}));
49790 })(app || (app = {}));
49794 (function (controllers) {
49795 var Workspace = (function () {
49796 function Workspace($scope, APIEndPoint, MyModal) {
49797 this.$scope = $scope;
49798 this.APIEndPoint = APIEndPoint;
49799 this.MyModal = MyModal;
49800 this.directoryList = [];
49801 var controller = this;
49802 var directoryList = this.directoryList;
49804 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
49812 directoryList.push(o);
49814 Workspace.prototype.addDirectory = function (info, directoryList) {
49815 directoryList.push(info);
49817 Workspace.prototype.debug = function () {
49818 this.MyModal.preview();
49820 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
49823 controllers.Workspace = Workspace;
49824 })(controllers = app.controllers || (app.controllers = {}));
49825 })(app || (app = {}));
49829 (function (controllers) {
49830 var History = (function () {
49831 function History($scope) {
49832 this.page = "History";
49834 History.$inject = ['$scope'];
49837 controllers.History = History;
49838 })(controllers = app.controllers || (app.controllers = {}));
49839 })(app || (app = {}));
49843 (function (controllers) {
49844 var SelectCommand = (function () {
49845 function SelectCommand($scope, APIEndPoint, $modalInstance) {
49846 this.APIEndPoint = APIEndPoint;
49847 this.$modalInstance = $modalInstance;
49848 var controller = this;
49851 .$promise.then(function (result) {
49852 controller.tags = result.info;
49856 .$promise.then(function (result) {
49857 controller.commands = result.info;
49859 this.currentTag = 'all';
49861 SelectCommand.prototype.changeTag = function (tag) {
49862 this.currentTag = tag;
49864 SelectCommand.prototype.selectCommand = function (command) {
49865 this.$modalInstance.close(command);
49867 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
49868 return SelectCommand;
49870 controllers.SelectCommand = SelectCommand;
49871 })(controllers = app.controllers || (app.controllers = {}));
49872 })(app || (app = {}));
49876 (function (controllers) {
49877 var Preview = (function () {
49878 function Preview($scope, APIEndPoint, $modalInstance) {
49879 this.APIEndPoint = APIEndPoint;
49880 this.$modalInstance = $modalInstance;
49881 var controller = this;
49882 console.log('preview');
49884 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
49887 controllers.Preview = Preview;
49888 })(controllers = app.controllers || (app.controllers = {}));
49889 })(app || (app = {}));
49891 (function (filters) {
49893 return function (commands, tag) {
49895 angular.forEach(commands, function (command) {
49897 angular.forEach(command.tags, function (value) {
49902 result.push(command);
49908 })(filters || (filters = {}));
49912 var appName = 'zephyr';
49913 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
49914 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
49915 $urlRouterProvider.otherwise('/execution');
49916 $locationProvider.html5Mode({
49921 .state('execution', {
49923 templateUrl: 'templates/execution.html',
49924 controller: 'executionController',
49927 .state('workspace', {
49929 templateUrl: 'templates/workspace.html',
49930 controller: 'workspaceController',
49933 .state('history', {
49935 templateUrl: 'templates/history.html',
49936 controller: 'historyController',
49940 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
49941 app.zephyr.service('MyModal', app.services.MyModal);
49942 app.zephyr.service('WebSocket', app.services.WebSocket);
49943 app.zephyr.filter('Tag', filters.Tag);
49944 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
49945 app.zephyr.controller('previewController', app.controllers.Preview);
49946 app.zephyr.controller('executionController', app.controllers.Execution);
49947 app.zephyr.controller('workspaceController', app.controllers.Workspace);
49948 app.zephyr.controller('historyController', app.controllers.History);
49949 app.zephyr.controller('commandController', app.directives.CommandController);
49950 app.zephyr.controller('optionController', app.directives.OptionController);
49951 app.zephyr.controller('directoryController', app.directives.DirectoryController);
49952 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
49953 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
49954 app.zephyr.directive('command', app.directives.Command.Factory());
49955 app.zephyr.directive('option', app.directives.Option.Factory());
49956 app.zephyr.directive('directory', app.directives.Directory.Factory());
49957 })(app || (app = {}));