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);
65 __webpack_require__(22);
66 __webpack_require__(23);
67 __webpack_require__(24);
72 /***/ function(module, exports, __webpack_require__) {
74 __webpack_require__(2);
75 module.exports = angular;
80 /***/ function(module, exports) {
83 * @license AngularJS v1.4.8
84 * (c) 2010-2015 Google, Inc. http://angularjs.org
87 (function(window, document, undefined) {'use strict';
92 * This object provides a utility for producing rich Error messages within
93 * Angular. It can be called as follows:
95 * var exampleMinErr = minErr('example');
96 * throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
98 * The above creates an instance of minErr in the example namespace. The
99 * resulting error will have a namespaced error code of example.one. The
100 * resulting error will replace {0} with the value of foo, and {1} with the
101 * value of bar. The object is not restricted in the number of arguments it can
104 * If fewer arguments are specified than necessary for interpolation, the extra
105 * interpolation markers will be preserved in the final string.
107 * Since data will be parsed statically during a build step, some restrictions
108 * are applied with respect to how minErr instances are created and called.
109 * Instances should have names of the form namespaceMinErr for a minErr created
110 * using minErr('namespace') . Error codes, namespaces and template strings
111 * should all be static strings, not variables or general expressions.
113 * @param {string} module The namespace to use for the new minErr instance.
114 * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning
115 * error from returned function, for cases when a particular type of error is useful.
116 * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance
119 function minErr(module, ErrorConstructor) {
120 ErrorConstructor = ErrorConstructor || Error;
122 var SKIP_INDEXES = 2;
124 var templateArgs = arguments,
125 code = templateArgs[0],
126 message = '[' + (module ? module + ':' : '') + code + '] ',
127 template = templateArgs[1],
130 message += template.replace(/\{\d+\}/g, function(match) {
131 var index = +match.slice(1, -1),
132 shiftedIndex = index + SKIP_INDEXES;
134 if (shiftedIndex < templateArgs.length) {
135 return toDebugString(templateArgs[shiftedIndex]);
141 message += '\nhttp://errors.angularjs.org/1.4.8/' +
142 (module ? module + '/' : '') + code;
144 for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
145 message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' +
146 encodeURIComponent(toDebugString(templateArgs[i]));
149 return new ErrorConstructor(message);
153 /* We need to tell jshint what variables are being exported */
154 /* global angular: true,
165 REGEX_STRING_REGEXP: true,
166 VALIDITY_STATE_PROPERTY: true,
170 manualLowercase: true,
171 manualUppercase: true,
204 escapeForRegexp: true,
217 toJsonReplacer: true,
220 convertTimezoneToLocal: true,
221 timezoneToOffset: true,
223 tryDecodeURIComponent: true,
226 encodeUriSegment: true,
227 encodeUriQuery: true,
230 getTestability: true,
235 assertNotHasOwnProperty: true,
238 hasOwnProperty: true,
241 NODE_TYPE_ELEMENT: true,
242 NODE_TYPE_ATTRIBUTE: true,
243 NODE_TYPE_TEXT: true,
244 NODE_TYPE_COMMENT: true,
245 NODE_TYPE_DOCUMENT: true,
246 NODE_TYPE_DOCUMENT_FRAGMENT: true,
249 ////////////////////////////////////
258 * The ng module is loaded by default when an AngularJS application is started. The module itself
259 * contains the essential components for an AngularJS application to function. The table below
260 * lists a high level breakdown of each of the services/factories, filters, directives and testing
261 * components available within this core module.
263 * <div doc-module-components="ng"></div>
266 var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/;
268 // The name of a form control's ValidityState property.
269 // This is used so that it's possible for internal tests to create mock ValidityStates.
270 var VALIDITY_STATE_PROPERTY = 'validity';
274 * @name angular.lowercase
278 * @description Converts the specified string to lowercase.
279 * @param {string} string String to be converted to lowercase.
280 * @returns {string} Lowercased string.
282 var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;};
283 var hasOwnProperty = Object.prototype.hasOwnProperty;
287 * @name angular.uppercase
291 * @description Converts the specified string to uppercase.
292 * @param {string} string String to be converted to uppercase.
293 * @returns {string} Uppercased string.
295 var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;};
298 var manualLowercase = function(s) {
299 /* jshint bitwise: false */
301 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);})
304 var manualUppercase = function(s) {
305 /* jshint bitwise: false */
307 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);})
312 // String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
313 // locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
314 // with correct but slower alternatives.
315 if ('i' !== 'I'.toLowerCase()) {
316 lowercase = manualLowercase;
317 uppercase = manualUppercase;
322 msie, // holds major version number for IE, or NaN if UA is not IE.
323 jqLite, // delay binding since jQuery could be loaded after us.
324 jQuery, // delay binding
328 toString = Object.prototype.toString,
329 getPrototypeOf = Object.getPrototypeOf,
330 ngMinErr = minErr('ng'),
333 angular = window.angular || (window.angular = {}),
338 * documentMode is an IE-only property
339 * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
341 msie = document.documentMode;
347 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments,
350 function isArrayLike(obj) {
352 // `null`, `undefined` and `window` are not array-like
353 if (obj == null || isWindow(obj)) return false;
355 // arrays, strings and jQuery/jqLite objects are array like
356 // * jqLite is either the jQuery or jqLite constructor function
357 // * we have to check the existance of jqLite first as this method is called
358 // via the forEach method when constructing the jqLite object in the first place
359 if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true;
361 // Support: iOS 8.2 (not reproducible in simulator)
362 // "length" in obj used to prevent JIT error (gh-11508)
363 var length = "length" in Object(obj) && obj.length;
365 // NodeList objects (with `item` method) and
366 // other objects with suitable length characteristics are array-like
367 return isNumber(length) &&
368 (length >= 0 && (length - 1) in obj || typeof obj.item == 'function');
373 * @name angular.forEach
378 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an
379 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value`
380 * is the value of an object property or an array element, `key` is the object property key or
381 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional.
383 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters
384 * using the `hasOwnProperty` method.
387 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),
388 * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
389 * return the value provided.
392 var values = {name: 'misko', gender: 'male'};
394 angular.forEach(values, function(value, key) {
395 this.push(key + ': ' + value);
397 expect(log).toEqual(['name: misko', 'gender: male']);
400 * @param {Object|Array} obj Object to iterate over.
401 * @param {Function} iterator Iterator function.
402 * @param {Object=} context Object to become context (`this`) for the iterator function.
403 * @returns {Object|Array} Reference to `obj`.
406 function forEach(obj, iterator, context) {
409 if (isFunction(obj)) {
411 // Need to check if hasOwnProperty exists,
412 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
413 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {
414 iterator.call(context, obj[key], key, obj);
417 } else if (isArray(obj) || isArrayLike(obj)) {
418 var isPrimitive = typeof obj !== 'object';
419 for (key = 0, length = obj.length; key < length; key++) {
420 if (isPrimitive || key in obj) {
421 iterator.call(context, obj[key], key, obj);
424 } else if (obj.forEach && obj.forEach !== forEach) {
425 obj.forEach(iterator, context, obj);
426 } else if (isBlankObject(obj)) {
427 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
429 iterator.call(context, obj[key], key, obj);
431 } else if (typeof obj.hasOwnProperty === 'function') {
432 // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed
434 if (obj.hasOwnProperty(key)) {
435 iterator.call(context, obj[key], key, obj);
439 // Slow path for objects which do not have a method `hasOwnProperty`
441 if (hasOwnProperty.call(obj, key)) {
442 iterator.call(context, obj[key], key, obj);
450 function forEachSorted(obj, iterator, context) {
451 var keys = Object.keys(obj).sort();
452 for (var i = 0; i < keys.length; i++) {
453 iterator.call(context, obj[keys[i]], keys[i]);
460 * when using forEach the params are value, key, but it is often useful to have key, value.
461 * @param {function(string, *)} iteratorFn
462 * @returns {function(*, string)}
464 function reverseParams(iteratorFn) {
465 return function(value, key) { iteratorFn(key, value); };
469 * A consistent way of creating unique IDs in angular.
471 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before
472 * we hit number precision issues in JavaScript.
474 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M
476 * @returns {number} an unique alpha-numeric string
484 * Set or clear the hashkey for an object.
486 * @param h the hashkey (!truthy to delete the hashkey)
488 function setHashKey(obj, h) {
492 delete obj.$$hashKey;
497 function baseExtend(dst, objs, deep) {
498 var h = dst.$$hashKey;
500 for (var i = 0, ii = objs.length; i < ii; ++i) {
502 if (!isObject(obj) && !isFunction(obj)) continue;
503 var keys = Object.keys(obj);
504 for (var j = 0, jj = keys.length; j < jj; j++) {
508 if (deep && isObject(src)) {
510 dst[key] = new Date(src.valueOf());
511 } else if (isRegExp(src)) {
512 dst[key] = new RegExp(src);
513 } else if (src.nodeName) {
514 dst[key] = src.cloneNode(true);
515 } else if (isElement(src)) {
516 dst[key] = src.clone();
518 if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
519 baseExtend(dst[key], [src], true);
533 * @name angular.extend
538 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
539 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
540 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
542 * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use
543 * {@link angular.merge} for this.
545 * @param {Object} dst Destination object.
546 * @param {...Object} src Source object(s).
547 * @returns {Object} Reference to `dst`.
549 function extend(dst) {
550 return baseExtend(dst, slice.call(arguments, 1), false);
556 * @name angular.merge
561 * Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
562 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
563 * by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`.
565 * Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source
566 * objects, performing a deep copy.
568 * @param {Object} dst Destination object.
569 * @param {...Object} src Source object(s).
570 * @returns {Object} Reference to `dst`.
572 function merge(dst) {
573 return baseExtend(dst, slice.call(arguments, 1), true);
578 function toInt(str) {
579 return parseInt(str, 10);
583 function inherit(parent, extra) {
584 return extend(Object.create(parent), extra);
594 * A function that performs no operations. This function can be useful when writing code in the
597 function foo(callback) {
598 var result = calculateResult();
599 (callback || angular.noop)(result);
609 * @name angular.identity
614 * A function that returns its first argument. This function is useful when writing code in the
618 function transformer(transformationFn, value) {
619 return (transformationFn || angular.identity)(value);
622 * @param {*} value to be returned.
623 * @returns {*} the value passed in.
625 function identity($) {return $;}
626 identity.$inject = [];
629 function valueFn(value) {return function() {return value;};}
631 function hasCustomToString(obj) {
632 return isFunction(obj.toString) && obj.toString !== toString;
638 * @name angular.isUndefined
643 * Determines if a reference is undefined.
645 * @param {*} value Reference to check.
646 * @returns {boolean} True if `value` is undefined.
648 function isUndefined(value) {return typeof value === 'undefined';}
653 * @name angular.isDefined
658 * Determines if a reference is defined.
660 * @param {*} value Reference to check.
661 * @returns {boolean} True if `value` is defined.
663 function isDefined(value) {return typeof value !== 'undefined';}
668 * @name angular.isObject
673 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
674 * considered to be objects. Note that JavaScript arrays are objects.
676 * @param {*} value Reference to check.
677 * @returns {boolean} True if `value` is an `Object` but not `null`.
679 function isObject(value) {
680 // http://jsperf.com/isobject4
681 return value !== null && typeof value === 'object';
686 * Determine if a value is an object with a null prototype
688 * @returns {boolean} True if `value` is an `Object` with a null prototype
690 function isBlankObject(value) {
691 return value !== null && typeof value === 'object' && !getPrototypeOf(value);
697 * @name angular.isString
702 * Determines if a reference is a `String`.
704 * @param {*} value Reference to check.
705 * @returns {boolean} True if `value` is a `String`.
707 function isString(value) {return typeof value === 'string';}
712 * @name angular.isNumber
717 * Determines if a reference is a `Number`.
719 * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`.
721 * If you wish to exclude these then you can use the native
722 * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite)
725 * @param {*} value Reference to check.
726 * @returns {boolean} True if `value` is a `Number`.
728 function isNumber(value) {return typeof value === 'number';}
733 * @name angular.isDate
738 * Determines if a value is a date.
740 * @param {*} value Reference to check.
741 * @returns {boolean} True if `value` is a `Date`.
743 function isDate(value) {
744 return toString.call(value) === '[object Date]';
750 * @name angular.isArray
755 * Determines if a reference is an `Array`.
757 * @param {*} value Reference to check.
758 * @returns {boolean} True if `value` is an `Array`.
760 var isArray = Array.isArray;
764 * @name angular.isFunction
769 * Determines if a reference is a `Function`.
771 * @param {*} value Reference to check.
772 * @returns {boolean} True if `value` is a `Function`.
774 function isFunction(value) {return typeof value === 'function';}
778 * Determines if a value is a regular expression object.
781 * @param {*} value Reference to check.
782 * @returns {boolean} True if `value` is a `RegExp`.
784 function isRegExp(value) {
785 return toString.call(value) === '[object RegExp]';
790 * Checks if `obj` is a window object.
793 * @param {*} obj Object to check
794 * @returns {boolean} True if `obj` is a window obj.
796 function isWindow(obj) {
797 return obj && obj.window === obj;
801 function isScope(obj) {
802 return obj && obj.$evalAsync && obj.$watch;
806 function isFile(obj) {
807 return toString.call(obj) === '[object File]';
811 function isFormData(obj) {
812 return toString.call(obj) === '[object FormData]';
816 function isBlob(obj) {
817 return toString.call(obj) === '[object Blob]';
821 function isBoolean(value) {
822 return typeof value === 'boolean';
826 function isPromiseLike(obj) {
827 return obj && isFunction(obj.then);
831 var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/;
832 function isTypedArray(value) {
833 return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value));
837 var trim = function(value) {
838 return isString(value) ? value.trim() : value;
842 // http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021
843 // Prereq: s is a string.
844 var escapeForRegexp = function(s) {
845 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
846 replace(/\x08/g, '\\x08');
852 * @name angular.isElement
857 * Determines if a reference is a DOM element (or wrapped jQuery element).
859 * @param {*} value Reference to check.
860 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
862 function isElement(node) {
864 (node.nodeName // we are a direct element
865 || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API
869 * @param str 'key1,key2,...'
870 * @returns {object} in the form of {key1:true, key2:true, ...}
872 function makeMap(str) {
873 var obj = {}, items = str.split(","), i;
874 for (i = 0; i < items.length; i++) {
875 obj[items[i]] = true;
881 function nodeName_(element) {
882 return lowercase(element.nodeName || (element[0] && element[0].nodeName));
885 function includes(array, obj) {
886 return Array.prototype.indexOf.call(array, obj) != -1;
889 function arrayRemove(array, value) {
890 var index = array.indexOf(value);
892 array.splice(index, 1);
904 * Creates a deep copy of `source`, which should be an object or an array.
906 * * If no destination is supplied, a copy of the object or array is created.
907 * * If a destination is provided, all of its elements (for arrays) or properties (for objects)
908 * are deleted and then all elements/properties from the source are copied to it.
909 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
910 * * If `source` is identical to 'destination' an exception will be thrown.
912 * @param {*} source The source that will be used to make a copy.
913 * Can be any type, including primitives, `null`, and `undefined`.
914 * @param {(Object|Array)=} destination Destination into which the source is copied. If
915 * provided, must be of the same type as `source`.
916 * @returns {*} The copy or updated `destination`, if `destination` was specified.
919 <example module="copyExample">
920 <file name="index.html">
921 <div ng-controller="ExampleController">
922 <form novalidate class="simple-form">
923 Name: <input type="text" ng-model="user.name" /><br />
924 E-mail: <input type="email" ng-model="user.email" /><br />
925 Gender: <input type="radio" ng-model="user.gender" value="male" />male
926 <input type="radio" ng-model="user.gender" value="female" />female<br />
927 <button ng-click="reset()">RESET</button>
928 <button ng-click="update(user)">SAVE</button>
930 <pre>form = {{user | json}}</pre>
931 <pre>master = {{master | json}}</pre>
935 angular.module('copyExample', [])
936 .controller('ExampleController', ['$scope', function($scope) {
939 $scope.update = function(user) {
940 // Example with 1 argument
941 $scope.master= angular.copy(user);
944 $scope.reset = function() {
945 // Example with 2 arguments
946 angular.copy($scope.master, $scope.user);
955 function copy(source, destination) {
956 var stackSource = [];
960 if (isTypedArray(destination)) {
961 throw ngMinErr('cpta', "Can't copy! TypedArray destination cannot be mutated.");
963 if (source === destination) {
964 throw ngMinErr('cpi', "Can't copy! Source and destination are identical.");
967 // Empty the destination object
968 if (isArray(destination)) {
969 destination.length = 0;
971 forEach(destination, function(value, key) {
972 if (key !== '$$hashKey') {
973 delete destination[key];
978 stackSource.push(source);
979 stackDest.push(destination);
980 return copyRecurse(source, destination);
983 return copyElement(source);
985 function copyRecurse(source, destination) {
986 var h = destination.$$hashKey;
988 if (isArray(source)) {
989 for (var i = 0, ii = source.length; i < ii; i++) {
990 destination.push(copyElement(source[i]));
992 } else if (isBlankObject(source)) {
993 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
994 for (key in source) {
995 destination[key] = copyElement(source[key]);
997 } else if (source && typeof source.hasOwnProperty === 'function') {
998 // Slow path, which must rely on hasOwnProperty
999 for (key in source) {
1000 if (source.hasOwnProperty(key)) {
1001 destination[key] = copyElement(source[key]);
1005 // Slowest path --- hasOwnProperty can't be called as a method
1006 for (key in source) {
1007 if (hasOwnProperty.call(source, key)) {
1008 destination[key] = copyElement(source[key]);
1012 setHashKey(destination, h);
1016 function copyElement(source) {
1018 if (!isObject(source)) {
1022 // Already copied values
1023 var index = stackSource.indexOf(source);
1025 return stackDest[index];
1028 if (isWindow(source) || isScope(source)) {
1029 throw ngMinErr('cpws',
1030 "Can't copy! Making copies of Window or Scope instances is not supported.");
1033 var needsRecurse = false;
1036 if (isArray(source)) {
1038 needsRecurse = true;
1039 } else if (isTypedArray(source)) {
1040 destination = new source.constructor(source);
1041 } else if (isDate(source)) {
1042 destination = new Date(source.getTime());
1043 } else if (isRegExp(source)) {
1044 destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
1045 destination.lastIndex = source.lastIndex;
1046 } else if (isFunction(source.cloneNode)) {
1047 destination = source.cloneNode(true);
1049 destination = Object.create(getPrototypeOf(source));
1050 needsRecurse = true;
1053 stackSource.push(source);
1054 stackDest.push(destination);
1057 ? copyRecurse(source, destination)
1063 * Creates a shallow copy of an object, an array or a primitive.
1065 * Assumes that there are no proto properties for objects.
1067 function shallowCopy(src, dst) {
1071 for (var i = 0, ii = src.length; i < ii; i++) {
1074 } else if (isObject(src)) {
1077 for (var key in src) {
1078 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
1079 dst[key] = src[key];
1090 * @name angular.equals
1095 * Determines if two objects or two values are equivalent. Supports value types, regular
1096 * expressions, arrays and objects.
1098 * Two objects or values are considered equivalent if at least one of the following is true:
1100 * * Both objects or values pass `===` comparison.
1101 * * Both objects or values are of the same type and all of their properties are equal by
1102 * comparing them with `angular.equals`.
1103 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal)
1104 * * Both values represent the same regular expression (In JavaScript,
1105 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual
1106 * representation matches).
1108 * During a property comparison, properties of `function` type and properties with names
1109 * that begin with `$` are ignored.
1111 * Scope and DOMWindow objects are being compared only by identify (`===`).
1113 * @param {*} o1 Object or value to compare.
1114 * @param {*} o2 Object or value to compare.
1115 * @returns {boolean} True if arguments are equal.
1117 function equals(o1, o2) {
1118 if (o1 === o2) return true;
1119 if (o1 === null || o2 === null) return false;
1120 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
1121 var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
1123 if (t1 == 'object') {
1125 if (!isArray(o2)) return false;
1126 if ((length = o1.length) == o2.length) {
1127 for (key = 0; key < length; key++) {
1128 if (!equals(o1[key], o2[key])) return false;
1132 } else if (isDate(o1)) {
1133 if (!isDate(o2)) return false;
1134 return equals(o1.getTime(), o2.getTime());
1135 } else if (isRegExp(o1)) {
1136 return isRegExp(o2) ? o1.toString() == o2.toString() : false;
1138 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
1139 isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
1140 keySet = createMap();
1142 if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
1143 if (!equals(o1[key], o2[key])) return false;
1147 if (!(key in keySet) &&
1148 key.charAt(0) !== '$' &&
1149 isDefined(o2[key]) &&
1150 !isFunction(o2[key])) return false;
1159 var csp = function() {
1160 if (!isDefined(csp.rules)) {
1163 var ngCspElement = (document.querySelector('[ng-csp]') ||
1164 document.querySelector('[data-ng-csp]'));
1167 var ngCspAttribute = ngCspElement.getAttribute('ng-csp') ||
1168 ngCspElement.getAttribute('data-ng-csp');
1170 noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1),
1171 noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1)
1175 noUnsafeEval: noUnsafeEval(),
1176 noInlineStyle: false
1183 function noUnsafeEval() {
1185 /* jshint -W031, -W054 */
1187 /* jshint +W031, +W054 */
1201 * @param {string=} ngJq the name of the library available under `window`
1202 * to be used for angular.element
1204 * Use this directive to force the angular.element library. This should be
1205 * used to force either jqLite by leaving ng-jq blank or setting the name of
1206 * the jquery variable under window (eg. jQuery).
1208 * Since angular looks for this directive when it is loaded (doesn't wait for the
1209 * DOMContentLoaded event), it must be placed on an element that comes before the script
1210 * which loads angular. Also, only the first instance of `ng-jq` will be used and all
1214 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag.
1223 * This example shows how to use a jQuery based library of a different name.
1224 * The library name must be available at the top most 'window'.
1227 <html ng-app ng-jq="jQueryLib">
1233 var jq = function() {
1234 if (isDefined(jq.name_)) return jq.name_;
1236 var i, ii = ngAttrPrefixes.length, prefix, name;
1237 for (i = 0; i < ii; ++i) {
1238 prefix = ngAttrPrefixes[i];
1239 if (el = document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) {
1240 name = el.getAttribute(prefix + 'jq');
1245 return (jq.name_ = name);
1248 function concat(array1, array2, index) {
1249 return array1.concat(slice.call(array2, index));
1252 function sliceArgs(args, startIndex) {
1253 return slice.call(args, startIndex || 0);
1260 * @name angular.bind
1265 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for
1266 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also
1267 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as
1268 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application).
1270 * @param {Object} self Context which `fn` should be evaluated in.
1271 * @param {function()} fn Function to be bound.
1272 * @param {...*} args Optional arguments to be prebound to the `fn` function call.
1273 * @returns {function()} Function that wraps the `fn` with all the specified bindings.
1276 function bind(self, fn) {
1277 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : [];
1278 if (isFunction(fn) && !(fn instanceof RegExp)) {
1279 return curryArgs.length
1281 return arguments.length
1282 ? fn.apply(self, concat(curryArgs, arguments, 0))
1283 : fn.apply(self, curryArgs);
1286 return arguments.length
1287 ? fn.apply(self, arguments)
1291 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be)
1297 function toJsonReplacer(key, value) {
1300 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') {
1302 } else if (isWindow(value)) {
1304 } else if (value && document === value) {
1306 } else if (isScope(value)) {
1316 * @name angular.toJson
1321 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be
1322 * stripped since angular uses this notation internally.
1324 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
1325 * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace.
1326 * If set to an integer, the JSON output will contain that many spaces per indentation.
1327 * @returns {string|undefined} JSON-ified string representing `obj`.
1329 function toJson(obj, pretty) {
1330 if (typeof obj === 'undefined') return undefined;
1331 if (!isNumber(pretty)) {
1332 pretty = pretty ? 2 : null;
1334 return JSON.stringify(obj, toJsonReplacer, pretty);
1340 * @name angular.fromJson
1345 * Deserializes a JSON string.
1347 * @param {string} json JSON string to deserialize.
1348 * @returns {Object|Array|string|number} Deserialized JSON string.
1350 function fromJson(json) {
1351 return isString(json)
1357 function timezoneToOffset(timezone, fallback) {
1358 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
1359 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
1363 function addDateMinutes(date, minutes) {
1364 date = new Date(date.getTime());
1365 date.setMinutes(date.getMinutes() + minutes);
1370 function convertTimezoneToLocal(date, timezone, reverse) {
1371 reverse = reverse ? -1 : 1;
1372 var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
1373 return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
1378 * @returns {string} Returns the string representation of the element.
1380 function startingTag(element) {
1381 element = jqLite(element).clone();
1383 // turns out IE does not let you set .html() on elements which
1384 // are not allowed to have children. So we just ignore it.
1387 var elemHtml = jqLite('<div>').append(element).html();
1389 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
1391 match(/^(<[^>]+>)/)[1].
1392 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
1394 return lowercase(elemHtml);
1400 /////////////////////////////////////////////////
1403 * Tries to decode the URI component without throwing an exception.
1406 * @param str value potential URI component to check.
1407 * @returns {boolean} True if `value` can be decoded
1408 * with the decodeURIComponent function.
1410 function tryDecodeURIComponent(value) {
1412 return decodeURIComponent(value);
1414 // Ignore any invalid uri component
1420 * Parses an escaped url query string into key-value pairs.
1421 * @returns {Object.<string,boolean|Array>}
1423 function parseKeyValue(/**string*/keyValue) {
1425 forEach((keyValue || "").split('&'), function(keyValue) {
1426 var splitPoint, key, val;
1428 key = keyValue = keyValue.replace(/\+/g,'%20');
1429 splitPoint = keyValue.indexOf('=');
1430 if (splitPoint !== -1) {
1431 key = keyValue.substring(0, splitPoint);
1432 val = keyValue.substring(splitPoint + 1);
1434 key = tryDecodeURIComponent(key);
1435 if (isDefined(key)) {
1436 val = isDefined(val) ? tryDecodeURIComponent(val) : true;
1437 if (!hasOwnProperty.call(obj, key)) {
1439 } else if (isArray(obj[key])) {
1442 obj[key] = [obj[key],val];
1450 function toKeyValue(obj) {
1452 forEach(obj, function(value, key) {
1453 if (isArray(value)) {
1454 forEach(value, function(arrayValue) {
1455 parts.push(encodeUriQuery(key, true) +
1456 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true)));
1459 parts.push(encodeUriQuery(key, true) +
1460 (value === true ? '' : '=' + encodeUriQuery(value, true)));
1463 return parts.length ? parts.join('&') : '';
1468 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
1469 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
1472 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1473 * pct-encoded = "%" HEXDIG HEXDIG
1474 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1475 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1476 * / "*" / "+" / "," / ";" / "="
1478 function encodeUriSegment(val) {
1479 return encodeUriQuery(val, true).
1480 replace(/%26/gi, '&').
1481 replace(/%3D/gi, '=').
1482 replace(/%2B/gi, '+');
1487 * This method is intended for encoding *key* or *value* parts of query component. We need a custom
1488 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
1489 * encoded per http://tools.ietf.org/html/rfc3986:
1490 * query = *( pchar / "/" / "?" )
1491 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1492 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1493 * pct-encoded = "%" HEXDIG HEXDIG
1494 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1495 * / "*" / "+" / "," / ";" / "="
1497 function encodeUriQuery(val, pctEncodeSpaces) {
1498 return encodeURIComponent(val).
1499 replace(/%40/gi, '@').
1500 replace(/%3A/gi, ':').
1501 replace(/%24/g, '$').
1502 replace(/%2C/gi, ',').
1503 replace(/%3B/gi, ';').
1504 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
1507 var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];
1509 function getNgAttribute(element, ngAttr) {
1510 var attr, i, ii = ngAttrPrefixes.length;
1511 for (i = 0; i < ii; ++i) {
1512 attr = ngAttrPrefixes[i] + ngAttr;
1513 if (isString(attr = element.getAttribute(attr))) {
1526 * @param {angular.Module} ngApp an optional application
1527 * {@link angular.module module} name to load.
1528 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be
1529 * created in "strict-di" mode. This means that the application will fail to invoke functions which
1530 * do not use explicit function annotation (and are thus unsuitable for minification), as described
1531 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in
1532 * tracking down the root of these bugs.
1536 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive
1537 * designates the **root element** of the application and is typically placed near the root element
1538 * of the page - e.g. on the `<body>` or `<html>` tags.
1540 * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp`
1541 * found in the document will be used to define the root element to auto-bootstrap as an
1542 * application. To run multiple applications in an HTML document you must manually bootstrap them using
1543 * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other.
1545 * You can specify an **AngularJS module** to be used as the root module for the application. This
1546 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It
1547 * should contain the application code needed or have dependencies on other modules that will
1548 * contain the code. See {@link angular.module} for more information.
1550 * In the example below if the `ngApp` directive were not placed on the `html` element then the
1551 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
1552 * would not be resolved to `3`.
1554 * `ngApp` is the easiest, and most common way to bootstrap an application.
1556 <example module="ngAppDemo">
1557 <file name="index.html">
1558 <div ng-controller="ngAppDemoController">
1559 I can add: {{a}} + {{b}} = {{ a+b }}
1562 <file name="script.js">
1563 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
1570 * Using `ngStrictDi`, you would see something like this:
1572 <example ng-app-included="true">
1573 <file name="index.html">
1574 <div ng-app="ngAppStrictDemo" ng-strict-di>
1575 <div ng-controller="GoodController1">
1576 I can add: {{a}} + {{b}} = {{ a+b }}
1578 <p>This renders because the controller does not fail to
1579 instantiate, by using explicit annotation style (see
1580 script.js for details)
1584 <div ng-controller="GoodController2">
1585 Name: <input ng-model="name"><br />
1588 <p>This renders because the controller does not fail to
1589 instantiate, by using explicit annotation style
1590 (see script.js for details)
1594 <div ng-controller="BadController">
1595 I can add: {{a}} + {{b}} = {{ a+b }}
1597 <p>The controller could not be instantiated, due to relying
1598 on automatic function annotations (which are disabled in
1599 strict mode). As such, the content of this section is not
1600 interpolated, and there should be an error in your web console.
1605 <file name="script.js">
1606 angular.module('ngAppStrictDemo', [])
1607 // BadController will fail to instantiate, due to relying on automatic function annotation,
1608 // rather than an explicit annotation
1609 .controller('BadController', function($scope) {
1613 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated,
1614 // due to using explicit annotations using the array style and $inject property, respectively.
1615 .controller('GoodController1', ['$scope', function($scope) {
1619 .controller('GoodController2', GoodController2);
1620 function GoodController2($scope) {
1621 $scope.name = "World";
1623 GoodController2.$inject = ['$scope'];
1625 <file name="style.css">
1626 div[ng-controller] {
1628 -webkit-border-radius: 4px;
1633 div[ng-controller^=Good] {
1634 border-color: #d6e9c6;
1635 background-color: #dff0d8;
1638 div[ng-controller^=Bad] {
1639 border-color: #ebccd1;
1640 background-color: #f2dede;
1647 function angularInit(element, bootstrap) {
1652 // The element `element` has priority over any other element
1653 forEach(ngAttrPrefixes, function(prefix) {
1654 var name = prefix + 'app';
1656 if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
1657 appElement = element;
1658 module = element.getAttribute(name);
1661 forEach(ngAttrPrefixes, function(prefix) {
1662 var name = prefix + 'app';
1665 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) {
1666 appElement = candidate;
1667 module = candidate.getAttribute(name);
1671 config.strictDi = getNgAttribute(appElement, "strict-di") !== null;
1672 bootstrap(appElement, module ? [module] : [], config);
1678 * @name angular.bootstrap
1681 * Use this function to manually start up angular application.
1683 * See: {@link guide/bootstrap Bootstrap}
1685 * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually.
1686 * They must use {@link ng.directive:ngApp ngApp}.
1688 * Angular will detect if it has been loaded into the browser more than once and only allow the
1689 * first loaded script to be bootstrapped and will report a warning to the browser console for
1690 * each of the subsequent scripts. This prevents strange results in applications, where otherwise
1691 * multiple instances of Angular try to work on the DOM.
1697 * <div ng-controller="WelcomeController">
1701 * <script src="angular.js"></script>
1703 * var app = angular.module('demo', [])
1704 * .controller('WelcomeController', function($scope) {
1705 * $scope.greeting = 'Welcome!';
1707 * angular.bootstrap(document, ['demo']);
1713 * @param {DOMElement} element DOM element which is the root of angular application.
1714 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application.
1715 * Each item in the array should be the name of a predefined module or a (DI annotated)
1716 * function that will be invoked by the injector as a `config` block.
1717 * See: {@link angular.module modules}
1718 * @param {Object=} config an object for defining configuration options for the application. The
1719 * following keys are supported:
1721 * * `strictDi` - disable automatic function annotation for the application. This is meant to
1722 * assist in finding bugs which break minified code. Defaults to `false`.
1724 * @returns {auto.$injector} Returns the newly created injector for this app.
1726 function bootstrap(element, modules, config) {
1727 if (!isObject(config)) config = {};
1728 var defaultConfig = {
1731 config = extend(defaultConfig, config);
1732 var doBootstrap = function() {
1733 element = jqLite(element);
1735 if (element.injector()) {
1736 var tag = (element[0] === document) ? 'document' : startingTag(element);
1737 //Encode angle brackets to prevent input from being sanitized to empty string #8683
1740 "App Already Bootstrapped with this Element '{0}'",
1741 tag.replace(/</,'<').replace(/>/,'>'));
1744 modules = modules || [];
1745 modules.unshift(['$provide', function($provide) {
1746 $provide.value('$rootElement', element);
1749 if (config.debugInfoEnabled) {
1750 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
1751 modules.push(['$compileProvider', function($compileProvider) {
1752 $compileProvider.debugInfoEnabled(true);
1756 modules.unshift('ng');
1757 var injector = createInjector(modules, config.strictDi);
1758 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
1759 function bootstrapApply(scope, element, compile, injector) {
1760 scope.$apply(function() {
1761 element.data('$injector', injector);
1762 compile(element)(scope);
1769 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/;
1770 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
1772 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) {
1773 config.debugInfoEnabled = true;
1774 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, '');
1777 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
1778 return doBootstrap();
1781 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
1782 angular.resumeBootstrap = function(extraModules) {
1783 forEach(extraModules, function(module) {
1784 modules.push(module);
1786 return doBootstrap();
1789 if (isFunction(angular.resumeDeferredBootstrap)) {
1790 angular.resumeDeferredBootstrap();
1796 * @name angular.reloadWithDebugInfo
1799 * Use this function to reload the current application with debug information turned on.
1800 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`.
1802 * See {@link ng.$compileProvider#debugInfoEnabled} for more.
1804 function reloadWithDebugInfo() {
1805 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name;
1806 window.location.reload();
1810 * @name angular.getTestability
1813 * Get the testability service for the instance of Angular on the given
1815 * @param {DOMElement} element DOM element which is the root of angular application.
1817 function getTestability(rootElement) {
1818 var injector = angular.element(rootElement).injector();
1820 throw ngMinErr('test',
1821 'no injector found for element argument to getTestability');
1823 return injector.get('$$testability');
1826 var SNAKE_CASE_REGEXP = /[A-Z]/g;
1827 function snake_case(name, separator) {
1828 separator = separator || '_';
1829 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
1830 return (pos ? separator : '') + letter.toLowerCase();
1834 var bindJQueryFired = false;
1835 var skipDestroyOnNextJQueryCleanData;
1836 function bindJQuery() {
1837 var originalCleanData;
1839 if (bindJQueryFired) {
1843 // bind to jQuery if present;
1845 jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present)
1846 !jqName ? undefined : // use jqLite
1847 window[jqName]; // use jQuery specified by `ngJq`
1849 // Use jQuery if it exists with proper functionality, otherwise default to us.
1850 // Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
1851 // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older
1852 // versions. It will not work for sure with jQuery <1.7, though.
1853 if (jQuery && jQuery.fn.on) {
1856 scope: JQLitePrototype.scope,
1857 isolateScope: JQLitePrototype.isolateScope,
1858 controller: JQLitePrototype.controller,
1859 injector: JQLitePrototype.injector,
1860 inheritedData: JQLitePrototype.inheritedData
1863 // All nodes removed from the DOM via various jQuery APIs like .remove()
1864 // are passed through jQuery.cleanData. Monkey-patch this method to fire
1865 // the $destroy event on all removed nodes.
1866 originalCleanData = jQuery.cleanData;
1867 jQuery.cleanData = function(elems) {
1869 if (!skipDestroyOnNextJQueryCleanData) {
1870 for (var i = 0, elem; (elem = elems[i]) != null; i++) {
1871 events = jQuery._data(elem, "events");
1872 if (events && events.$destroy) {
1873 jQuery(elem).triggerHandler('$destroy');
1877 skipDestroyOnNextJQueryCleanData = false;
1879 originalCleanData(elems);
1885 angular.element = jqLite;
1887 // Prevent double-proxying.
1888 bindJQueryFired = true;
1892 * throw error if the argument is falsy.
1894 function assertArg(arg, name, reason) {
1896 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
1901 function assertArgFn(arg, name, acceptArrayAnnotation) {
1902 if (acceptArrayAnnotation && isArray(arg)) {
1903 arg = arg[arg.length - 1];
1906 assertArg(isFunction(arg), name, 'not a function, got ' +
1907 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg));
1912 * throw error if the name given is hasOwnProperty
1913 * @param {String} name the name to test
1914 * @param {String} context the context in which the name is used, such as module or directive
1916 function assertNotHasOwnProperty(name, context) {
1917 if (name === 'hasOwnProperty') {
1918 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context);
1923 * Return the value accessible from the object by path. Any undefined traversals are ignored
1924 * @param {Object} obj starting object
1925 * @param {String} path path to traverse
1926 * @param {boolean} [bindFnToScope=true]
1927 * @returns {Object} value as accessible by path
1929 //TODO(misko): this function needs to be removed
1930 function getter(obj, path, bindFnToScope) {
1931 if (!path) return obj;
1932 var keys = path.split('.');
1934 var lastInstance = obj;
1935 var len = keys.length;
1937 for (var i = 0; i < len; i++) {
1940 obj = (lastInstance = obj)[key];
1943 if (!bindFnToScope && isFunction(obj)) {
1944 return bind(lastInstance, obj);
1950 * Return the DOM siblings between the first and last node in the given array.
1951 * @param {Array} array like object
1952 * @returns {Array} the inputted object or a jqLite collection containing the nodes
1954 function getBlockNodes(nodes) {
1955 // TODO(perf): update `nodes` instead of creating a new object?
1956 var node = nodes[0];
1957 var endNode = nodes[nodes.length - 1];
1960 for (var i = 1; node !== endNode && (node = node.nextSibling); i++) {
1961 if (blockNodes || nodes[i] !== node) {
1963 blockNodes = jqLite(slice.call(nodes, 0, i));
1965 blockNodes.push(node);
1969 return blockNodes || nodes;
1974 * Creates a new object without a prototype. This object is useful for lookup without having to
1975 * guard against prototypically inherited properties via hasOwnProperty.
1977 * Related micro-benchmarks:
1978 * - http://jsperf.com/object-create2
1979 * - http://jsperf.com/proto-map-lookup/2
1980 * - http://jsperf.com/for-in-vs-object-keys2
1984 function createMap() {
1985 return Object.create(null);
1988 var NODE_TYPE_ELEMENT = 1;
1989 var NODE_TYPE_ATTRIBUTE = 2;
1990 var NODE_TYPE_TEXT = 3;
1991 var NODE_TYPE_COMMENT = 8;
1992 var NODE_TYPE_DOCUMENT = 9;
1993 var NODE_TYPE_DOCUMENT_FRAGMENT = 11;
1997 * @name angular.Module
2001 * Interface for configuring angular {@link angular.module modules}.
2004 function setupModuleLoader(window) {
2006 var $injectorMinErr = minErr('$injector');
2007 var ngMinErr = minErr('ng');
2009 function ensure(obj, name, factory) {
2010 return obj[name] || (obj[name] = factory());
2013 var angular = ensure(window, 'angular', Object);
2015 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
2016 angular.$$minErr = angular.$$minErr || minErr;
2018 return ensure(angular, 'module', function() {
2019 /** @type {Object.<string, angular.Module>} */
2024 * @name angular.module
2028 * The `angular.module` is a global place for creating, registering and retrieving Angular
2030 * All modules (angular core or 3rd party) that should be available to an application must be
2031 * registered using this mechanism.
2033 * Passing one argument retrieves an existing {@link angular.Module},
2034 * whereas passing more than one argument creates a new {@link angular.Module}
2039 * A module is a collection of services, directives, controllers, filters, and configuration information.
2040 * `angular.module` is used to configure the {@link auto.$injector $injector}.
2043 * // Create a new module
2044 * var myModule = angular.module('myModule', []);
2046 * // register a new service
2047 * myModule.value('appName', 'MyCoolApp');
2049 * // configure existing services inside initialization blocks.
2050 * myModule.config(['$locationProvider', function($locationProvider) {
2051 * // Configure existing providers
2052 * $locationProvider.hashPrefix('!');
2056 * Then you can create an injector and load your modules like this:
2059 * var injector = angular.injector(['ng', 'myModule'])
2062 * However it's more likely that you'll just use
2063 * {@link ng.directive:ngApp ngApp} or
2064 * {@link angular.bootstrap} to simplify this process for you.
2066 * @param {!string} name The name of the module to create or retrieve.
2067 * @param {!Array.<string>=} requires If specified then new module is being created. If
2068 * unspecified then the module is being retrieved for further configuration.
2069 * @param {Function=} configFn Optional configuration function for the module. Same as
2070 * {@link angular.Module#config Module#config()}.
2071 * @returns {module} new module with the {@link angular.Module} api.
2073 return function module(name, requires, configFn) {
2074 var assertNotHasOwnProperty = function(name, context) {
2075 if (name === 'hasOwnProperty') {
2076 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
2080 assertNotHasOwnProperty(name, 'module');
2081 if (requires && modules.hasOwnProperty(name)) {
2082 modules[name] = null;
2084 return ensure(modules, name, function() {
2086 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
2087 "the module name or forgot to load it. If registering a module ensure that you " +
2088 "specify the dependencies as the second argument.", name);
2091 /** @type {!Array.<Array.<*>>} */
2092 var invokeQueue = [];
2094 /** @type {!Array.<Function>} */
2095 var configBlocks = [];
2097 /** @type {!Array.<Function>} */
2100 var config = invokeLater('$injector', 'invoke', 'push', configBlocks);
2102 /** @type {angular.Module} */
2103 var moduleInstance = {
2105 _invokeQueue: invokeQueue,
2106 _configBlocks: configBlocks,
2107 _runBlocks: runBlocks,
2111 * @name angular.Module#requires
2115 * Holds the list of modules which the injector will load before the current module is
2122 * @name angular.Module#name
2126 * Name of the module.
2133 * @name angular.Module#provider
2135 * @param {string} name service name
2136 * @param {Function} providerType Construction function for creating new instance of the
2139 * See {@link auto.$provide#provider $provide.provider()}.
2141 provider: invokeLaterAndSetModuleName('$provide', 'provider'),
2145 * @name angular.Module#factory
2147 * @param {string} name service name
2148 * @param {Function} providerFunction Function for creating new instance of the service.
2150 * See {@link auto.$provide#factory $provide.factory()}.
2152 factory: invokeLaterAndSetModuleName('$provide', 'factory'),
2156 * @name angular.Module#service
2158 * @param {string} name service name
2159 * @param {Function} constructor A constructor function that will be instantiated.
2161 * See {@link auto.$provide#service $provide.service()}.
2163 service: invokeLaterAndSetModuleName('$provide', 'service'),
2167 * @name angular.Module#value
2169 * @param {string} name service name
2170 * @param {*} object Service instance object.
2172 * See {@link auto.$provide#value $provide.value()}.
2174 value: invokeLater('$provide', 'value'),
2178 * @name angular.Module#constant
2180 * @param {string} name constant name
2181 * @param {*} object Constant value.
2183 * Because the constants are fixed, they get applied before other provide methods.
2184 * See {@link auto.$provide#constant $provide.constant()}.
2186 constant: invokeLater('$provide', 'constant', 'unshift'),
2190 * @name angular.Module#decorator
2192 * @param {string} The name of the service to decorate.
2193 * @param {Function} This function will be invoked when the service needs to be
2194 * instantiated and should return the decorated service instance.
2196 * See {@link auto.$provide#decorator $provide.decorator()}.
2198 decorator: invokeLaterAndSetModuleName('$provide', 'decorator'),
2202 * @name angular.Module#animation
2204 * @param {string} name animation name
2205 * @param {Function} animationFactory Factory function for creating new instance of an
2209 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
2212 * Defines an animation hook that can be later used with
2213 * {@link $animate $animate} service and directives that use this service.
2216 * module.animation('.animation-name', function($inject1, $inject2) {
2218 * eventName : function(element, done) {
2219 * //code to run the animation
2220 * //once complete, then run done()
2221 * return function cancellationFunction(element) {
2222 * //code to cancel the animation
2229 * See {@link ng.$animateProvider#register $animateProvider.register()} and
2230 * {@link ngAnimate ngAnimate module} for more information.
2232 animation: invokeLaterAndSetModuleName('$animateProvider', 'register'),
2236 * @name angular.Module#filter
2238 * @param {string} name Filter name - this must be a valid angular expression identifier
2239 * @param {Function} filterFactory Factory function for creating new instance of filter.
2241 * See {@link ng.$filterProvider#register $filterProvider.register()}.
2243 * <div class="alert alert-warning">
2244 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
2245 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
2246 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
2247 * (`myapp_subsection_filterx`).
2250 filter: invokeLaterAndSetModuleName('$filterProvider', 'register'),
2254 * @name angular.Module#controller
2256 * @param {string|Object} name Controller name, or an object map of controllers where the
2257 * keys are the names and the values are the constructors.
2258 * @param {Function} constructor Controller constructor function.
2260 * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
2262 controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'),
2266 * @name angular.Module#directive
2268 * @param {string|Object} name Directive name, or an object map of directives where the
2269 * keys are the names and the values are the factories.
2270 * @param {Function} directiveFactory Factory function for creating new instance of
2273 * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
2275 directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
2279 * @name angular.Module#config
2281 * @param {Function} configFn Execute this function on module load. Useful for service
2284 * Use this method to register work which needs to be performed on module loading.
2285 * For more about how to configure services, see
2286 * {@link providers#provider-recipe Provider Recipe}.
2292 * @name angular.Module#run
2294 * @param {Function} initializationFn Execute this function after injector creation.
2295 * Useful for application initialization.
2297 * Use this method to register work which should be performed when the injector is done
2298 * loading all modules.
2300 run: function(block) {
2301 runBlocks.push(block);
2310 return moduleInstance;
2313 * @param {string} provider
2314 * @param {string} method
2315 * @param {String=} insertMethod
2316 * @returns {angular.Module}
2318 function invokeLater(provider, method, insertMethod, queue) {
2319 if (!queue) queue = invokeQueue;
2321 queue[insertMethod || 'push']([provider, method, arguments]);
2322 return moduleInstance;
2327 * @param {string} provider
2328 * @param {string} method
2329 * @returns {angular.Module}
2331 function invokeLaterAndSetModuleName(provider, method) {
2332 return function(recipeName, factoryFunction) {
2333 if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
2334 invokeQueue.push([provider, method, arguments]);
2335 return moduleInstance;
2344 /* global: toDebugString: true */
2346 function serializeObject(obj) {
2349 return JSON.stringify(obj, function(key, val) {
2350 val = toJsonReplacer(key, val);
2351 if (isObject(val)) {
2353 if (seen.indexOf(val) >= 0) return '...';
2361 function toDebugString(obj) {
2362 if (typeof obj === 'function') {
2363 return obj.toString().replace(/ \{[\s\S]*$/, '');
2364 } else if (isUndefined(obj)) {
2366 } else if (typeof obj !== 'string') {
2367 return serializeObject(obj);
2372 /* global angularModule: true,
2377 htmlAnchorDirective,
2386 ngBindHtmlDirective,
2387 ngBindTemplateDirective,
2389 ngClassEvenDirective,
2390 ngClassOddDirective,
2392 ngControllerDirective,
2397 ngIncludeFillContentDirective,
2399 ngNonBindableDirective,
2400 ngPluralizeDirective,
2405 ngSwitchWhenDirective,
2406 ngSwitchDefaultDirective,
2408 ngTranscludeDirective,
2421 ngModelOptionsDirective,
2422 ngAttributeAliasDirectives,
2425 $AnchorScrollProvider,
2427 $CoreAnimateCssProvider,
2428 $$CoreAnimateQueueProvider,
2429 $$CoreAnimateRunnerProvider,
2431 $CacheFactoryProvider,
2432 $ControllerProvider,
2434 $ExceptionHandlerProvider,
2436 $$ForceReflowProvider,
2437 $InterpolateProvider,
2441 $HttpParamSerializerProvider,
2442 $HttpParamSerializerJQLikeProvider,
2443 $HttpBackendProvider,
2444 $xhrFactoryProvider,
2451 $$SanitizeUriProvider,
2453 $SceDelegateProvider,
2455 $TemplateCacheProvider,
2456 $TemplateRequestProvider,
2457 $$TestabilityProvider,
2462 $$CookieReaderProvider
2468 * @name angular.version
2471 * An object that contains information about the current AngularJS version.
2473 * This object has the following properties:
2475 * - `full` – `{string}` – Full version string, such as "0.9.18".
2476 * - `major` – `{number}` – Major version number, such as "0".
2477 * - `minor` – `{number}` – Minor version number, such as "9".
2478 * - `dot` – `{number}` – Dot version number, such as "18".
2479 * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
2482 full: '1.4.8', // all of these placeholder strings will be replaced by grunt's
2483 major: 1, // package task
2486 codeName: 'ice-manipulation'
2490 function publishExternalAPI(angular) {
2492 'bootstrap': bootstrap,
2499 'injector': createInjector,
2503 'fromJson': fromJson,
2504 'identity': identity,
2505 'isUndefined': isUndefined,
2506 'isDefined': isDefined,
2507 'isString': isString,
2508 'isFunction': isFunction,
2509 'isObject': isObject,
2510 'isNumber': isNumber,
2511 'isElement': isElement,
2515 'lowercase': lowercase,
2516 'uppercase': uppercase,
2517 'callbacks': {counter: 0},
2518 'getTestability': getTestability,
2521 'reloadWithDebugInfo': reloadWithDebugInfo
2524 angularModule = setupModuleLoader(window);
2526 angularModule('ng', ['ngLocale'], ['$provide',
2527 function ngModule($provide) {
2528 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
2530 $$sanitizeUri: $$SanitizeUriProvider
2532 $provide.provider('$compile', $CompileProvider).
2534 a: htmlAnchorDirective,
2535 input: inputDirective,
2536 textarea: inputDirective,
2537 form: formDirective,
2538 script: scriptDirective,
2539 select: selectDirective,
2540 style: styleDirective,
2541 option: optionDirective,
2542 ngBind: ngBindDirective,
2543 ngBindHtml: ngBindHtmlDirective,
2544 ngBindTemplate: ngBindTemplateDirective,
2545 ngClass: ngClassDirective,
2546 ngClassEven: ngClassEvenDirective,
2547 ngClassOdd: ngClassOddDirective,
2548 ngCloak: ngCloakDirective,
2549 ngController: ngControllerDirective,
2550 ngForm: ngFormDirective,
2551 ngHide: ngHideDirective,
2552 ngIf: ngIfDirective,
2553 ngInclude: ngIncludeDirective,
2554 ngInit: ngInitDirective,
2555 ngNonBindable: ngNonBindableDirective,
2556 ngPluralize: ngPluralizeDirective,
2557 ngRepeat: ngRepeatDirective,
2558 ngShow: ngShowDirective,
2559 ngStyle: ngStyleDirective,
2560 ngSwitch: ngSwitchDirective,
2561 ngSwitchWhen: ngSwitchWhenDirective,
2562 ngSwitchDefault: ngSwitchDefaultDirective,
2563 ngOptions: ngOptionsDirective,
2564 ngTransclude: ngTranscludeDirective,
2565 ngModel: ngModelDirective,
2566 ngList: ngListDirective,
2567 ngChange: ngChangeDirective,
2568 pattern: patternDirective,
2569 ngPattern: patternDirective,
2570 required: requiredDirective,
2571 ngRequired: requiredDirective,
2572 minlength: minlengthDirective,
2573 ngMinlength: minlengthDirective,
2574 maxlength: maxlengthDirective,
2575 ngMaxlength: maxlengthDirective,
2576 ngValue: ngValueDirective,
2577 ngModelOptions: ngModelOptionsDirective
2580 ngInclude: ngIncludeFillContentDirective
2582 directive(ngAttributeAliasDirectives).
2583 directive(ngEventDirectives);
2585 $anchorScroll: $AnchorScrollProvider,
2586 $animate: $AnimateProvider,
2587 $animateCss: $CoreAnimateCssProvider,
2588 $$animateQueue: $$CoreAnimateQueueProvider,
2589 $$AnimateRunner: $$CoreAnimateRunnerProvider,
2590 $browser: $BrowserProvider,
2591 $cacheFactory: $CacheFactoryProvider,
2592 $controller: $ControllerProvider,
2593 $document: $DocumentProvider,
2594 $exceptionHandler: $ExceptionHandlerProvider,
2595 $filter: $FilterProvider,
2596 $$forceReflow: $$ForceReflowProvider,
2597 $interpolate: $InterpolateProvider,
2598 $interval: $IntervalProvider,
2599 $http: $HttpProvider,
2600 $httpParamSerializer: $HttpParamSerializerProvider,
2601 $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
2602 $httpBackend: $HttpBackendProvider,
2603 $xhrFactory: $xhrFactoryProvider,
2604 $location: $LocationProvider,
2606 $parse: $ParseProvider,
2607 $rootScope: $RootScopeProvider,
2611 $sceDelegate: $SceDelegateProvider,
2612 $sniffer: $SnifferProvider,
2613 $templateCache: $TemplateCacheProvider,
2614 $templateRequest: $TemplateRequestProvider,
2615 $$testability: $$TestabilityProvider,
2616 $timeout: $TimeoutProvider,
2617 $window: $WindowProvider,
2618 $$rAF: $$RAFProvider,
2619 $$jqLite: $$jqLiteProvider,
2620 $$HashMap: $$HashMapProvider,
2621 $$cookieReader: $$CookieReaderProvider
2627 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2628 * Any commits to this file should be reviewed with security in mind. *
2629 * Changes to this file can potentially create security vulnerabilities. *
2630 * An approval from 2 Core members with history of modifying *
2631 * this file is required. *
2633 * Does the change somehow allow for arbitrary javascript to be executed? *
2634 * Or allows for someone to change the prototype of built-in objects? *
2635 * Or gives undesired access to variables likes document or window? *
2636 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2638 /* global JQLitePrototype: true,
2639 addEventListenerFn: true,
2640 removeEventListenerFn: true,
2645 //////////////////////////////////
2647 //////////////////////////////////
2651 * @name angular.element
2656 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
2658 * If jQuery is available, `angular.element` is an alias for the
2659 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element`
2660 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite."
2662 * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows
2663 * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most
2664 * commonly needed functionality with the goal of having a very small footprint.</div>
2666 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file.
2668 * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or
2669 * jqLite; they are never raw DOM references.</div>
2671 * ## Angular's jqLite
2672 * jqLite provides only the following jQuery methods:
2674 * - [`addClass()`](http://api.jquery.com/addClass/)
2675 * - [`after()`](http://api.jquery.com/after/)
2676 * - [`append()`](http://api.jquery.com/append/)
2677 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
2678 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData
2679 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
2680 * - [`clone()`](http://api.jquery.com/clone/)
2681 * - [`contents()`](http://api.jquery.com/contents/)
2682 * - [`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'.
2683 * - [`data()`](http://api.jquery.com/data/)
2684 * - [`detach()`](http://api.jquery.com/detach/)
2685 * - [`empty()`](http://api.jquery.com/empty/)
2686 * - [`eq()`](http://api.jquery.com/eq/)
2687 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name
2688 * - [`hasClass()`](http://api.jquery.com/hasClass/)
2689 * - [`html()`](http://api.jquery.com/html/)
2690 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors
2691 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
2692 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter
2693 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors
2694 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors
2695 * - [`prepend()`](http://api.jquery.com/prepend/)
2696 * - [`prop()`](http://api.jquery.com/prop/)
2697 * - [`ready()`](http://api.jquery.com/ready/)
2698 * - [`remove()`](http://api.jquery.com/remove/)
2699 * - [`removeAttr()`](http://api.jquery.com/removeAttr/)
2700 * - [`removeClass()`](http://api.jquery.com/removeClass/)
2701 * - [`removeData()`](http://api.jquery.com/removeData/)
2702 * - [`replaceWith()`](http://api.jquery.com/replaceWith/)
2703 * - [`text()`](http://api.jquery.com/text/)
2704 * - [`toggleClass()`](http://api.jquery.com/toggleClass/)
2705 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers.
2706 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter
2707 * - [`val()`](http://api.jquery.com/val/)
2708 * - [`wrap()`](http://api.jquery.com/wrap/)
2710 * ## jQuery/jqLite Extras
2711 * Angular also provides the following additional methods and events to both jQuery and jqLite:
2714 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event
2715 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM
2716 * element before it is removed.
2719 * - `controller(name)` - retrieves the controller of the current element or its parent. By default
2720 * retrieves controller associated with the `ngController` directive. If `name` is provided as
2721 * camelCase directive name, then the controller for this directive will be retrieved (e.g.
2723 * - `injector()` - retrieves the injector of the current element or its parent.
2724 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current
2725 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to
2727 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the
2728 * current element. This getter should be used only on elements that contain a directive which starts a new isolate
2729 * scope. Calling `scope()` on this element always returns the original non-isolate scope.
2730 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled.
2731 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
2732 * parent element is reached.
2734 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
2735 * @returns {Object} jQuery object.
2738 JQLite.expando = 'ng339';
2740 var jqCache = JQLite.cache = {},
2742 addEventListenerFn = function(element, type, fn) {
2743 element.addEventListener(type, fn, false);
2745 removeEventListenerFn = function(element, type, fn) {
2746 element.removeEventListener(type, fn, false);
2750 * !!! This is an undocumented "private" function !!!
2752 JQLite._data = function(node) {
2753 //jQuery always returns an object on cache miss
2754 return this.cache[node[this.expando]] || {};
2757 function jqNextId() { return ++jqId; }
2760 var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
2761 var MOZ_HACK_REGEXP = /^moz([A-Z])/;
2762 var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"};
2763 var jqLiteMinErr = minErr('jqLite');
2766 * Converts snake_case to camelCase.
2767 * Also there is special case for Moz prefix starting with upper case letter.
2768 * @param name Name to normalize
2770 function camelCase(name) {
2772 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
2773 return offset ? letter.toUpperCase() : letter;
2775 replace(MOZ_HACK_REGEXP, 'Moz$1');
2778 var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/;
2779 var HTML_REGEXP = /<|&#?\w+;/;
2780 var TAG_NAME_REGEXP = /<([\w:-]+)/;
2781 var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi;
2784 'option': [1, '<select multiple="multiple">', '</select>'],
2786 'thead': [1, '<table>', '</table>'],
2787 'col': [2, '<table><colgroup>', '</colgroup></table>'],
2788 'tr': [2, '<table><tbody>', '</tbody></table>'],
2789 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'],
2790 '_default': [0, "", ""]
2793 wrapMap.optgroup = wrapMap.option;
2794 wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
2795 wrapMap.th = wrapMap.td;
2798 function jqLiteIsTextNode(html) {
2799 return !HTML_REGEXP.test(html);
2802 function jqLiteAcceptsData(node) {
2803 // The window object can accept data but has no nodeType
2804 // Otherwise we are only interested in elements (1) and documents (9)
2805 var nodeType = node.nodeType;
2806 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
2809 function jqLiteHasData(node) {
2810 for (var key in jqCache[node.ng339]) {
2816 function jqLiteBuildFragment(html, context) {
2818 fragment = context.createDocumentFragment(),
2821 if (jqLiteIsTextNode(html)) {
2822 // Convert non-html into a text node
2823 nodes.push(context.createTextNode(html));
2825 // Convert html into DOM nodes
2826 tmp = tmp || fragment.appendChild(context.createElement("div"));
2827 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase();
2828 wrap = wrapMap[tag] || wrapMap._default;
2829 tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2];
2831 // Descend through wrappers to the right content
2834 tmp = tmp.lastChild;
2837 nodes = concat(nodes, tmp.childNodes);
2839 tmp = fragment.firstChild;
2840 tmp.textContent = "";
2843 // Remove wrapper from fragment
2844 fragment.textContent = "";
2845 fragment.innerHTML = ""; // Clear inner HTML
2846 forEach(nodes, function(node) {
2847 fragment.appendChild(node);
2853 function jqLiteParseHTML(html, context) {
2854 context = context || document;
2857 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) {
2858 return [context.createElement(parsed[1])];
2861 if ((parsed = jqLiteBuildFragment(html, context))) {
2862 return parsed.childNodes;
2869 // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
2870 var jqLiteContains = Node.prototype.contains || function(arg) {
2871 // jshint bitwise: false
2872 return !!(this.compareDocumentPosition(arg) & 16);
2873 // jshint bitwise: true
2876 /////////////////////////////////////////////
2877 function JQLite(element) {
2878 if (element instanceof JQLite) {
2884 if (isString(element)) {
2885 element = trim(element);
2888 if (!(this instanceof JQLite)) {
2889 if (argIsString && element.charAt(0) != '<') {
2890 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
2892 return new JQLite(element);
2896 jqLiteAddNodes(this, jqLiteParseHTML(element));
2898 jqLiteAddNodes(this, element);
2902 function jqLiteClone(element) {
2903 return element.cloneNode(true);
2906 function jqLiteDealoc(element, onlyDescendants) {
2907 if (!onlyDescendants) jqLiteRemoveData(element);
2909 if (element.querySelectorAll) {
2910 var descendants = element.querySelectorAll('*');
2911 for (var i = 0, l = descendants.length; i < l; i++) {
2912 jqLiteRemoveData(descendants[i]);
2917 function jqLiteOff(element, type, fn, unsupported) {
2918 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument');
2920 var expandoStore = jqLiteExpandoStore(element);
2921 var events = expandoStore && expandoStore.events;
2922 var handle = expandoStore && expandoStore.handle;
2924 if (!handle) return; //no listeners registered
2927 for (type in events) {
2928 if (type !== '$destroy') {
2929 removeEventListenerFn(element, type, handle);
2931 delete events[type];
2935 var removeHandler = function(type) {
2936 var listenerFns = events[type];
2937 if (isDefined(fn)) {
2938 arrayRemove(listenerFns || [], fn);
2940 if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) {
2941 removeEventListenerFn(element, type, handle);
2942 delete events[type];
2946 forEach(type.split(' '), function(type) {
2947 removeHandler(type);
2948 if (MOUSE_EVENT_MAP[type]) {
2949 removeHandler(MOUSE_EVENT_MAP[type]);
2955 function jqLiteRemoveData(element, name) {
2956 var expandoId = element.ng339;
2957 var expandoStore = expandoId && jqCache[expandoId];
2961 delete expandoStore.data[name];
2965 if (expandoStore.handle) {
2966 if (expandoStore.events.$destroy) {
2967 expandoStore.handle({}, '$destroy');
2971 delete jqCache[expandoId];
2972 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
2977 function jqLiteExpandoStore(element, createIfNecessary) {
2978 var expandoId = element.ng339,
2979 expandoStore = expandoId && jqCache[expandoId];
2981 if (createIfNecessary && !expandoStore) {
2982 element.ng339 = expandoId = jqNextId();
2983 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined};
2986 return expandoStore;
2990 function jqLiteData(element, key, value) {
2991 if (jqLiteAcceptsData(element)) {
2993 var isSimpleSetter = isDefined(value);
2994 var isSimpleGetter = !isSimpleSetter && key && !isObject(key);
2995 var massGetter = !key;
2996 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter);
2997 var data = expandoStore && expandoStore.data;
2999 if (isSimpleSetter) { // data('key', value)
3002 if (massGetter) { // data()
3005 if (isSimpleGetter) { // data('key')
3006 // don't force creation of expandoStore if it doesn't exist yet
3007 return data && data[key];
3008 } else { // mass-setter: data({key1: val1, key2: val2})
3016 function jqLiteHasClass(element, selector) {
3017 if (!element.getAttribute) return false;
3018 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " ").
3019 indexOf(" " + selector + " ") > -1);
3022 function jqLiteRemoveClass(element, cssClasses) {
3023 if (cssClasses && element.setAttribute) {
3024 forEach(cssClasses.split(' '), function(cssClass) {
3025 element.setAttribute('class', trim(
3026 (" " + (element.getAttribute('class') || '') + " ")
3027 .replace(/[\n\t]/g, " ")
3028 .replace(" " + trim(cssClass) + " ", " "))
3034 function jqLiteAddClass(element, cssClasses) {
3035 if (cssClasses && element.setAttribute) {
3036 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ')
3037 .replace(/[\n\t]/g, " ");
3039 forEach(cssClasses.split(' '), function(cssClass) {
3040 cssClass = trim(cssClass);
3041 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
3042 existingClasses += cssClass + ' ';
3046 element.setAttribute('class', trim(existingClasses));
3051 function jqLiteAddNodes(root, elements) {
3052 // THIS CODE IS VERY HOT. Don't make changes without benchmarking.
3056 // if a Node (the most common case)
3057 if (elements.nodeType) {
3058 root[root.length++] = elements;
3060 var length = elements.length;
3062 // if an Array or NodeList and not a Window
3063 if (typeof length === 'number' && elements.window !== elements) {
3065 for (var i = 0; i < length; i++) {
3066 root[root.length++] = elements[i];
3070 root[root.length++] = elements;
3077 function jqLiteController(element, name) {
3078 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller');
3081 function jqLiteInheritedData(element, name, value) {
3082 // if element is the document object work with the html element instead
3083 // this makes $(document).scope() possible
3084 if (element.nodeType == NODE_TYPE_DOCUMENT) {
3085 element = element.documentElement;
3087 var names = isArray(name) ? name : [name];
3090 for (var i = 0, ii = names.length; i < ii; i++) {
3091 if (isDefined(value = jqLite.data(element, names[i]))) return value;
3094 // If dealing with a document fragment node with a host element, and no parent, use the host
3095 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
3096 // to lookup parent controllers.
3097 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host);
3101 function jqLiteEmpty(element) {
3102 jqLiteDealoc(element, true);
3103 while (element.firstChild) {
3104 element.removeChild(element.firstChild);
3108 function jqLiteRemove(element, keepData) {
3109 if (!keepData) jqLiteDealoc(element);
3110 var parent = element.parentNode;
3111 if (parent) parent.removeChild(element);
3115 function jqLiteDocumentLoaded(action, win) {
3116 win = win || window;
3117 if (win.document.readyState === 'complete') {
3118 // Force the action to be run async for consistent behaviour
3119 // from the action's point of view
3120 // i.e. it will definitely not be in a $apply
3121 win.setTimeout(action);
3123 // No need to unbind this handler as load is only ever called once
3124 jqLite(win).on('load', action);
3128 //////////////////////////////////////////
3129 // Functions which are declared directly.
3130 //////////////////////////////////////////
3131 var JQLitePrototype = JQLite.prototype = {
3132 ready: function(fn) {
3135 function trigger() {
3141 // check if document is already loaded
3142 if (document.readyState === 'complete') {
3143 setTimeout(trigger);
3145 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9
3146 // we can not use jqLite since we are not done loading and jQuery could be loaded later.
3148 JQLite(window).on('load', trigger); // fallback to window.onload for others
3152 toString: function() {
3154 forEach(this, function(e) { value.push('' + e);});
3155 return '[' + value.join(', ') + ']';
3158 eq: function(index) {
3159 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
3168 //////////////////////////////////////////
3169 // Functions iterating getter/setters.
3170 // these functions return self on setter and
3172 //////////////////////////////////////////
3173 var BOOLEAN_ATTR = {};
3174 forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) {
3175 BOOLEAN_ATTR[lowercase(value)] = value;
3177 var BOOLEAN_ELEMENTS = {};
3178 forEach('input,select,option,textarea,button,form,details'.split(','), function(value) {
3179 BOOLEAN_ELEMENTS[value] = true;
3181 var ALIASED_ATTR = {
3182 'ngMinlength': 'minlength',
3183 'ngMaxlength': 'maxlength',
3186 'ngPattern': 'pattern'
3189 function getBooleanAttrName(element, name) {
3190 // check dom last since we will most likely fail on name
3191 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
3193 // booleanAttr is here twice to minimize DOM access
3194 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
3197 function getAliasedAttrName(name) {
3198 return ALIASED_ATTR[name];
3203 removeData: jqLiteRemoveData,
3204 hasData: jqLiteHasData
3205 }, function(fn, name) {
3211 inheritedData: jqLiteInheritedData,
3213 scope: function(element) {
3214 // Can't use jqLiteData here directly so we stay compatible with jQuery!
3215 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']);
3218 isolateScope: function(element) {
3219 // Can't use jqLiteData here directly so we stay compatible with jQuery!
3220 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate');
3223 controller: jqLiteController,
3225 injector: function(element) {
3226 return jqLiteInheritedData(element, '$injector');
3229 removeAttr: function(element, name) {
3230 element.removeAttribute(name);
3233 hasClass: jqLiteHasClass,
3235 css: function(element, name, value) {
3236 name = camelCase(name);
3238 if (isDefined(value)) {
3239 element.style[name] = value;
3241 return element.style[name];
3245 attr: function(element, name, value) {
3246 var nodeType = element.nodeType;
3247 if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) {
3250 var lowercasedName = lowercase(name);
3251 if (BOOLEAN_ATTR[lowercasedName]) {
3252 if (isDefined(value)) {
3254 element[name] = true;
3255 element.setAttribute(name, lowercasedName);
3257 element[name] = false;
3258 element.removeAttribute(lowercasedName);
3261 return (element[name] ||
3262 (element.attributes.getNamedItem(name) || noop).specified)
3266 } else if (isDefined(value)) {
3267 element.setAttribute(name, value);
3268 } else if (element.getAttribute) {
3269 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code
3270 // some elements (e.g. Document) don't have get attribute, so return undefined
3271 var ret = element.getAttribute(name, 2);
3272 // normalize non-existing attributes to undefined (as jQuery)
3273 return ret === null ? undefined : ret;
3277 prop: function(element, name, value) {
3278 if (isDefined(value)) {
3279 element[name] = value;
3281 return element[name];
3289 function getText(element, value) {
3290 if (isUndefined(value)) {
3291 var nodeType = element.nodeType;
3292 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : '';
3294 element.textContent = value;
3298 val: function(element, value) {
3299 if (isUndefined(value)) {
3300 if (element.multiple && nodeName_(element) === 'select') {
3302 forEach(element.options, function(option) {
3303 if (option.selected) {
3304 result.push(option.value || option.text);
3307 return result.length === 0 ? null : result;
3309 return element.value;
3311 element.value = value;
3314 html: function(element, value) {
3315 if (isUndefined(value)) {
3316 return element.innerHTML;
3318 jqLiteDealoc(element, true);
3319 element.innerHTML = value;
3323 }, function(fn, name) {
3325 * Properties: writes return selection, reads return first value
3327 JQLite.prototype[name] = function(arg1, arg2) {
3329 var nodeCount = this.length;
3331 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
3332 // in a way that survives minification.
3333 // jqLiteEmpty takes no arguments but is a setter.
3334 if (fn !== jqLiteEmpty &&
3335 (isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) {
3336 if (isObject(arg1)) {
3338 // we are a write, but the object properties are the key/values
3339 for (i = 0; i < nodeCount; i++) {
3340 if (fn === jqLiteData) {
3341 // data() takes the whole object in jQuery
3345 fn(this[i], key, arg1[key]);
3349 // return self for chaining
3352 // we are a read, so read the first child.
3353 // TODO: do we still need this?
3355 // Only if we have $dv do we iterate over all, otherwise it is just the first element.
3356 var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount;
3357 for (var j = 0; j < jj; j++) {
3358 var nodeValue = fn(this[j], arg1, arg2);
3359 value = value ? value + nodeValue : nodeValue;
3364 // we are a write, so apply to all children
3365 for (i = 0; i < nodeCount; i++) {
3366 fn(this[i], arg1, arg2);
3368 // return self for chaining
3374 function createEventHandler(element, events) {
3375 var eventHandler = function(event, type) {
3376 // jQuery specific api
3377 event.isDefaultPrevented = function() {
3378 return event.defaultPrevented;
3381 var eventFns = events[type || event.type];
3382 var eventFnsLength = eventFns ? eventFns.length : 0;
3384 if (!eventFnsLength) return;
3386 if (isUndefined(event.immediatePropagationStopped)) {
3387 var originalStopImmediatePropagation = event.stopImmediatePropagation;
3388 event.stopImmediatePropagation = function() {
3389 event.immediatePropagationStopped = true;
3391 if (event.stopPropagation) {
3392 event.stopPropagation();
3395 if (originalStopImmediatePropagation) {
3396 originalStopImmediatePropagation.call(event);
3401 event.isImmediatePropagationStopped = function() {
3402 return event.immediatePropagationStopped === true;
3405 // Some events have special handlers that wrap the real handler
3406 var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper;
3408 // Copy event handlers in case event handlers array is modified during execution.
3409 if ((eventFnsLength > 1)) {
3410 eventFns = shallowCopy(eventFns);
3413 for (var i = 0; i < eventFnsLength; i++) {
3414 if (!event.isImmediatePropagationStopped()) {
3415 handlerWrapper(element, event, eventFns[i]);
3420 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all
3421 // events on `element`
3422 eventHandler.elem = element;
3423 return eventHandler;
3426 function defaultHandlerWrapper(element, event, handler) {
3427 handler.call(element, event);
3430 function specialMouseHandlerWrapper(target, event, handler) {
3431 // Refer to jQuery's implementation of mouseenter & mouseleave
3432 // Read about mouseenter and mouseleave:
3433 // http://www.quirksmode.org/js/events_mouse.html#link8
3434 var related = event.relatedTarget;
3435 // For mousenter/leave call the handler if related is outside the target.
3436 // NB: No relatedTarget if the mouse left/entered the browser window
3437 if (!related || (related !== target && !jqLiteContains.call(target, related))) {
3438 handler.call(target, event);
3442 //////////////////////////////////////////
3443 // Functions iterating traversal.
3444 // These functions chain results into a single
3446 //////////////////////////////////////////
3448 removeData: jqLiteRemoveData,
3450 on: function jqLiteOn(element, type, fn, unsupported) {
3451 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters');
3453 // Do not add event handlers to non-elements because they will not be cleaned up.
3454 if (!jqLiteAcceptsData(element)) {
3458 var expandoStore = jqLiteExpandoStore(element, true);
3459 var events = expandoStore.events;
3460 var handle = expandoStore.handle;
3463 handle = expandoStore.handle = createEventHandler(element, events);
3466 // http://jsperf.com/string-indexof-vs-split
3467 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type];
3468 var i = types.length;
3470 var addHandler = function(type, specialHandlerWrapper, noEventListener) {
3471 var eventFns = events[type];
3474 eventFns = events[type] = [];
3475 eventFns.specialHandlerWrapper = specialHandlerWrapper;
3476 if (type !== '$destroy' && !noEventListener) {
3477 addEventListenerFn(element, type, handle);
3486 if (MOUSE_EVENT_MAP[type]) {
3487 addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper);
3488 addHandler(type, undefined, true);
3497 one: function(element, type, fn) {
3498 element = jqLite(element);
3500 //add the listener twice so that when it is called
3501 //you can remove the original function and still be
3502 //able to call element.off(ev, fn) normally
3503 element.on(type, function onFn() {
3504 element.off(type, fn);
3505 element.off(type, onFn);
3507 element.on(type, fn);
3510 replaceWith: function(element, replaceNode) {
3511 var index, parent = element.parentNode;
3512 jqLiteDealoc(element);
3513 forEach(new JQLite(replaceNode), function(node) {
3515 parent.insertBefore(node, index.nextSibling);
3517 parent.replaceChild(node, element);
3523 children: function(element) {
3525 forEach(element.childNodes, function(element) {
3526 if (element.nodeType === NODE_TYPE_ELEMENT) {
3527 children.push(element);
3533 contents: function(element) {
3534 return element.contentDocument || element.childNodes || [];
3537 append: function(element, node) {
3538 var nodeType = element.nodeType;
3539 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return;
3541 node = new JQLite(node);
3543 for (var i = 0, ii = node.length; i < ii; i++) {
3544 var child = node[i];
3545 element.appendChild(child);
3549 prepend: function(element, node) {
3550 if (element.nodeType === NODE_TYPE_ELEMENT) {
3551 var index = element.firstChild;
3552 forEach(new JQLite(node), function(child) {
3553 element.insertBefore(child, index);
3558 wrap: function(element, wrapNode) {
3559 wrapNode = jqLite(wrapNode).eq(0).clone()[0];
3560 var parent = element.parentNode;
3562 parent.replaceChild(wrapNode, element);
3564 wrapNode.appendChild(element);
3567 remove: jqLiteRemove,
3569 detach: function(element) {
3570 jqLiteRemove(element, true);
3573 after: function(element, newElement) {
3574 var index = element, parent = element.parentNode;
3575 newElement = new JQLite(newElement);
3577 for (var i = 0, ii = newElement.length; i < ii; i++) {
3578 var node = newElement[i];
3579 parent.insertBefore(node, index.nextSibling);
3584 addClass: jqLiteAddClass,
3585 removeClass: jqLiteRemoveClass,
3587 toggleClass: function(element, selector, condition) {
3589 forEach(selector.split(' '), function(className) {
3590 var classCondition = condition;
3591 if (isUndefined(classCondition)) {
3592 classCondition = !jqLiteHasClass(element, className);
3594 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className);
3599 parent: function(element) {
3600 var parent = element.parentNode;
3601 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null;
3604 next: function(element) {
3605 return element.nextElementSibling;
3608 find: function(element, selector) {
3609 if (element.getElementsByTagName) {
3610 return element.getElementsByTagName(selector);
3618 triggerHandler: function(element, event, extraParameters) {
3620 var dummyEvent, eventFnsCopy, handlerArgs;
3621 var eventName = event.type || event;
3622 var expandoStore = jqLiteExpandoStore(element);
3623 var events = expandoStore && expandoStore.events;
3624 var eventFns = events && events[eventName];
3627 // Create a dummy event to pass to the handlers
3629 preventDefault: function() { this.defaultPrevented = true; },
3630 isDefaultPrevented: function() { return this.defaultPrevented === true; },
3631 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; },
3632 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; },
3633 stopPropagation: noop,
3638 // If a custom event was provided then extend our dummy event with it
3640 dummyEvent = extend(dummyEvent, event);
3643 // Copy event handlers in case event handlers array is modified during execution.
3644 eventFnsCopy = shallowCopy(eventFns);
3645 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent];
3647 forEach(eventFnsCopy, function(fn) {
3648 if (!dummyEvent.isImmediatePropagationStopped()) {
3649 fn.apply(element, handlerArgs);
3654 }, function(fn, name) {
3656 * chaining functions
3658 JQLite.prototype[name] = function(arg1, arg2, arg3) {
3661 for (var i = 0, ii = this.length; i < ii; i++) {
3662 if (isUndefined(value)) {
3663 value = fn(this[i], arg1, arg2, arg3);
3664 if (isDefined(value)) {
3665 // any function which returns a value needs to be wrapped
3666 value = jqLite(value);
3669 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3));
3672 return isDefined(value) ? value : this;
3675 // bind legacy bind/unbind to on/off
3676 JQLite.prototype.bind = JQLite.prototype.on;
3677 JQLite.prototype.unbind = JQLite.prototype.off;
3681 // Provider for private $$jqLite service
3682 function $$jqLiteProvider() {
3683 this.$get = function $$jqLite() {
3684 return extend(JQLite, {
3685 hasClass: function(node, classes) {
3686 if (node.attr) node = node[0];
3687 return jqLiteHasClass(node, classes);
3689 addClass: function(node, classes) {
3690 if (node.attr) node = node[0];
3691 return jqLiteAddClass(node, classes);
3693 removeClass: function(node, classes) {
3694 if (node.attr) node = node[0];
3695 return jqLiteRemoveClass(node, classes);
3702 * Computes a hash of an 'obj'.
3705 * number is number as string
3706 * object is either result of calling $$hashKey function on the object or uniquely generated id,
3707 * that is also assigned to the $$hashKey property of the object.
3710 * @returns {string} hash string such that the same input will have the same hash string.
3711 * The resulting string key is in 'type:hashKey' format.
3713 function hashKey(obj, nextUidFn) {
3714 var key = obj && obj.$$hashKey;
3717 if (typeof key === 'function') {
3718 key = obj.$$hashKey();
3723 var objType = typeof obj;
3724 if (objType == 'function' || (objType == 'object' && obj !== null)) {
3725 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)();
3727 key = objType + ':' + obj;
3734 * HashMap which can use objects as keys
3736 function HashMap(array, isolatedUid) {
3739 this.nextUid = function() {
3743 forEach(array, this.put, this);
3745 HashMap.prototype = {
3747 * Store key value pair
3748 * @param key key to store can be any type
3749 * @param value value to store can be any type
3751 put: function(key, value) {
3752 this[hashKey(key, this.nextUid)] = value;
3757 * @returns {Object} the value for the key
3759 get: function(key) {
3760 return this[hashKey(key, this.nextUid)];
3764 * Remove the key/value pair
3767 remove: function(key) {
3768 var value = this[key = hashKey(key, this.nextUid)];
3774 var $$HashMapProvider = [function() {
3775 this.$get = [function() {
3783 * @name angular.injector
3787 * Creates an injector object that can be used for retrieving services as well as for
3788 * dependency injection (see {@link guide/di dependency injection}).
3790 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
3791 * {@link angular.module}. The `ng` module must be explicitly added.
3792 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which
3793 * disallows argument name annotation inference.
3794 * @returns {injector} Injector object. See {@link auto.$injector $injector}.
3799 * // create an injector
3800 * var $injector = angular.injector(['ng']);
3802 * // use the injector to kick off your application
3803 * // use the type inference to auto inject arguments, or use implicit injection
3804 * $injector.invoke(function($rootScope, $compile, $document) {
3805 * $compile($document)($rootScope);
3806 * $rootScope.$digest();
3810 * Sometimes you want to get access to the injector of a currently running Angular app
3811 * from outside Angular. Perhaps, you want to inject and compile some markup after the
3812 * application has been bootstrapped. You can do this using the extra `injector()` added
3813 * to JQuery/jqLite elements. See {@link angular.element}.
3815 * *This is fairly rare but could be the case if a third party library is injecting the
3818 * In the following example a new block of HTML containing a `ng-controller`
3819 * directive is added to the end of the document body by JQuery. We then compile and link
3820 * it into the current AngularJS scope.
3823 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>');
3824 * $(document.body).append($div);
3826 * angular.element(document).injector().invoke(function($compile) {
3827 * var scope = angular.element($div).scope();
3828 * $compile($div)(scope);
3839 * Implicit module which gets automatically added to each {@link auto.$injector $injector}.
3842 var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m;
3843 var FN_ARG_SPLIT = /,/;
3844 var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
3845 var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
3846 var $injectorMinErr = minErr('$injector');
3848 function anonFn(fn) {
3849 // For anonymous functions, showing at the very least the function signature can help in
3851 var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
3852 args = fnText.match(FN_ARGS);
3854 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
3859 function annotate(fn, strictDi, name) {
3865 if (typeof fn === 'function') {
3866 if (!($inject = fn.$inject)) {
3870 if (!isString(name) || !name) {
3871 name = fn.name || anonFn(fn);
3873 throw $injectorMinErr('strictdi',
3874 '{0} is not using explicit annotation and cannot be invoked in strict mode', name);
3876 fnText = fn.toString().replace(STRIP_COMMENTS, '');
3877 argDecl = fnText.match(FN_ARGS);
3878 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
3879 arg.replace(FN_ARG, function(all, underscore, name) {
3884 fn.$inject = $inject;
3886 } else if (isArray(fn)) {
3887 last = fn.length - 1;
3888 assertArgFn(fn[last], 'fn');
3889 $inject = fn.slice(0, last);
3891 assertArgFn(fn, 'fn', true);
3896 ///////////////////////////////////////
3904 * `$injector` is used to retrieve object instances as defined by
3905 * {@link auto.$provide provider}, instantiate types, invoke methods,
3908 * The following always holds true:
3911 * var $injector = angular.injector();
3912 * expect($injector.get('$injector')).toBe($injector);
3913 * expect($injector.invoke(function($injector) {
3915 * })).toBe($injector);
3918 * # Injection Function Annotation
3920 * JavaScript does not have annotations, and annotations are needed for dependency injection. The
3921 * following are all valid ways of annotating function with injection arguments and are equivalent.
3924 * // inferred (only works if code not minified/obfuscated)
3925 * $injector.invoke(function(serviceA){});
3928 * function explicit(serviceA) {};
3929 * explicit.$inject = ['serviceA'];
3930 * $injector.invoke(explicit);
3933 * $injector.invoke(['serviceA', function(serviceA){}]);
3938 * In JavaScript calling `toString()` on a function returns the function definition. The definition
3939 * can then be parsed and the function arguments can be extracted. This method of discovering
3940 * annotations is disallowed when the injector is in strict mode.
3941 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the
3944 * ## `$inject` Annotation
3945 * By adding an `$inject` property onto a function the injection parameters can be specified.
3948 * As an array of injection names, where the last item in the array is the function to call.
3953 * @name $injector#get
3956 * Return an instance of the service.
3958 * @param {string} name The name of the instance to retrieve.
3959 * @param {string=} caller An optional string to provide the origin of the function call for error messages.
3960 * @return {*} The instance.
3965 * @name $injector#invoke
3968 * Invoke the method and supply the method arguments from the `$injector`.
3970 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are
3971 * injected according to the {@link guide/di $inject Annotation} rules.
3972 * @param {Object=} self The `this` for the invoked method.
3973 * @param {Object=} locals Optional object. If preset then any argument names are read from this
3974 * object first, before the `$injector` is consulted.
3975 * @returns {*} the value returned by the invoked `fn` function.
3980 * @name $injector#has
3983 * Allows the user to query if the particular service exists.
3985 * @param {string} name Name of the service to query.
3986 * @returns {boolean} `true` if injector has given service.
3991 * @name $injector#instantiate
3993 * Create a new instance of JS type. The method takes a constructor function, invokes the new
3994 * operator, and supplies all of the arguments to the constructor function as specified by the
3995 * constructor annotation.
3997 * @param {Function} Type Annotated constructor function.
3998 * @param {Object=} locals Optional object. If preset then any argument names are read from this
3999 * object first, before the `$injector` is consulted.
4000 * @returns {Object} new instance of `Type`.
4005 * @name $injector#annotate
4008 * Returns an array of service names which the function is requesting for injection. This API is
4009 * used by the injector to determine which services need to be injected into the function when the
4010 * function is invoked. There are three ways in which the function can be annotated with the needed
4015 * The simplest form is to extract the dependencies from the arguments of the function. This is done
4016 * by converting the function into a string using `toString()` method and extracting the argument
4020 * function MyController($scope, $route) {
4025 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
4028 * You can disallow this method by using strict injection mode.
4030 * This method does not work with code minification / obfuscation. For this reason the following
4031 * annotation strategies are supported.
4033 * # The `$inject` property
4035 * If a function has an `$inject` property and its value is an array of strings, then the strings
4036 * represent names of services to be injected into the function.
4039 * var MyController = function(obfuscatedScope, obfuscatedRoute) {
4042 * // Define function dependencies
4043 * MyController['$inject'] = ['$scope', '$route'];
4046 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
4049 * # The array notation
4051 * It is often desirable to inline Injected functions and that's when setting the `$inject` property
4052 * is very inconvenient. In these situations using the array notation to specify the dependencies in
4053 * a way that survives minification is a better choice:
4056 * // We wish to write this (not minification / obfuscation safe)
4057 * injector.invoke(function($compile, $rootScope) {
4061 * // We are forced to write break inlining
4062 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
4065 * tmpFn.$inject = ['$compile', '$rootScope'];
4066 * injector.invoke(tmpFn);
4068 * // To better support inline function the inline annotation is supported
4069 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
4074 * expect(injector.annotate(
4075 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
4076 * ).toEqual(['$compile', '$rootScope']);
4079 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to
4080 * be retrieved as described above.
4082 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference.
4084 * @returns {Array.<string>} The names of the services which the function requires.
4096 * The {@link auto.$provide $provide} service has a number of methods for registering components
4097 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on
4098 * {@link angular.Module}.
4100 * An Angular **service** is a singleton object created by a **service factory**. These **service
4101 * factories** are functions which, in turn, are created by a **service provider**.
4102 * The **service providers** are constructor functions. When instantiated they must contain a
4103 * property called `$get`, which holds the **service factory** function.
4105 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the
4106 * correct **service provider**, instantiating it and then calling its `$get` **service factory**
4107 * function to get the instance of the **service**.
4109 * Often services have no configuration options and there is no need to add methods to the service
4110 * provider. The provider will be no more than a constructor function with a `$get` property. For
4111 * these cases the {@link auto.$provide $provide} service has additional helper methods to register
4112 * services without specifying a provider.
4114 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the
4115 * {@link auto.$injector $injector}
4116 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by
4117 * providers and services.
4118 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by
4119 * services, not providers.
4120 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`,
4121 * that will be wrapped in a **service provider** object, whose `$get` property will contain the
4122 * given factory function.
4123 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class`
4124 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate
4125 * a new object using the given constructor function.
4127 * See the individual methods for more information and examples.
4132 * @name $provide#provider
4135 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions
4136 * are constructor functions, whose instances are responsible for "providing" a factory for a
4139 * Service provider names start with the name of the service they provide followed by `Provider`.
4140 * For example, the {@link ng.$log $log} service has a provider called
4141 * {@link ng.$logProvider $logProvider}.
4143 * Service provider objects can have additional methods which allow configuration of the provider
4144 * and its service. Importantly, you can configure what kind of service is created by the `$get`
4145 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a
4146 * method {@link ng.$logProvider#debugEnabled debugEnabled}
4147 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the
4150 * @param {string} name The name of the instance. NOTE: the provider will be available under `name +
4152 * @param {(Object|function())} provider If the provider is:
4154 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using
4155 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created.
4156 * - `Constructor`: a new instance of the provider will be created using
4157 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`.
4159 * @returns {Object} registered provider instance
4163 * The following example shows how to create a simple event tracking service and register it using
4164 * {@link auto.$provide#provider $provide.provider()}.
4167 * // Define the eventTracker provider
4168 * function EventTrackerProvider() {
4169 * var trackingUrl = '/track';
4171 * // A provider method for configuring where the tracked events should been saved
4172 * this.setTrackingUrl = function(url) {
4173 * trackingUrl = url;
4176 * // The service factory function
4177 * this.$get = ['$http', function($http) {
4178 * var trackedEvents = {};
4180 * // Call this to track an event
4181 * event: function(event) {
4182 * var count = trackedEvents[event] || 0;
4184 * trackedEvents[event] = count;
4187 * // Call this to save the tracked events to the trackingUrl
4188 * save: function() {
4189 * $http.post(trackingUrl, trackedEvents);
4195 * describe('eventTracker', function() {
4198 * beforeEach(module(function($provide) {
4199 * // Register the eventTracker provider
4200 * $provide.provider('eventTracker', EventTrackerProvider);
4203 * beforeEach(module(function(eventTrackerProvider) {
4204 * // Configure eventTracker provider
4205 * eventTrackerProvider.setTrackingUrl('/custom-track');
4208 * it('tracks events', inject(function(eventTracker) {
4209 * expect(eventTracker.event('login')).toEqual(1);
4210 * expect(eventTracker.event('login')).toEqual(2);
4213 * it('saves to the tracking url', inject(function(eventTracker, $http) {
4214 * postSpy = spyOn($http, 'post');
4215 * eventTracker.event('login');
4216 * eventTracker.save();
4217 * expect(postSpy).toHaveBeenCalled();
4218 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track');
4219 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track');
4220 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 });
4228 * @name $provide#factory
4231 * Register a **service factory**, which will be called to return the service instance.
4232 * This is short for registering a service where its provider consists of only a `$get` property,
4233 * which is the given service factory function.
4234 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to
4235 * configure your service in a provider.
4237 * @param {string} name The name of the instance.
4238 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation.
4239 * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`.
4240 * @returns {Object} registered provider instance
4243 * Here is an example of registering a service
4245 * $provide.factory('ping', ['$http', function($http) {
4246 * return function ping() {
4247 * return $http.send('/ping');
4251 * You would then inject and use this service like this:
4253 * someModule.controller('Ctrl', ['ping', function(ping) {
4262 * @name $provide#service
4265 * Register a **service constructor**, which will be invoked with `new` to create the service
4267 * This is short for registering a service where its provider's `$get` property is the service
4268 * constructor function that will be used to instantiate the service instance.
4270 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service
4273 * @param {string} name The name of the instance.
4274 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function)
4275 * that will be instantiated.
4276 * @returns {Object} registered provider instance
4279 * Here is an example of registering a service using
4280 * {@link auto.$provide#service $provide.service(class)}.
4282 * var Ping = function($http) {
4283 * this.$http = $http;
4286 * Ping.$inject = ['$http'];
4288 * Ping.prototype.send = function() {
4289 * return this.$http.get('/ping');
4291 * $provide.service('ping', Ping);
4293 * You would then inject and use this service like this:
4295 * someModule.controller('Ctrl', ['ping', function(ping) {
4304 * @name $provide#value
4307 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a
4308 * number, an array, an object or a function. This is short for registering a service where its
4309 * provider's `$get` property is a factory function that takes no arguments and returns the **value
4312 * Value services are similar to constant services, except that they cannot be injected into a
4313 * module configuration function (see {@link angular.Module#config}) but they can be overridden by
4315 * {@link auto.$provide#decorator decorator}.
4317 * @param {string} name The name of the instance.
4318 * @param {*} value The value.
4319 * @returns {Object} registered provider instance
4322 * Here are some examples of creating value services.
4324 * $provide.value('ADMIN_USER', 'admin');
4326 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 });
4328 * $provide.value('halfOf', function(value) {
4337 * @name $provide#constant
4340 * Register a **constant service**, such as a string, a number, an array, an object or a function,
4341 * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be
4342 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot
4343 * be overridden by an Angular {@link auto.$provide#decorator decorator}.
4345 * @param {string} name The name of the constant.
4346 * @param {*} value The constant value.
4347 * @returns {Object} registered instance
4350 * Here a some examples of creating constants:
4352 * $provide.constant('SHARD_HEIGHT', 306);
4354 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']);
4356 * $provide.constant('double', function(value) {
4365 * @name $provide#decorator
4368 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator
4369 * intercepts the creation of a service, allowing it to override or modify the behaviour of the
4370 * service. The object returned by the decorator may be the original service, or a new service
4371 * object which replaces or wraps and delegates to the original service.
4373 * @param {string} name The name of the service to decorate.
4374 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be
4375 * instantiated and should return the decorated service instance. The function is called using
4376 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable.
4377 * Local injection arguments:
4379 * * `$delegate` - The original service instance, which can be monkey patched, configured,
4380 * decorated or delegated to.
4383 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting
4384 * calls to {@link ng.$log#error $log.warn()}.
4386 * $provide.decorator('$log', ['$delegate', function($delegate) {
4387 * $delegate.warn = $delegate.error;
4394 function createInjector(modulesToLoad, strictDi) {
4395 strictDi = (strictDi === true);
4396 var INSTANTIATING = {},
4397 providerSuffix = 'Provider',
4399 loadedModules = new HashMap([], true),
4402 provider: supportObject(provider),
4403 factory: supportObject(factory),
4404 service: supportObject(service),
4405 value: supportObject(value),
4406 constant: supportObject(constant),
4407 decorator: decorator
4410 providerInjector = (providerCache.$injector =
4411 createInternalInjector(providerCache, function(serviceName, caller) {
4412 if (angular.isString(caller)) {
4415 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
4418 instanceInjector = (instanceCache.$injector =
4419 createInternalInjector(instanceCache, function(serviceName, caller) {
4420 var provider = providerInjector.get(serviceName + providerSuffix, caller);
4421 return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
4425 forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });
4427 return instanceInjector;
4429 ////////////////////////////////////
4431 ////////////////////////////////////
4433 function supportObject(delegate) {
4434 return function(key, value) {
4435 if (isObject(key)) {
4436 forEach(key, reverseParams(delegate));
4438 return delegate(key, value);
4443 function provider(name, provider_) {
4444 assertNotHasOwnProperty(name, 'service');
4445 if (isFunction(provider_) || isArray(provider_)) {
4446 provider_ = providerInjector.instantiate(provider_);
4448 if (!provider_.$get) {
4449 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name);
4451 return providerCache[name + providerSuffix] = provider_;
4454 function enforceReturnValue(name, factory) {
4455 return function enforcedReturnValue() {
4456 var result = instanceInjector.invoke(factory, this);
4457 if (isUndefined(result)) {
4458 throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
4464 function factory(name, factoryFn, enforce) {
4465 return provider(name, {
4466 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
4470 function service(name, constructor) {
4471 return factory(name, ['$injector', function($injector) {
4472 return $injector.instantiate(constructor);
4476 function value(name, val) { return factory(name, valueFn(val), false); }
4478 function constant(name, value) {
4479 assertNotHasOwnProperty(name, 'constant');
4480 providerCache[name] = value;
4481 instanceCache[name] = value;
4484 function decorator(serviceName, decorFn) {
4485 var origProvider = providerInjector.get(serviceName + providerSuffix),
4486 orig$get = origProvider.$get;
4488 origProvider.$get = function() {
4489 var origInstance = instanceInjector.invoke(orig$get, origProvider);
4490 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
4494 ////////////////////////////////////
4496 ////////////////////////////////////
4497 function loadModules(modulesToLoad) {
4498 assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array');
4499 var runBlocks = [], moduleFn;
4500 forEach(modulesToLoad, function(module) {
4501 if (loadedModules.get(module)) return;
4502 loadedModules.put(module, true);
4504 function runInvokeQueue(queue) {
4506 for (i = 0, ii = queue.length; i < ii; i++) {
4507 var invokeArgs = queue[i],
4508 provider = providerInjector.get(invokeArgs[0]);
4510 provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
4515 if (isString(module)) {
4516 moduleFn = angularModule(module);
4517 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
4518 runInvokeQueue(moduleFn._invokeQueue);
4519 runInvokeQueue(moduleFn._configBlocks);
4520 } else if (isFunction(module)) {
4521 runBlocks.push(providerInjector.invoke(module));
4522 } else if (isArray(module)) {
4523 runBlocks.push(providerInjector.invoke(module));
4525 assertArgFn(module, 'module');
4528 if (isArray(module)) {
4529 module = module[module.length - 1];
4531 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) {
4532 // Safari & FF's stack traces don't contain error.message content
4533 // unlike those of Chrome and IE
4534 // So if stack doesn't contain message, we create a new string that contains both.
4535 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
4537 e = e.message + '\n' + e.stack;
4539 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}",
4540 module, e.stack || e.message || e);
4546 ////////////////////////////////////
4547 // internal Injector
4548 ////////////////////////////////////
4550 function createInternalInjector(cache, factory) {
4552 function getService(serviceName, caller) {
4553 if (cache.hasOwnProperty(serviceName)) {
4554 if (cache[serviceName] === INSTANTIATING) {
4555 throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
4556 serviceName + ' <- ' + path.join(' <- '));
4558 return cache[serviceName];
4561 path.unshift(serviceName);
4562 cache[serviceName] = INSTANTIATING;
4563 return cache[serviceName] = factory(serviceName, caller);
4565 if (cache[serviceName] === INSTANTIATING) {
4566 delete cache[serviceName];
4575 function invoke(fn, self, locals, serviceName) {
4576 if (typeof locals === 'string') {
4577 serviceName = locals;
4582 $inject = createInjector.$$annotate(fn, strictDi, serviceName),
4586 for (i = 0, length = $inject.length; i < length; i++) {
4588 if (typeof key !== 'string') {
4589 throw $injectorMinErr('itkn',
4590 'Incorrect injection token! Expected service name as string, got {0}', key);
4593 locals && locals.hasOwnProperty(key)
4595 : getService(key, serviceName)
4602 // http://jsperf.com/angularjs-invoke-apply-vs-switch
4604 return fn.apply(self, args);
4607 function instantiate(Type, locals, serviceName) {
4608 // Check if Type is annotated and use just the given function at n-1 as parameter
4609 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
4610 // Object creation: http://jsperf.com/create-constructor/2
4611 var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
4612 var returnedValue = invoke(Type, instance, locals, serviceName);
4614 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
4619 instantiate: instantiate,
4621 annotate: createInjector.$$annotate,
4622 has: function(name) {
4623 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
4629 createInjector.$$annotate = annotate;
4633 * @name $anchorScrollProvider
4636 * Use `$anchorScrollProvider` to disable automatic scrolling whenever
4637 * {@link ng.$location#hash $location.hash()} changes.
4639 function $AnchorScrollProvider() {
4641 var autoScrollingEnabled = true;
4645 * @name $anchorScrollProvider#disableAutoScrolling
4648 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to
4649 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br />
4650 * Use this method to disable automatic scrolling.
4652 * If automatic scrolling is disabled, one must explicitly call
4653 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the
4656 this.disableAutoScrolling = function() {
4657 autoScrollingEnabled = false;
4662 * @name $anchorScroll
4665 * @requires $location
4666 * @requires $rootScope
4669 * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the
4670 * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified
4672 * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#the-indicated-part-of-the-document).
4674 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
4675 * match any anchor whenever it changes. This can be disabled by calling
4676 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}.
4678 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a
4679 * vertical scroll-offset (either fixed or dynamic).
4681 * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of
4682 * {@link ng.$location#hash $location.hash()} will be used.
4684 * @property {(number|function|jqLite)} yOffset
4685 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed
4686 * positioned elements at the top of the page, such as navbars, headers etc.
4688 * `yOffset` can be specified in various ways:
4689 * - **number**: A fixed number of pixels to be used as offset.<br /><br />
4690 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return
4691 * a number representing the offset (in pixels).<br /><br />
4692 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from
4693 * the top of the page to the element's bottom will be used as offset.<br />
4694 * **Note**: The element will be taken into account only as long as its `position` is set to
4695 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust
4696 * their height and/or positioning according to the viewport's size.
4699 * <div class="alert alert-warning">
4700 * In order for `yOffset` to work properly, scrolling should take place on the document's root and
4701 * not some child element.
4705 <example module="anchorScrollExample">
4706 <file name="index.html">
4707 <div id="scrollArea" ng-controller="ScrollController">
4708 <a ng-click="gotoBottom()">Go to bottom</a>
4709 <a id="bottom"></a> You're at the bottom!
4712 <file name="script.js">
4713 angular.module('anchorScrollExample', [])
4714 .controller('ScrollController', ['$scope', '$location', '$anchorScroll',
4715 function ($scope, $location, $anchorScroll) {
4716 $scope.gotoBottom = function() {
4717 // set the location.hash to the id of
4718 // the element you wish to scroll to.
4719 $location.hash('bottom');
4721 // call $anchorScroll()
4726 <file name="style.css">
4740 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value).
4741 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details.
4744 <example module="anchorScrollOffsetExample">
4745 <file name="index.html">
4746 <div class="fixed-header" ng-controller="headerCtrl">
4747 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]">
4751 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]">
4755 <file name="script.js">
4756 angular.module('anchorScrollOffsetExample', [])
4757 .run(['$anchorScroll', function($anchorScroll) {
4758 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels
4760 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope',
4761 function ($anchorScroll, $location, $scope) {
4762 $scope.gotoAnchor = function(x) {
4763 var newHash = 'anchor' + x;
4764 if ($location.hash() !== newHash) {
4765 // set the $location.hash to `newHash` and
4766 // $anchorScroll will automatically scroll to it
4767 $location.hash('anchor' + x);
4769 // call $anchorScroll() explicitly,
4770 // since $location.hash hasn't changed
4777 <file name="style.css">
4783 border: 2px dashed DarkOrchid;
4784 padding: 10px 10px 200px 10px;
4788 background-color: rgba(0, 0, 0, 0.2);
4791 top: 0; left: 0; right: 0;
4795 display: inline-block;
4801 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
4802 var document = $window.document;
4804 // Helper function to get first anchor from a NodeList
4805 // (using `Array#some()` instead of `angular#forEach()` since it's more performant
4806 // and working in all supported browsers.)
4807 function getFirstAnchor(list) {
4809 Array.prototype.some.call(list, function(element) {
4810 if (nodeName_(element) === 'a') {
4818 function getYOffset() {
4820 var offset = scroll.yOffset;
4822 if (isFunction(offset)) {
4824 } else if (isElement(offset)) {
4825 var elem = offset[0];
4826 var style = $window.getComputedStyle(elem);
4827 if (style.position !== 'fixed') {
4830 offset = elem.getBoundingClientRect().bottom;
4832 } else if (!isNumber(offset)) {
4839 function scrollTo(elem) {
4841 elem.scrollIntoView();
4843 var offset = getYOffset();
4846 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly.
4847 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the
4848 // top of the viewport.
4850 // IF the number of pixels from the top of `elem` to the end of the page's content is less
4851 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some
4852 // way down the page.
4854 // This is often the case for elements near the bottom of the page.
4856 // In such cases we do not need to scroll the whole `offset` up, just the difference between
4857 // the top of the element and the offset, which is enough to align the top of `elem` at the
4858 // desired position.
4859 var elemTop = elem.getBoundingClientRect().top;
4860 $window.scrollBy(0, elemTop - offset);
4863 $window.scrollTo(0, 0);
4867 function scroll(hash) {
4868 hash = isString(hash) ? hash : $location.hash();
4871 // empty hash, scroll to the top of the page
4872 if (!hash) scrollTo(null);
4874 // element with given id
4875 else if ((elm = document.getElementById(hash))) scrollTo(elm);
4877 // first anchor with given name :-D
4878 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm);
4880 // no element and hash == 'top', scroll to the top of the page
4881 else if (hash === 'top') scrollTo(null);
4884 // does not scroll when user clicks on anchor link that is currently on
4885 // (no url change, no $location.hash() change), browser native does scroll
4886 if (autoScrollingEnabled) {
4887 $rootScope.$watch(function autoScrollWatch() {return $location.hash();},
4888 function autoScrollWatchAction(newVal, oldVal) {
4889 // skip the initial scroll if $location.hash is empty
4890 if (newVal === oldVal && newVal === '') return;
4892 jqLiteDocumentLoaded(function() {
4893 $rootScope.$evalAsync(scroll);
4902 var $animateMinErr = minErr('$animate');
4903 var ELEMENT_NODE = 1;
4904 var NG_ANIMATE_CLASSNAME = 'ng-animate';
4906 function mergeClasses(a,b) {
4907 if (!a && !b) return '';
4910 if (isArray(a)) a = a.join(' ');
4911 if (isArray(b)) b = b.join(' ');
4915 function extractElementNode(element) {
4916 for (var i = 0; i < element.length; i++) {
4917 var elm = element[i];
4918 if (elm.nodeType === ELEMENT_NODE) {
4924 function splitClasses(classes) {
4925 if (isString(classes)) {
4926 classes = classes.split(' ');
4929 // Use createMap() to prevent class assumptions involving property names in
4931 var obj = createMap();
4932 forEach(classes, function(klass) {
4933 // sometimes the split leaves empty string values
4934 // incase extra spaces were applied to the options
4942 // if any other type of options value besides an Object value is
4943 // passed into the $animate.method() animation then this helper code
4944 // will be run which will ignore it. While this patch is not the
4945 // greatest solution to this, a lot of existing plugins depend on
4946 // $animate to either call the callback (< 1.2) or return a promise
4947 // that can be changed. This helper function ensures that the options
4948 // are wiped clean incase a callback function is provided.
4949 function prepareAnimateOptions(options) {
4950 return isObject(options)
4955 var $$CoreAnimateRunnerProvider = function() {
4956 this.$get = ['$q', '$$rAF', function($q, $$rAF) {
4957 function AnimateRunner() {}
4958 AnimateRunner.all = noop;
4959 AnimateRunner.chain = noop;
4960 AnimateRunner.prototype = {
4966 then: function(pass, fail) {
4967 return $q(function(resolve) {
4971 }).then(pass, fail);
4974 return AnimateRunner;
4978 // this is prefixed with Core since it conflicts with
4979 // the animateQueueProvider defined in ngAnimate/animateQueue.js
4980 var $$CoreAnimateQueueProvider = function() {
4981 var postDigestQueue = new HashMap();
4982 var postDigestElements = [];
4984 this.$get = ['$$AnimateRunner', '$rootScope',
4985 function($$AnimateRunner, $rootScope) {
4992 push: function(element, event, options, domOperation) {
4993 domOperation && domOperation();
4995 options = options || {};
4996 options.from && element.css(options.from);
4997 options.to && element.css(options.to);
4999 if (options.addClass || options.removeClass) {
5000 addRemoveClassesPostDigest(element, options.addClass, options.removeClass);
5003 return new $$AnimateRunner(); // jshint ignore:line
5008 function updateData(data, classes, value) {
5009 var changed = false;
5011 classes = isString(classes) ? classes.split(' ') :
5012 isArray(classes) ? classes : [];
5013 forEach(classes, function(className) {
5016 data[className] = value;
5023 function handleCSSClassChanges() {
5024 forEach(postDigestElements, function(element) {
5025 var data = postDigestQueue.get(element);
5027 var existing = splitClasses(element.attr('class'));
5030 forEach(data, function(status, className) {
5031 var hasClass = !!existing[className];
5032 if (status !== hasClass) {
5034 toAdd += (toAdd.length ? ' ' : '') + className;
5036 toRemove += (toRemove.length ? ' ' : '') + className;
5041 forEach(element, function(elm) {
5042 toAdd && jqLiteAddClass(elm, toAdd);
5043 toRemove && jqLiteRemoveClass(elm, toRemove);
5045 postDigestQueue.remove(element);
5048 postDigestElements.length = 0;
5052 function addRemoveClassesPostDigest(element, add, remove) {
5053 var data = postDigestQueue.get(element) || {};
5055 var classesAdded = updateData(data, add, true);
5056 var classesRemoved = updateData(data, remove, false);
5058 if (classesAdded || classesRemoved) {
5060 postDigestQueue.put(element, data);
5061 postDigestElements.push(element);
5063 if (postDigestElements.length === 1) {
5064 $rootScope.$$postDigest(handleCSSClassChanges);
5073 * @name $animateProvider
5076 * Default implementation of $animate that doesn't perform any animations, instead just
5077 * synchronously performs DOM updates and resolves the returned runner promise.
5079 * In order to enable animations the `ngAnimate` module has to be loaded.
5081 * To see the functional implementation check out `src/ngAnimate/animate.js`.
5083 var $AnimateProvider = ['$provide', function($provide) {
5084 var provider = this;
5086 this.$$registeredAnimations = Object.create(null);
5090 * @name $animateProvider#register
5093 * Registers a new injectable animation factory function. The factory function produces the
5094 * animation object which contains callback functions for each event that is expected to be
5097 * * `eventFn`: `function(element, ... , doneFunction, options)`
5098 * The element to animate, the `doneFunction` and the options fed into the animation. Depending
5099 * on the type of animation additional arguments will be injected into the animation function. The
5100 * list below explains the function signatures for the different animation methods:
5102 * - setClass: function(element, addedClasses, removedClasses, doneFunction, options)
5103 * - addClass: function(element, addedClasses, doneFunction, options)
5104 * - removeClass: function(element, removedClasses, doneFunction, options)
5105 * - enter, leave, move: function(element, doneFunction, options)
5106 * - animate: function(element, fromStyles, toStyles, doneFunction, options)
5108 * Make sure to trigger the `doneFunction` once the animation is fully complete.
5112 * //enter, leave, move signature
5113 * eventFn : function(element, done, options) {
5114 * //code to run the animation
5115 * //once complete, then run done()
5116 * return function endFunction(wasCancelled) {
5117 * //code to cancel the animation
5123 * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to).
5124 * @param {Function} factory The factory function that will be executed to return the animation
5127 this.register = function(name, factory) {
5128 if (name && name.charAt(0) !== '.') {
5129 throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name);
5132 var key = name + '-animation';
5133 provider.$$registeredAnimations[name.substr(1)] = key;
5134 $provide.factory(key, factory);
5139 * @name $animateProvider#classNameFilter
5142 * Sets and/or returns the CSS class regular expression that is checked when performing
5143 * an animation. Upon bootstrap the classNameFilter value is not set at all and will
5144 * therefore enable $animate to attempt to perform an animation on any element that is triggered.
5145 * When setting the `classNameFilter` value, animations will only be performed on elements
5146 * that successfully match the filter expression. This in turn can boost performance
5147 * for low-powered devices as well as applications containing a lot of structural operations.
5148 * @param {RegExp=} expression The className expression which will be checked against all animations
5149 * @return {RegExp} The current CSS className expression value. If null then there is no expression value
5151 this.classNameFilter = function(expression) {
5152 if (arguments.length === 1) {
5153 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null;
5154 if (this.$$classNameFilter) {
5155 var reservedRegex = new RegExp("(\\s+|\\/)" + NG_ANIMATE_CLASSNAME + "(\\s+|\\/)");
5156 if (reservedRegex.test(this.$$classNameFilter.toString())) {
5157 throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME);
5162 return this.$$classNameFilter;
5165 this.$get = ['$$animateQueue', function($$animateQueue) {
5166 function domInsert(element, parentElement, afterElement) {
5167 // if for some reason the previous element was removed
5168 // from the dom sometime before this code runs then let's
5169 // just stick to using the parent element as the anchor
5171 var afterNode = extractElementNode(afterElement);
5172 if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) {
5173 afterElement = null;
5176 afterElement ? afterElement.after(element) : parentElement.prepend(element);
5182 * @description The $animate service exposes a series of DOM utility methods that provide support
5183 * for animation hooks. The default behavior is the application of DOM operations, however,
5184 * when an animation is detected (and animations are enabled), $animate will do the heavy lifting
5185 * to ensure that animation runs with the triggered DOM operation.
5187 * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't
5188 * included and only when it is active then the animation hooks that `$animate` triggers will be
5189 * functional. Once active then all structural `ng-` directives will trigger animations as they perform
5190 * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`,
5191 * `ngShow`, `ngHide` and `ngMessages` also provide support for animations.
5193 * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives.
5195 * To learn more about enabling animation support, click here to visit the
5196 * {@link ngAnimate ngAnimate module page}.
5199 // we don't call it directly since non-existant arguments may
5200 // be interpreted as null within the sub enabled function
5207 * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...)
5208 * has fired on the given element or among any of its children. Once the listener is fired, the provided callback
5209 * is fired with the following params:
5212 * $animate.on('enter', container,
5213 * function callback(element, phase) {
5214 * // cool we detected an enter animation within the container
5219 * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...)
5220 * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself
5221 * as well as among its children
5222 * @param {Function} callback the callback function that will be fired when the listener is triggered
5224 * The arguments present in the callback function are:
5225 * * `element` - The captured DOM element that the animation was fired on.
5226 * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends).
5228 on: $$animateQueue.on,
5233 * @name $animate#off
5235 * @description Deregisters an event listener based on the event which has been associated with the provided element. This method
5236 * can be used in three different ways depending on the arguments:
5239 * // remove all the animation event listeners listening for `enter`
5240 * $animate.off('enter');
5242 * // remove all the animation event listeners listening for `enter` on the given element and its children
5243 * $animate.off('enter', container);
5245 * // remove the event listener function provided by `listenerFn` that is set
5246 * // to listen for `enter` on the given `element` as well as its children
5247 * $animate.off('enter', container, callback);
5250 * @param {string} event the animation event (e.g. enter, leave, move, addClass, removeClass, etc...)
5251 * @param {DOMElement=} container the container element the event listener was placed on
5252 * @param {Function=} callback the callback function that was registered as the listener
5254 off: $$animateQueue.off,
5258 * @name $animate#pin
5260 * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists
5261 * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the
5262 * element despite being outside the realm of the application or within another application. Say for example if the application
5263 * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated
5264 * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind
5265 * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association.
5267 * Note that this feature is only active when the `ngAnimate` module is used.
5269 * @param {DOMElement} element the external element that will be pinned
5270 * @param {DOMElement} parentElement the host parent element that will be associated with the external element
5272 pin: $$animateQueue.pin,
5277 * @name $animate#enabled
5279 * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This
5280 * function can be called in four ways:
5283 * // returns true or false
5284 * $animate.enabled();
5286 * // changes the enabled state for all animations
5287 * $animate.enabled(false);
5288 * $animate.enabled(true);
5290 * // returns true or false if animations are enabled for an element
5291 * $animate.enabled(element);
5293 * // changes the enabled state for an element and its children
5294 * $animate.enabled(element, true);
5295 * $animate.enabled(element, false);
5298 * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state
5299 * @param {boolean=} enabled whether or not the animations will be enabled for the element
5301 * @return {boolean} whether or not animations are enabled
5303 enabled: $$animateQueue.enabled,
5307 * @name $animate#cancel
5309 * @description Cancels the provided animation.
5311 * @param {Promise} animationPromise The animation promise that is returned when an animation is started.
5313 cancel: function(runner) {
5314 runner.end && runner.end();
5320 * @name $animate#enter
5322 * @description Inserts the element into the DOM either after the `after` element (if provided) or
5323 * as the first child within the `parent` element and then triggers an animation.
5324 * A promise is returned that will be resolved during the next digest once the animation
5327 * @param {DOMElement} element the element which will be inserted into the DOM
5328 * @param {DOMElement} parent the parent element which will append the element as
5329 * a child (so long as the after element is not present)
5330 * @param {DOMElement=} after the sibling element after which the element will be appended
5331 * @param {object=} options an optional collection of options/styles that will be applied to the element
5333 * @return {Promise} the animation callback promise
5335 enter: function(element, parent, after, options) {
5336 parent = parent && jqLite(parent);
5337 after = after && jqLite(after);
5338 parent = parent || after.parent();
5339 domInsert(element, parent, after);
5340 return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options));
5346 * @name $animate#move
5348 * @description Inserts (moves) the element into its new position in the DOM either after
5349 * the `after` element (if provided) or as the first child within the `parent` element
5350 * and then triggers an animation. A promise is returned that will be resolved
5351 * during the next digest once the animation has completed.
5353 * @param {DOMElement} element the element which will be moved into the new DOM position
5354 * @param {DOMElement} parent the parent element which will append the element as
5355 * a child (so long as the after element is not present)
5356 * @param {DOMElement=} after the sibling element after which the element will be appended
5357 * @param {object=} options an optional collection of options/styles that will be applied to the element
5359 * @return {Promise} the animation callback promise
5361 move: function(element, parent, after, options) {
5362 parent = parent && jqLite(parent);
5363 after = after && jqLite(after);
5364 parent = parent || after.parent();
5365 domInsert(element, parent, after);
5366 return $$animateQueue.push(element, 'move', prepareAnimateOptions(options));
5371 * @name $animate#leave
5373 * @description Triggers an animation and then removes the element from the DOM.
5374 * When the function is called a promise is returned that will be resolved during the next
5375 * digest once the animation has completed.
5377 * @param {DOMElement} element the element which will be removed from the DOM
5378 * @param {object=} options an optional collection of options/styles that will be applied to the element
5380 * @return {Promise} the animation callback promise
5382 leave: function(element, options) {
5383 return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() {
5390 * @name $animate#addClass
5393 * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon
5394 * execution, the addClass operation will only be handled after the next digest and it will not trigger an
5395 * animation if element already contains the CSS class or if the class is removed at a later step.
5396 * Note that class-based animations are treated differently compared to structural animations
5397 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5398 * depending if CSS or JavaScript animations are used.
5400 * @param {DOMElement} element the element which the CSS classes will be applied to
5401 * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces)
5402 * @param {object=} options an optional collection of options/styles that will be applied to the element
5404 * @return {Promise} the animation callback promise
5406 addClass: function(element, className, options) {
5407 options = prepareAnimateOptions(options);
5408 options.addClass = mergeClasses(options.addclass, className);
5409 return $$animateQueue.push(element, 'addClass', options);
5414 * @name $animate#removeClass
5417 * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon
5418 * execution, the removeClass operation will only be handled after the next digest and it will not trigger an
5419 * animation if element does not contain the CSS class or if the class is added at a later step.
5420 * Note that class-based animations are treated differently compared to structural animations
5421 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5422 * depending if CSS or JavaScript animations are used.
5424 * @param {DOMElement} element the element which the CSS classes will be applied to
5425 * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces)
5426 * @param {object=} options an optional collection of options/styles that will be applied to the element
5428 * @return {Promise} the animation callback promise
5430 removeClass: function(element, className, options) {
5431 options = prepareAnimateOptions(options);
5432 options.removeClass = mergeClasses(options.removeClass, className);
5433 return $$animateQueue.push(element, 'removeClass', options);
5438 * @name $animate#setClass
5441 * @description Performs both the addition and removal of a CSS classes on an element and (during the process)
5442 * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and
5443 * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has
5444 * passed. Note that class-based animations are treated differently compared to structural animations
5445 * (like enter, move and leave) since the CSS classes may be added/removed at different points
5446 * depending if CSS or JavaScript animations are used.
5448 * @param {DOMElement} element the element which the CSS classes will be applied to
5449 * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces)
5450 * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces)
5451 * @param {object=} options an optional collection of options/styles that will be applied to the element
5453 * @return {Promise} the animation callback promise
5455 setClass: function(element, add, remove, options) {
5456 options = prepareAnimateOptions(options);
5457 options.addClass = mergeClasses(options.addClass, add);
5458 options.removeClass = mergeClasses(options.removeClass, remove);
5459 return $$animateQueue.push(element, 'setClass', options);
5464 * @name $animate#animate
5467 * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element.
5468 * If any detected CSS transition, keyframe or JavaScript matches the provided className value then the animation will take
5469 * on the provided styles. For example, if a transition animation is set for the given className then the provided from and
5470 * to styles will be applied alongside the given transition. If a JavaScript animation is detected then the provided styles
5471 * will be given in as function paramters into the `animate` method (or as apart of the `options` parameter).
5473 * @param {DOMElement} element the element which the CSS styles will be applied to
5474 * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation.
5475 * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation.
5476 * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If
5477 * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element.
5478 * (Note that if no animation is detected then this value will not be appplied to the element.)
5479 * @param {object=} options an optional collection of options/styles that will be applied to the element
5481 * @return {Promise} the animation callback promise
5483 animate: function(element, from, to, className, options) {
5484 options = prepareAnimateOptions(options);
5485 options.from = options.from ? extend(options.from, from) : from;
5486 options.to = options.to ? extend(options.to, to) : to;
5488 className = className || 'ng-inline-animate';
5489 options.tempClasses = mergeClasses(options.tempClasses, className);
5490 return $$animateQueue.push(element, 'animate', options);
5502 * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included,
5503 * then the `$animateCss` service will actually perform animations.
5505 * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}.
5507 var $CoreAnimateCssProvider = function() {
5508 this.$get = ['$$rAF', '$q', function($$rAF, $q) {
5510 var RAFPromise = function() {};
5511 RAFPromise.prototype = {
5512 done: function(cancel) {
5513 this.defer && this.defer[cancel === true ? 'reject' : 'resolve']();
5518 cancel: function() {
5521 getPromise: function() {
5523 this.defer = $q.defer();
5525 return this.defer.promise;
5527 then: function(f1,f2) {
5528 return this.getPromise().then(f1,f2);
5530 'catch': function(f1) {
5531 return this.getPromise()['catch'](f1);
5533 'finally': function(f1) {
5534 return this.getPromise()['finally'](f1);
5538 return function(element, options) {
5539 // there is no point in applying the styles since
5540 // there is no animation that goes on at all in
5541 // this version of $animateCss.
5542 if (options.cleanupStyles) {
5543 options.from = options.to = null;
5547 element.css(options.from);
5548 options.from = null;
5551 var closed, runner = new RAFPromise();
5569 if (options.addClass) {
5570 element.addClass(options.addClass);
5571 options.addClass = null;
5573 if (options.removeClass) {
5574 element.removeClass(options.removeClass);
5575 options.removeClass = null;
5578 element.css(options.to);
5586 /* global stripHash: true */
5589 * ! This is a private undocumented service !
5594 * This object has two goals:
5596 * - hide all the global state in the browser caused by the window object
5597 * - abstract away all the browser specific features and inconsistencies
5599 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser`
5600 * service, which can be used for convenient testing of the application without the interaction with
5601 * the real browser apis.
5604 * @param {object} window The global window object.
5605 * @param {object} document jQuery wrapped document.
5606 * @param {object} $log window.console or an object with the same interface.
5607 * @param {object} $sniffer $sniffer service
5609 function Browser(window, document, $log, $sniffer) {
5611 rawDocument = document[0],
5612 location = window.location,
5613 history = window.history,
5614 setTimeout = window.setTimeout,
5615 clearTimeout = window.clearTimeout,
5616 pendingDeferIds = {};
5618 self.isMock = false;
5620 var outstandingRequestCount = 0;
5621 var outstandingRequestCallbacks = [];
5623 // TODO(vojta): remove this temporary api
5624 self.$$completeOutstandingRequest = completeOutstandingRequest;
5625 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
5628 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
5629 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
5631 function completeOutstandingRequest(fn) {
5633 fn.apply(null, sliceArgs(arguments, 1));
5635 outstandingRequestCount--;
5636 if (outstandingRequestCount === 0) {
5637 while (outstandingRequestCallbacks.length) {
5639 outstandingRequestCallbacks.pop()();
5648 function getHash(url) {
5649 var index = url.indexOf('#');
5650 return index === -1 ? '' : url.substr(index);
5655 * Note: this method is used only by scenario runner
5656 * TODO(vojta): prefix this method with $$ ?
5657 * @param {function()} callback Function that will be called when no outstanding request
5659 self.notifyWhenNoOutstandingRequests = function(callback) {
5660 if (outstandingRequestCount === 0) {
5663 outstandingRequestCallbacks.push(callback);
5667 //////////////////////////////////////////////////////////////
5669 //////////////////////////////////////////////////////////////
5671 var cachedState, lastHistoryState,
5672 lastBrowserUrl = location.href,
5673 baseElement = document.find('base'),
5674 pendingLocation = null;
5677 lastHistoryState = cachedState;
5680 * @name $browser#url
5684 * Without any argument, this method just returns current value of location.href.
5687 * With at least one argument, this method sets url to new value.
5688 * If html5 history api supported, pushState/replaceState is used, otherwise
5689 * location.href/location.replace is used.
5690 * Returns its own instance to allow chaining
5692 * NOTE: this api is intended for use only by the $location service. Please use the
5693 * {@link ng.$location $location service} to change url.
5695 * @param {string} url New url (when used as setter)
5696 * @param {boolean=} replace Should new url replace current history record?
5697 * @param {object=} state object to use with pushState/replaceState
5699 self.url = function(url, replace, state) {
5700 // In modern browsers `history.state` is `null` by default; treating it separately
5701 // from `undefined` would cause `$browser.url('/foo')` to change `history.state`
5702 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here.
5703 if (isUndefined(state)) {
5707 // Android Browser BFCache causes location, history reference to become stale.
5708 if (location !== window.location) location = window.location;
5709 if (history !== window.history) history = window.history;
5713 var sameState = lastHistoryState === state;
5715 // Don't change anything if previous and current URLs and states match. This also prevents
5716 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
5717 // See https://github.com/angular/angular.js/commit/ffb2701
5718 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) {
5721 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
5722 lastBrowserUrl = url;
5723 lastHistoryState = state;
5724 // Don't use history API if only the hash changed
5725 // due to a bug in IE10/IE11 which leads
5726 // to not firing a `hashchange` nor `popstate` event
5727 // in some cases (see #9143).
5728 if ($sniffer.history && (!sameBase || !sameState)) {
5729 history[replace ? 'replaceState' : 'pushState'](state, '', url);
5731 // Do the assignment again so that those two variables are referentially identical.
5732 lastHistoryState = cachedState;
5734 if (!sameBase || pendingLocation) {
5735 pendingLocation = url;
5738 location.replace(url);
5739 } else if (!sameBase) {
5740 location.href = url;
5742 location.hash = getHash(url);
5744 if (location.href !== url) {
5745 pendingLocation = url;
5751 // - pendingLocation is needed as browsers don't allow to read out
5752 // the new location.href if a reload happened or if there is a bug like in iOS 9 (see
5753 // https://openradar.appspot.com/22186109).
5754 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
5755 return pendingLocation || location.href.replace(/%27/g,"'");
5760 * @name $browser#state
5763 * This method is a getter.
5765 * Return history.state or null if history.state is undefined.
5767 * @returns {object} state
5769 self.state = function() {
5773 var urlChangeListeners = [],
5774 urlChangeInit = false;
5776 function cacheStateAndFireUrlChange() {
5777 pendingLocation = null;
5782 function getCurrentState() {
5784 return history.state;
5786 // MSIE can reportedly throw when there is no state (UNCONFIRMED).
5790 // This variable should be used *only* inside the cacheState function.
5791 var lastCachedState = null;
5792 function cacheState() {
5793 // This should be the only place in $browser where `history.state` is read.
5794 cachedState = getCurrentState();
5795 cachedState = isUndefined(cachedState) ? null : cachedState;
5797 // Prevent callbacks fo fire twice if both hashchange & popstate were fired.
5798 if (equals(cachedState, lastCachedState)) {
5799 cachedState = lastCachedState;
5801 lastCachedState = cachedState;
5804 function fireUrlChange() {
5805 if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) {
5809 lastBrowserUrl = self.url();
5810 lastHistoryState = cachedState;
5811 forEach(urlChangeListeners, function(listener) {
5812 listener(self.url(), cachedState);
5817 * @name $browser#onUrlChange
5820 * Register callback function that will be called, when url changes.
5822 * It's only called when the url is changed from outside of angular:
5823 * - user types different url into address bar
5824 * - user clicks on history (forward/back) button
5825 * - user clicks on a link
5827 * It's not called when url is changed by $browser.url() method
5829 * The listener gets called with new url as parameter.
5831 * NOTE: this api is intended for use only by the $location service. Please use the
5832 * {@link ng.$location $location service} to monitor url changes in angular apps.
5834 * @param {function(string)} listener Listener function to be called when url changes.
5835 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous.
5837 self.onUrlChange = function(callback) {
5838 // TODO(vojta): refactor to use node's syntax for events
5839 if (!urlChangeInit) {
5840 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera)
5841 // don't fire popstate when user change the address bar and don't fire hashchange when url
5842 // changed by push/replaceState
5844 // html5 history api - popstate event
5845 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange);
5847 jqLite(window).on('hashchange', cacheStateAndFireUrlChange);
5849 urlChangeInit = true;
5852 urlChangeListeners.push(callback);
5858 * Remove popstate and hashchange handler from window.
5860 * NOTE: this api is intended for use only by $rootScope.
5862 self.$$applicationDestroyed = function() {
5863 jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange);
5867 * Checks whether the url has changed outside of Angular.
5868 * Needs to be exported to be able to check for changes that have been done in sync,
5869 * as hashchange/popstate events fire in async.
5871 self.$$checkUrlChange = fireUrlChange;
5873 //////////////////////////////////////////////////////////////
5875 //////////////////////////////////////////////////////////////
5878 * @name $browser#baseHref
5881 * Returns current <base href>
5882 * (always relative - without domain)
5884 * @returns {string} The current base href
5886 self.baseHref = function() {
5887 var href = baseElement.attr('href');
5888 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : '';
5892 * @name $browser#defer
5893 * @param {function()} fn A function, who's execution should be deferred.
5894 * @param {number=} [delay=0] of milliseconds to defer the function execution.
5895 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
5898 * Executes a fn asynchronously via `setTimeout(fn, delay)`.
5900 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
5901 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed
5902 * via `$browser.defer.flush()`.
5905 self.defer = function(fn, delay) {
5907 outstandingRequestCount++;
5908 timeoutId = setTimeout(function() {
5909 delete pendingDeferIds[timeoutId];
5910 completeOutstandingRequest(fn);
5912 pendingDeferIds[timeoutId] = true;
5918 * @name $browser#defer.cancel
5921 * Cancels a deferred task identified with `deferId`.
5923 * @param {*} deferId Token returned by the `$browser.defer` function.
5924 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
5927 self.defer.cancel = function(deferId) {
5928 if (pendingDeferIds[deferId]) {
5929 delete pendingDeferIds[deferId];
5930 clearTimeout(deferId);
5931 completeOutstandingRequest(noop);
5939 function $BrowserProvider() {
5940 this.$get = ['$window', '$log', '$sniffer', '$document',
5941 function($window, $log, $sniffer, $document) {
5942 return new Browser($window, $document, $log, $sniffer);
5948 * @name $cacheFactory
5951 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to
5956 * var cache = $cacheFactory('cacheId');
5957 * expect($cacheFactory.get('cacheId')).toBe(cache);
5958 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined();
5960 * cache.put("key", "value");
5961 * cache.put("another key", "another value");
5963 * // We've specified no options on creation
5964 * expect(cache.info()).toEqual({id: 'cacheId', size: 2});
5969 * @param {string} cacheId Name or id of the newly created cache.
5970 * @param {object=} options Options object that specifies the cache behavior. Properties:
5972 * - `{number=}` `capacity` — turns the cache into LRU cache.
5974 * @returns {object} Newly created cache object with the following set of methods:
5976 * - `{object}` `info()` — Returns id, size, and options of cache.
5977 * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns
5979 * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss.
5980 * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache.
5981 * - `{void}` `removeAll()` — Removes all cached values.
5982 * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory.
5985 <example module="cacheExampleApp">
5986 <file name="index.html">
5987 <div ng-controller="CacheController">
5988 <input ng-model="newCacheKey" placeholder="Key">
5989 <input ng-model="newCacheValue" placeholder="Value">
5990 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button>
5992 <p ng-if="keys.length">Cached Values</p>
5993 <div ng-repeat="key in keys">
5994 <span ng-bind="key"></span>
5996 <b ng-bind="cache.get(key)"></b>
6000 <div ng-repeat="(key, value) in cache.info()">
6001 <span ng-bind="key"></span>
6003 <b ng-bind="value"></b>
6007 <file name="script.js">
6008 angular.module('cacheExampleApp', []).
6009 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) {
6011 $scope.cache = $cacheFactory('cacheId');
6012 $scope.put = function(key, value) {
6013 if (angular.isUndefined($scope.cache.get(key))) {
6014 $scope.keys.push(key);
6016 $scope.cache.put(key, angular.isUndefined(value) ? null : value);
6020 <file name="style.css">
6027 function $CacheFactoryProvider() {
6029 this.$get = function() {
6032 function cacheFactory(cacheId, options) {
6033 if (cacheId in caches) {
6034 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId);
6038 stats = extend({}, options, {id: cacheId}),
6040 capacity = (options && options.capacity) || Number.MAX_VALUE,
6041 lruHash = createMap(),
6047 * @name $cacheFactory.Cache
6050 * A cache object used to store and retrieve data, primarily used by
6051 * {@link $http $http} and the {@link ng.directive:script script} directive to cache
6052 * templates and other data.
6055 * angular.module('superCache')
6056 * .factory('superCache', ['$cacheFactory', function($cacheFactory) {
6057 * return $cacheFactory('super-cache');
6064 * it('should behave like a cache', inject(function(superCache) {
6065 * superCache.put('key', 'value');
6066 * superCache.put('another key', 'another value');
6068 * expect(superCache.info()).toEqual({
6069 * id: 'super-cache',
6073 * superCache.remove('another key');
6074 * expect(superCache.get('another key')).toBeUndefined();
6076 * superCache.removeAll();
6077 * expect(superCache.info()).toEqual({
6078 * id: 'super-cache',
6084 return caches[cacheId] = {
6088 * @name $cacheFactory.Cache#put
6092 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be
6093 * retrieved later, and incrementing the size of the cache if the key was not already
6094 * present in the cache. If behaving like an LRU cache, it will also remove stale
6095 * entries from the set.
6097 * It will not insert undefined values into the cache.
6099 * @param {string} key the key under which the cached data is stored.
6100 * @param {*} value the value to store alongside the key. If it is undefined, the key
6101 * will not be stored.
6102 * @returns {*} the value stored.
6104 put: function(key, value) {
6105 if (isUndefined(value)) return;
6106 if (capacity < Number.MAX_VALUE) {
6107 var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
6112 if (!(key in data)) size++;
6115 if (size > capacity) {
6116 this.remove(staleEnd.key);
6124 * @name $cacheFactory.Cache#get
6128 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object.
6130 * @param {string} key the key of the data to be retrieved
6131 * @returns {*} the value stored.
6133 get: function(key) {
6134 if (capacity < Number.MAX_VALUE) {
6135 var lruEntry = lruHash[key];
6137 if (!lruEntry) return;
6148 * @name $cacheFactory.Cache#remove
6152 * Removes an entry from the {@link $cacheFactory.Cache Cache} object.
6154 * @param {string} key the key of the entry to be removed
6156 remove: function(key) {
6157 if (capacity < Number.MAX_VALUE) {
6158 var lruEntry = lruHash[key];
6160 if (!lruEntry) return;
6162 if (lruEntry == freshEnd) freshEnd = lruEntry.p;
6163 if (lruEntry == staleEnd) staleEnd = lruEntry.n;
6164 link(lruEntry.n,lruEntry.p);
6166 delete lruHash[key];
6169 if (!(key in data)) return;
6178 * @name $cacheFactory.Cache#removeAll
6182 * Clears the cache object of any entries.
6184 removeAll: function() {
6187 lruHash = createMap();
6188 freshEnd = staleEnd = null;
6194 * @name $cacheFactory.Cache#destroy
6198 * Destroys the {@link $cacheFactory.Cache Cache} object entirely,
6199 * removing it from the {@link $cacheFactory $cacheFactory} set.
6201 destroy: function() {
6205 delete caches[cacheId];
6211 * @name $cacheFactory.Cache#info
6215 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}.
6217 * @returns {object} an object with the following properties:
6219 * <li>**id**: the id of the cache instance</li>
6220 * <li>**size**: the number of entries kept in the cache instance</li>
6221 * <li>**...**: any additional properties from the options object when creating the
6226 return extend({}, stats, {size: size});
6232 * makes the `entry` the freshEnd of the LRU linked list
6234 function refresh(entry) {
6235 if (entry != freshEnd) {
6238 } else if (staleEnd == entry) {
6242 link(entry.n, entry.p);
6243 link(entry, freshEnd);
6251 * bidirectionally links two entries of the LRU linked list
6253 function link(nextEntry, prevEntry) {
6254 if (nextEntry != prevEntry) {
6255 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify
6256 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify
6264 * @name $cacheFactory#info
6267 * Get information about all the caches that have been created
6269 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info`
6271 cacheFactory.info = function() {
6273 forEach(caches, function(cache, cacheId) {
6274 info[cacheId] = cache.info();
6282 * @name $cacheFactory#get
6285 * Get access to a cache object by the `cacheId` used when it was created.
6287 * @param {string} cacheId Name or id of a cache to access.
6288 * @returns {object} Cache object identified by the cacheId or undefined if no such cache.
6290 cacheFactory.get = function(cacheId) {
6291 return caches[cacheId];
6295 return cacheFactory;
6301 * @name $templateCache
6304 * The first time a template is used, it is loaded in the template cache for quick retrieval. You
6305 * can load templates directly into the cache in a `script` tag, or by consuming the
6306 * `$templateCache` service directly.
6308 * Adding via the `script` tag:
6311 * <script type="text/ng-template" id="templateId.html">
6312 * <p>This is the content of the template</p>
6316 * **Note:** the `script` tag containing the template does not need to be included in the `head` of
6317 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE,
6318 * element with ng-app attribute), otherwise the template will be ignored.
6320 * Adding via the `$templateCache` service:
6323 * var myApp = angular.module('myApp', []);
6324 * myApp.run(function($templateCache) {
6325 * $templateCache.put('templateId.html', 'This is the content of the template');
6329 * To retrieve the template later, simply use it in your HTML:
6331 * <div ng-include=" 'templateId.html' "></div>
6334 * or get it via Javascript:
6336 * $templateCache.get('templateId.html')
6339 * See {@link ng.$cacheFactory $cacheFactory}.
6342 function $TemplateCacheProvider() {
6343 this.$get = ['$cacheFactory', function($cacheFactory) {
6344 return $cacheFactory('templates');
6348 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
6349 * Any commits to this file should be reviewed with security in mind. *
6350 * Changes to this file can potentially create security vulnerabilities. *
6351 * An approval from 2 Core members with history of modifying *
6352 * this file is required. *
6354 * Does the change somehow allow for arbitrary javascript to be executed? *
6355 * Or allows for someone to change the prototype of built-in objects? *
6356 * Or gives undesired access to variables likes document or window? *
6357 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
6359 /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
6361 * DOM-related variables:
6363 * - "node" - DOM Node
6364 * - "element" - DOM Element or Node
6365 * - "$node" or "$element" - jqLite-wrapped node or element
6368 * Compiler related stuff:
6370 * - "linkFn" - linking fn of a single directive
6371 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node
6372 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node
6373 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList)
6383 * Compiles an HTML string or DOM into a template and produces a template function, which
6384 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together.
6386 * The compilation is a process of walking the DOM tree and matching DOM elements to
6387 * {@link ng.$compileProvider#directive directives}.
6389 * <div class="alert alert-warning">
6390 * **Note:** This document is an in-depth reference of all directive options.
6391 * For a gentle introduction to directives with examples of common use cases,
6392 * see the {@link guide/directive directive guide}.
6395 * ## Comprehensive Directive API
6397 * There are many different options for a directive.
6399 * The difference resides in the return value of the factory function.
6400 * You can either return a "Directive Definition Object" (see below) that defines the directive properties,
6401 * or just the `postLink` function (all other properties will have the default values).
6403 * <div class="alert alert-success">
6404 * **Best Practice:** It's recommended to use the "directive definition object" form.
6407 * Here's an example directive declared with a Directive Definition Object:
6410 * var myModule = angular.module(...);
6412 * myModule.directive('directiveName', function factory(injectables) {
6413 * var directiveDefinitionObject = {
6415 * template: '<div></div>', // or // function(tElement, tAttrs) { ... },
6417 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... },
6418 * transclude: false,
6420 * templateNamespace: 'html',
6422 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
6423 * controllerAs: 'stringIdentifier',
6424 * bindToController: false,
6425 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
6426 * compile: function compile(tElement, tAttrs, transclude) {
6428 * pre: function preLink(scope, iElement, iAttrs, controller) { ... },
6429 * post: function postLink(scope, iElement, iAttrs, controller) { ... }
6432 * // return function postLink( ... ) { ... }
6436 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... },
6437 * // post: function postLink(scope, iElement, iAttrs, controller) { ... }
6440 * // link: function postLink( ... ) { ... }
6442 * return directiveDefinitionObject;
6446 * <div class="alert alert-warning">
6447 * **Note:** Any unspecified options will use the default value. You can see the default values below.
6450 * Therefore the above can be simplified as:
6453 * var myModule = angular.module(...);
6455 * myModule.directive('directiveName', function factory(injectables) {
6456 * var directiveDefinitionObject = {
6457 * link: function postLink(scope, iElement, iAttrs) { ... }
6459 * return directiveDefinitionObject;
6461 * // return function postLink(scope, iElement, iAttrs) { ... }
6467 * ### Directive Definition Object
6469 * The directive definition object provides instructions to the {@link ng.$compile
6470 * compiler}. The attributes are:
6472 * #### `multiElement`
6473 * When this property is set to true, the HTML compiler will collect DOM nodes between
6474 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
6475 * together as the directive elements. It is recommended that this feature be used on directives
6476 * which are not strictly behavioural (such as {@link ngClick}), and which
6477 * do not manipulate or replace child nodes (such as {@link ngInclude}).
6480 * When there are multiple directives defined on a single DOM element, sometimes it
6481 * is necessary to specify the order in which the directives are applied. The `priority` is used
6482 * to sort the directives before their `compile` functions get called. Priority is defined as a
6483 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions
6484 * are also run in priority order, but post-link functions are run in reverse order. The order
6485 * of directives with the same priority is undefined. The default priority is `0`.
6488 * If set to true then the current `priority` will be the last set of directives
6489 * which will execute (any directives at the current priority will still execute
6490 * as the order of execution on same `priority` is undefined). Note that expressions
6491 * and other directives used in the directive's template will also be excluded from execution.
6494 * The scope property can be `true`, an object or a falsy value:
6496 * * **falsy:** No scope will be created for the directive. The directive will use its parent's scope.
6498 * * **`true`:** A new child scope that prototypically inherits from its parent will be created for
6499 * the directive's element. If multiple directives on the same element request a new scope,
6500 * only one new scope is created. The new scope rule does not apply for the root of the template
6501 * since the root of the template always gets a new scope.
6503 * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's element. The
6504 * 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent
6505 * scope. This is useful when creating reusable components, which should not accidentally read or modify
6506 * data in the parent scope.
6508 * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the
6509 * directive's element. These local properties are useful for aliasing values for templates. The keys in
6510 * the object hash map to the name of the property on the isolate scope; the values define how the property
6511 * is bound to the parent scope, via matching attributes on the directive's element:
6513 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
6514 * always a string since DOM attributes are strings. If no `attr` name is specified then the
6515 * attribute name is assumed to be the same as the local name.
6516 * Given `<widget my-attr="hello {{name}}">` and widget definition
6517 * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect
6518 * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the
6519 * `localName` property on the widget scope. The `name` is read from the parent scope (not
6522 * * `=` or `=attr` - set up bi-directional binding between a local scope property and the
6523 * parent scope property of name defined via the value of the `attr` attribute. If no `attr`
6524 * name is specified then the attribute name is assumed to be the same as the local name.
6525 * Given `<widget my-attr="parentModel">` and widget definition of
6526 * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the
6527 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
6528 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent
6529 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You
6530 * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If
6531 * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use
6532 * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional).
6534 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope.
6535 * If no `attr` name is specified then the attribute name is assumed to be the same as the
6536 * local name. Given `<widget my-attr="count = count + value">` and widget definition of
6537 * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to
6538 * a function wrapper for the `count = count + value` expression. Often it's desirable to
6539 * pass data from the isolated scope via an expression to the parent scope, this can be
6540 * done by passing a map of local variable names and values into the expression wrapper fn.
6541 * For example, if the expression is `increment(amount)` then we can specify the amount value
6542 * by calling the `localFn` as `localFn({amount: 22})`.
6544 * In general it's possible to apply more than one directive to one element, but there might be limitations
6545 * depending on the type of scope required by the directives. The following points will help explain these limitations.
6546 * For simplicity only two directives are taken into account, but it is also applicable for several directives:
6548 * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope
6549 * * **child scope** + **no scope** => Both directives will share one single child scope
6550 * * **child scope** + **child scope** => Both directives will share one single child scope
6551 * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use
6552 * its parent's scope
6553 * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot
6554 * be applied to the same element.
6555 * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives
6556 * cannot be applied to the same element.
6559 * #### `bindToController`
6560 * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will
6561 * allow a component to have its properties bound to the controller, rather than to scope. When the controller
6562 * is instantiated, the initial values of the isolate scope bindings are already available.
6565 * Controller constructor function. The controller is instantiated before the
6566 * pre-linking phase and can be accessed by other directives (see
6567 * `require` attribute). This allows the directives to communicate with each other and augment
6568 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals:
6570 * * `$scope` - Current scope associated with the element
6571 * * `$element` - Current element
6572 * * `$attrs` - Current attributes object for the element
6573 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope:
6574 * `function([scope], cloneLinkingFn, futureParentElement)`.
6575 * * `scope`: optional argument to override the scope.
6576 * * `cloneLinkingFn`: optional argument to create clones of the original transcluded content.
6577 * * `futureParentElement`:
6578 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements.
6579 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`.
6580 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements)
6581 * and when the `cloneLinkinFn` is passed,
6582 * as those elements need to created and cloned in a special way when they are defined outside their
6583 * usual containers (e.g. like `<svg>`).
6584 * * See also the `directive.templateNamespace` property.
6588 * Require another directive and inject its controller as the fourth argument to the linking function. The
6589 * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the
6590 * injected argument will be an array in corresponding order. If no such directive can be
6591 * found, or if the directive does not have a controller, then an error is raised (unless no link function
6592 * is specified, in which case error checking is skipped). The name can be prefixed with:
6594 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
6595 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
6596 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found.
6597 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found.
6598 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass
6599 * `null` to the `link` fn if not found.
6600 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass
6601 * `null` to the `link` fn if not found.
6604 * #### `controllerAs`
6605 * Identifier name for a reference to the controller in the directive's scope.
6606 * This allows the controller to be referenced from the directive template. This is especially
6607 * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible
6608 * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the
6609 * `controllerAs` reference might overwrite a property that already exists on the parent scope.
6613 * String of subset of `EACM` which restricts the directive to a specific directive
6614 * declaration style. If omitted, the defaults (elements and attributes) are used.
6616 * * `E` - Element name (default): `<my-directive></my-directive>`
6617 * * `A` - Attribute (default): `<div my-directive="exp"></div>`
6618 * * `C` - Class: `<div class="my-directive: exp;"></div>`
6619 * * `M` - Comment: `<!-- directive: my-directive exp -->`
6622 * #### `templateNamespace`
6623 * String representing the document type used by the markup in the template.
6624 * AngularJS needs this information as those elements need to be created and cloned
6625 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`.
6627 * * `html` - All root nodes in the template are HTML. Root nodes may also be
6628 * top-level elements such as `<svg>` or `<math>`.
6629 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`).
6630 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`).
6632 * If no `templateNamespace` is specified, then the namespace is considered to be `html`.
6635 * HTML markup that may:
6636 * * Replace the contents of the directive's element (default).
6637 * * Replace the directive's element itself (if `replace` is true - DEPRECATED).
6638 * * Wrap the contents of the directive's element (if `transclude` is true).
6642 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`.
6643 * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile`
6644 * function api below) and returns a string value.
6647 * #### `templateUrl`
6648 * This is similar to `template` but the template is loaded from the specified URL, asynchronously.
6650 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element
6651 * for later when the template has been resolved. In the meantime it will continue to compile and link
6652 * sibling and parent elements as though this element had not contained any directives.
6654 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this
6655 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the
6656 * case when only one deeply nested directive has `templateUrl`.
6658 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache}
6660 * You can specify `templateUrl` as a string representing the URL or as a function which takes two
6661 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns
6662 * a string value representing the url. In either case, the template URL is passed through {@link
6663 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}.
6666 * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0)
6667 * specify what the template should replace. Defaults to `false`.
6669 * * `true` - the template will replace the directive's element.
6670 * * `false` - the template will replace the contents of the directive's element.
6672 * The replacement process migrates all of the attributes / classes from the old element to the new
6673 * one. See the {@link guide/directive#template-expanding-directive
6674 * Directives Guide} for an example.
6676 * There are very few scenarios where element replacement is required for the application function,
6677 * the main one being reusable custom components that are used within SVG contexts
6678 * (because SVG doesn't work with custom elements in the DOM tree).
6681 * Extract the contents of the element where the directive appears and make it available to the directive.
6682 * The contents are compiled and provided to the directive as a **transclusion function**. See the
6683 * {@link $compile#transclusion Transclusion} section below.
6685 * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the
6686 * directive's element or the entire element:
6688 * * `true` - transclude the content (i.e. the child nodes) of the directive's element.
6689 * * `'element'` - transclude the whole of the directive's element including any directives on this
6690 * element that defined at a lower priority than this directive. When used, the `template`
6691 * property is ignored.
6697 * function compile(tElement, tAttrs, transclude) { ... }
6700 * The compile function deals with transforming the template DOM. Since most directives do not do
6701 * template transformation, it is not used often. The compile function takes the following arguments:
6703 * * `tElement` - template element - The element where the directive has been declared. It is
6704 * safe to do template transformation on the element and child elements only.
6706 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared
6707 * between all directive compile functions.
6709 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)`
6711 * <div class="alert alert-warning">
6712 * **Note:** The template instance and the link instance may be different objects if the template has
6713 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that
6714 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
6715 * should be done in a linking function rather than in a compile function.
6718 * <div class="alert alert-warning">
6719 * **Note:** The compile function cannot handle directives that recursively use themselves in their
6720 * own templates or compile functions. Compiling these directives results in an infinite loop and a
6721 * stack overflow errors.
6723 * This can be avoided by manually using $compile in the postLink function to imperatively compile
6724 * a directive's template instead of relying on automatic template compilation via `template` or
6725 * `templateUrl` declaration or manual compilation inside the compile function.
6728 * <div class="alert alert-danger">
6729 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it
6730 * e.g. does not know about the right outer scope. Please use the transclude function that is passed
6731 * to the link function instead.
6734 * A compile function can have a return value which can be either a function or an object.
6736 * * returning a (post-link) function - is equivalent to registering the linking function via the
6737 * `link` property of the config object when the compile function is empty.
6739 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to
6740 * control when a linking function should be called during the linking phase. See info about
6741 * pre-linking and post-linking functions below.
6745 * This property is used only if the `compile` property is not defined.
6748 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }
6751 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is
6752 * executed after the template has been cloned. This is where most of the directive logic will be
6755 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the
6756 * directive for registering {@link ng.$rootScope.Scope#$watch watches}.
6758 * * `iElement` - instance element - The element where the directive is to be used. It is safe to
6759 * manipulate the children of the element only in `postLink` function since the children have
6760 * already been linked.
6762 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
6763 * between all directive linking functions.
6765 * * `controller` - the directive's required controller instance(s) - Instances are shared
6766 * among all directives, which allows the directives to use the controllers as a communication
6767 * channel. The exact value depends on the directive's `require` property:
6768 * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one
6769 * * `string`: the controller instance
6770 * * `array`: array of controller instances
6772 * If a required controller cannot be found, and it is optional, the instance is `null`,
6773 * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown.
6775 * Note that you can also require the directive's own controller - it will be made available like
6776 * any other controller.
6778 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
6779 * This is the same as the `$transclude`
6780 * parameter of directive controllers, see there for details.
6781 * `function([scope], cloneLinkingFn, futureParentElement)`.
6783 * #### Pre-linking function
6785 * Executed before the child elements are linked. Not safe to do DOM transformation since the
6786 * compiler linking function will fail to locate the correct elements for linking.
6788 * #### Post-linking function
6790 * Executed after the child elements are linked.
6792 * Note that child elements that contain `templateUrl` directives will not have been compiled
6793 * and linked since they are waiting for their template to load asynchronously and their own
6794 * compilation and linking has been suspended until that occurs.
6796 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting
6797 * for their async templates to be resolved.
6802 * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and
6803 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS
6804 * scope from where they were taken.
6806 * Transclusion is used (often with {@link ngTransclude}) to insert the
6807 * original contents of a directive's element into a specified place in the template of the directive.
6808 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded
6809 * content has access to the properties on the scope from which it was taken, even if the directive
6810 * has isolated scope.
6811 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}.
6813 * This makes it possible for the widget to have private state for its template, while the transcluded
6814 * content has access to its originating scope.
6816 * <div class="alert alert-warning">
6817 * **Note:** When testing an element transclude directive you must not place the directive at the root of the
6818 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
6819 * Testing Transclusion Directives}.
6822 * #### Transclusion Functions
6824 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion
6825 * function** to the directive's `link` function and `controller`. This transclusion function is a special
6826 * **linking function** that will return the compiled contents linked to a new transclusion scope.
6828 * <div class="alert alert-info">
6829 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since
6830 * ngTransclude will deal with it for us.
6833 * If you want to manually control the insertion and removal of the transcluded content in your directive
6834 * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery
6835 * object that contains the compiled DOM, which is linked to the correct transclusion scope.
6837 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts
6838 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded
6839 * content and the `scope` is the newly created transclusion scope, to which the clone is bound.
6841 * <div class="alert alert-info">
6842 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function
6843 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope.
6846 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone
6847 * attach function**:
6850 * var transcludedContent, transclusionScope;
6852 * $transclude(function(clone, scope) {
6853 * element.append(clone);
6854 * transcludedContent = clone;
6855 * transclusionScope = scope;
6859 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the
6860 * associated transclusion scope:
6863 * transcludedContent.remove();
6864 * transclusionScope.$destroy();
6867 * <div class="alert alert-info">
6868 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive
6869 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it),
6870 * then you are also responsible for calling `$destroy` on the transclusion scope.
6873 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat}
6874 * automatically destroy their transluded clones as necessary so you do not need to worry about this if
6875 * you are simply using {@link ngTransclude} to inject the transclusion into your directive.
6878 * #### Transclusion Scopes
6880 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion
6881 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed
6882 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it
6885 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look
6891 * <div transclusion>
6897 * The `$parent` scope hierarchy will look like this:
6905 * but the scopes will inherit prototypically from different scopes to their `$parent`.
6916 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
6917 * `link()` or `compile()` functions. It has a variety of uses.
6919 * accessing *Normalized attribute names:*
6920 * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'.
6921 * the attributes object allows for normalized access to
6924 * * *Directive inter-communication:* All directives share the same instance of the attributes
6925 * object which allows the directives to use the attributes object as inter directive
6928 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object
6929 * allowing other directives to read the interpolated value.
6931 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes
6932 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also
6933 * the only way to easily get the actual value because during the linking phase the interpolation
6934 * hasn't been evaluated yet and so the value is at this time set to `undefined`.
6937 * function linkingFn(scope, elm, attrs, ctrl) {
6938 * // get the attribute value
6939 * console.log(attrs.ngModel);
6941 * // change the attribute
6942 * attrs.$set('ngModel', 'new value');
6944 * // observe changes to interpolated attribute
6945 * attrs.$observe('ngModel', function(value) {
6946 * console.log('ngModel has changed value to ' + value);
6953 * <div class="alert alert-warning">
6954 * **Note**: Typically directives are registered with `module.directive`. The example below is
6955 * to illustrate how `$compile` works.
6958 <example module="compileExample">
6959 <file name="index.html">
6961 angular.module('compileExample', [], function($compileProvider) {
6962 // configure new 'compile' directive by passing a directive
6963 // factory function. The factory function injects the '$compile'
6964 $compileProvider.directive('compile', function($compile) {
6965 // directive factory creates a link function
6966 return function(scope, element, attrs) {
6969 // watch the 'compile' expression for changes
6970 return scope.$eval(attrs.compile);
6973 // when the 'compile' expression changes
6974 // assign it into the current DOM
6975 element.html(value);
6977 // compile the new DOM and link it to the current
6979 // NOTE: we only compile .childNodes so that
6980 // we don't get into infinite loop compiling ourselves
6981 $compile(element.contents())(scope);
6987 .controller('GreeterController', ['$scope', function($scope) {
6988 $scope.name = 'Angular';
6989 $scope.html = 'Hello {{name}}';
6992 <div ng-controller="GreeterController">
6993 <input ng-model="name"> <br/>
6994 <textarea ng-model="html"></textarea> <br/>
6995 <div compile="html"></div>
6998 <file name="protractor.js" type="protractor">
6999 it('should auto compile', function() {
7000 var textarea = $('textarea');
7001 var output = $('div[compile]');
7002 // The initial state reads 'Hello Angular'.
7003 expect(output.getText()).toBe('Hello Angular');
7005 textarea.sendKeys('{{name}}!');
7006 expect(output.getText()).toBe('Angular!');
7013 * @param {string|DOMElement} element Element or HTML string to compile into a template function.
7014 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
7016 * <div class="alert alert-danger">
7017 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
7018 * e.g. will not use the right outer scope. Please pass the transclude function as a
7019 * `parentBoundTranscludeFn` to the link function instead.
7022 * @param {number} maxPriority only apply directives lower than given priority (Only effects the
7023 * root element(s), not their children)
7024 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template
7025 * (a DOM element/tree) to a scope. Where:
7027 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
7028 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
7029 * `template` and call the `cloneAttachFn` function allowing the caller to attach the
7030 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
7031 * called as: <br/> `cloneAttachFn(clonedElement, scope)` where:
7033 * * `clonedElement` - is a clone of the original `element` passed into the compiler.
7034 * * `scope` - is the current scope with which the linking function is working with.
7036 * * `options` - An optional object hash with linking options. If `options` is provided, then the following
7037 * keys may be used to control linking behavior:
7039 * * `parentBoundTranscludeFn` - the transclude function made available to
7040 * directives; if given, it will be passed through to the link functions of
7041 * directives found in `element` during compilation.
7042 * * `transcludeControllers` - an object hash with keys that map controller names
7043 * to controller instances; if given, it will make the controllers
7044 * available to directives.
7045 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add
7046 * the cloned elements; only needed for transcludes that are allowed to contain non html
7047 * elements (e.g. SVG elements). See also the directive.controller property.
7049 * Calling the linking function returns the element of the template. It is either the original
7050 * element passed in, or the clone of the element if the `cloneAttachFn` is provided.
7052 * After linking the view is not updated until after a call to $digest which typically is done by
7053 * Angular automatically.
7055 * If you need access to the bound view, there are two ways to do it:
7057 * - If you are not asking the linking function to clone the template, create the DOM element(s)
7058 * before you send them to the compiler and keep this reference around.
7060 * var element = $compile('<p>{{total}}</p>')(scope);
7063 * - if on the other hand, you need the element to be cloned, the view reference from the original
7064 * example would not point to the clone, but rather to the original template that was cloned. In
7065 * this case, you can access the clone via the cloneAttachFn:
7067 * var templateElement = angular.element('<p>{{total}}</p>'),
7070 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) {
7071 * //attach the clone to DOM document at the right place
7074 * //now we have reference to the cloned DOM via `clonedElement`
7078 * For information on how the compiler works, see the
7079 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
7082 var $compileMinErr = minErr('$compile');
7086 * @name $compileProvider
7090 $CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider'];
7091 function $CompileProvider($provide, $$sanitizeUriProvider) {
7092 var hasDirectives = {},
7093 Suffix = 'Directive',
7094 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/,
7095 CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/,
7096 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
7097 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
7099 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
7100 // The assumption is that future DOM event attribute names will begin with
7101 // 'on' and be composed of only English letters.
7102 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
7104 function parseIsolateBindings(scope, directiveName, isController) {
7105 var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/;
7109 forEach(scope, function(definition, scopeName) {
7110 var match = definition.match(LOCAL_REGEXP);
7113 throw $compileMinErr('iscp',
7114 "Invalid {3} for directive '{0}'." +
7115 " Definition: {... {1}: '{2}' ...}",
7116 directiveName, scopeName, definition,
7117 (isController ? "controller bindings definition" :
7118 "isolate scope definition"));
7121 bindings[scopeName] = {
7123 collection: match[2] === '*',
7124 optional: match[3] === '?',
7125 attrName: match[4] || scopeName
7132 function parseDirectiveBindings(directive, directiveName) {
7135 bindToController: null
7137 if (isObject(directive.scope)) {
7138 if (directive.bindToController === true) {
7139 bindings.bindToController = parseIsolateBindings(directive.scope,
7140 directiveName, true);
7141 bindings.isolateScope = {};
7143 bindings.isolateScope = parseIsolateBindings(directive.scope,
7144 directiveName, false);
7147 if (isObject(directive.bindToController)) {
7148 bindings.bindToController =
7149 parseIsolateBindings(directive.bindToController, directiveName, true);
7151 if (isObject(bindings.bindToController)) {
7152 var controller = directive.controller;
7153 var controllerAs = directive.controllerAs;
7155 // There is no controller, there may or may not be a controllerAs property
7156 throw $compileMinErr('noctrl',
7157 "Cannot bind to controller without directive '{0}'s controller.",
7159 } else if (!identifierForController(controller, controllerAs)) {
7160 // There is a controller, but no identifier or controllerAs property
7161 throw $compileMinErr('noident',
7162 "Cannot bind to controller without identifier for directive '{0}'.",
7169 function assertValidDirectiveName(name) {
7170 var letter = name.charAt(0);
7171 if (!letter || letter !== lowercase(letter)) {
7172 throw $compileMinErr('baddir', "Directive name '{0}' is invalid. The first character must be a lowercase letter", name);
7174 if (name !== name.trim()) {
7175 throw $compileMinErr('baddir',
7176 "Directive name '{0}' is invalid. The name should not contain leading or trailing whitespaces",
7183 * @name $compileProvider#directive
7187 * Register a new directive with the compiler.
7189 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which
7190 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the
7191 * names and the values are the factories.
7192 * @param {Function|Array} directiveFactory An injectable directive factory function. See
7193 * {@link guide/directive} for more info.
7194 * @returns {ng.$compileProvider} Self for chaining.
7196 this.directive = function registerDirective(name, directiveFactory) {
7197 assertNotHasOwnProperty(name, 'directive');
7198 if (isString(name)) {
7199 assertValidDirectiveName(name);
7200 assertArg(directiveFactory, 'directiveFactory');
7201 if (!hasDirectives.hasOwnProperty(name)) {
7202 hasDirectives[name] = [];
7203 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
7204 function($injector, $exceptionHandler) {
7205 var directives = [];
7206 forEach(hasDirectives[name], function(directiveFactory, index) {
7208 var directive = $injector.invoke(directiveFactory);
7209 if (isFunction(directive)) {
7210 directive = { compile: valueFn(directive) };
7211 } else if (!directive.compile && directive.link) {
7212 directive.compile = valueFn(directive.link);
7214 directive.priority = directive.priority || 0;
7215 directive.index = index;
7216 directive.name = directive.name || name;
7217 directive.require = directive.require || (directive.controller && directive.name);
7218 directive.restrict = directive.restrict || 'EA';
7219 var bindings = directive.$$bindings =
7220 parseDirectiveBindings(directive, directive.name);
7221 if (isObject(bindings.isolateScope)) {
7222 directive.$$isolateBindings = bindings.isolateScope;
7224 directive.$$moduleName = directiveFactory.$$moduleName;
7225 directives.push(directive);
7227 $exceptionHandler(e);
7233 hasDirectives[name].push(directiveFactory);
7235 forEach(name, reverseParams(registerDirective));
7243 * @name $compileProvider#aHrefSanitizationWhitelist
7247 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
7248 * urls during a[href] sanitization.
7250 * The sanitization is a security measure aimed at preventing XSS attacks via html links.
7252 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
7253 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
7254 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
7255 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
7257 * @param {RegExp=} regexp New regexp to whitelist urls with.
7258 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
7259 * chaining otherwise.
7261 this.aHrefSanitizationWhitelist = function(regexp) {
7262 if (isDefined(regexp)) {
7263 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp);
7266 return $$sanitizeUriProvider.aHrefSanitizationWhitelist();
7273 * @name $compileProvider#imgSrcSanitizationWhitelist
7277 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
7278 * urls during img[src] sanitization.
7280 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
7282 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
7283 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
7284 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
7285 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
7287 * @param {RegExp=} regexp New regexp to whitelist urls with.
7288 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
7289 * chaining otherwise.
7291 this.imgSrcSanitizationWhitelist = function(regexp) {
7292 if (isDefined(regexp)) {
7293 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp);
7296 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist();
7302 * @name $compileProvider#debugInfoEnabled
7304 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the
7305 * current debugInfoEnabled state
7306 * @returns {*} current value if used as getter or itself (chaining) if used as setter
7311 * Call this method to enable/disable various debug runtime information in the compiler such as adding
7312 * binding information and a reference to the current scope on to DOM elements.
7313 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope
7314 * * `ng-binding` CSS class
7315 * * `$binding` data property containing an array of the binding expressions
7317 * You may want to disable this in production for a significant performance boost. See
7318 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more.
7320 * The default value is true.
7322 var debugInfoEnabled = true;
7323 this.debugInfoEnabled = function(enabled) {
7324 if (isDefined(enabled)) {
7325 debugInfoEnabled = enabled;
7328 return debugInfoEnabled;
7332 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
7333 '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri',
7334 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse,
7335 $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) {
7337 var Attributes = function(element, attributesToCopy) {
7338 if (attributesToCopy) {
7339 var keys = Object.keys(attributesToCopy);
7342 for (i = 0, l = keys.length; i < l; i++) {
7344 this[key] = attributesToCopy[key];
7350 this.$$element = element;
7353 Attributes.prototype = {
7356 * @name $compile.directive.Attributes#$normalize
7360 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
7361 * `data-`) to its normalized, camelCase form.
7363 * Also there is special case for Moz prefix starting with upper case letter.
7365 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
7367 * @param {string} name Name to normalize
7369 $normalize: directiveNormalize,
7374 * @name $compile.directive.Attributes#$addClass
7378 * Adds the CSS class value specified by the classVal parameter to the element. If animations
7379 * are enabled then an animation will be triggered for the class addition.
7381 * @param {string} classVal The className value that will be added to the element
7383 $addClass: function(classVal) {
7384 if (classVal && classVal.length > 0) {
7385 $animate.addClass(this.$$element, classVal);
7391 * @name $compile.directive.Attributes#$removeClass
7395 * Removes the CSS class value specified by the classVal parameter from the element. If
7396 * animations are enabled then an animation will be triggered for the class removal.
7398 * @param {string} classVal The className value that will be removed from the element
7400 $removeClass: function(classVal) {
7401 if (classVal && classVal.length > 0) {
7402 $animate.removeClass(this.$$element, classVal);
7408 * @name $compile.directive.Attributes#$updateClass
7412 * Adds and removes the appropriate CSS class values to the element based on the difference
7413 * between the new and old CSS class values (specified as newClasses and oldClasses).
7415 * @param {string} newClasses The current CSS className value
7416 * @param {string} oldClasses The former CSS className value
7418 $updateClass: function(newClasses, oldClasses) {
7419 var toAdd = tokenDifference(newClasses, oldClasses);
7420 if (toAdd && toAdd.length) {
7421 $animate.addClass(this.$$element, toAdd);
7424 var toRemove = tokenDifference(oldClasses, newClasses);
7425 if (toRemove && toRemove.length) {
7426 $animate.removeClass(this.$$element, toRemove);
7431 * Set a normalized attribute on the element in a way such that all directives
7432 * can share the attribute. This function properly handles boolean attributes.
7433 * @param {string} key Normalized key. (ie ngAttribute)
7434 * @param {string|boolean} value The value to set. If `null` attribute will be deleted.
7435 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
7437 * @param {string=} attrName Optional none normalized name. Defaults to key.
7439 $set: function(key, value, writeAttr, attrName) {
7440 // TODO: decide whether or not to throw an error if "class"
7441 //is set through this function since it may cause $updateClass to
7444 var node = this.$$element[0],
7445 booleanKey = getBooleanAttrName(node, key),
7446 aliasedKey = getAliasedAttrName(key),
7451 this.$$element.prop(key, value);
7452 attrName = booleanKey;
7453 } else if (aliasedKey) {
7454 this[aliasedKey] = value;
7455 observer = aliasedKey;
7460 // translate normalized key to actual key
7462 this.$attr[key] = attrName;
7464 attrName = this.$attr[key];
7466 this.$attr[key] = attrName = snake_case(key, '-');
7470 nodeName = nodeName_(this.$$element);
7472 if ((nodeName === 'a' && key === 'href') ||
7473 (nodeName === 'img' && key === 'src')) {
7474 // sanitize a[href] and img[src] values
7475 this[key] = value = $$sanitizeUri(value, key === 'src');
7476 } else if (nodeName === 'img' && key === 'srcset') {
7477 // sanitize img[srcset] values
7480 // first check if there are spaces because it's not the same pattern
7481 var trimmedSrcset = trim(value);
7482 // ( 999x ,| 999w ,| ,|, )
7483 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
7484 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
7486 // split srcset into tuple of uri and descriptor except for the last item
7487 var rawUris = trimmedSrcset.split(pattern);
7490 var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
7491 for (var i = 0; i < nbrUrisWith2parts; i++) {
7492 var innerIdx = i * 2;
7494 result += $$sanitizeUri(trim(rawUris[innerIdx]), true);
7495 // add the descriptor
7496 result += (" " + trim(rawUris[innerIdx + 1]));
7499 // split the last item into uri and descriptor
7500 var lastTuple = trim(rawUris[i * 2]).split(/\s/);
7502 // sanitize the last uri
7503 result += $$sanitizeUri(trim(lastTuple[0]), true);
7505 // and add the last descriptor if any
7506 if (lastTuple.length === 2) {
7507 result += (" " + trim(lastTuple[1]));
7509 this[key] = value = result;
7512 if (writeAttr !== false) {
7513 if (value === null || isUndefined(value)) {
7514 this.$$element.removeAttr(attrName);
7516 this.$$element.attr(attrName, value);
7521 var $$observers = this.$$observers;
7522 $$observers && forEach($$observers[observer], function(fn) {
7526 $exceptionHandler(e);
7534 * @name $compile.directive.Attributes#$observe
7538 * Observes an interpolated attribute.
7540 * The observer function will be invoked once during the next `$digest` following
7541 * compilation. The observer is then invoked whenever the interpolated value
7544 * @param {string} key Normalized key. (ie ngAttribute) .
7545 * @param {function(interpolatedValue)} fn Function that will be called whenever
7546 the interpolated value of the attribute changes.
7547 * See the {@link guide/directive#text-and-attribute-bindings Directives} guide for more info.
7548 * @returns {function()} Returns a deregistration function for this observer.
7550 $observe: function(key, fn) {
7552 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())),
7553 listeners = ($$observers[key] || ($$observers[key] = []));
7556 $rootScope.$evalAsync(function() {
7557 if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) {
7558 // no one registered attribute interpolation function, so lets call it manually
7564 arrayRemove(listeners, fn);
7570 function safeAddClass($element, className) {
7572 $element.addClass(className);
7574 // ignore, since it means that we are trying to set class on
7575 // SVG element, where class name is read-only.
7580 var startSymbol = $interpolate.startSymbol(),
7581 endSymbol = $interpolate.endSymbol(),
7582 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
7584 : function denormalizeTemplate(template) {
7585 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
7587 NG_ATTR_BINDING = /^ngAttr[A-Z]/;
7588 var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/;
7590 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) {
7591 var bindings = $element.data('$binding') || [];
7593 if (isArray(binding)) {
7594 bindings = bindings.concat(binding);
7596 bindings.push(binding);
7599 $element.data('$binding', bindings);
7602 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) {
7603 safeAddClass($element, 'ng-binding');
7606 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) {
7607 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope';
7608 $element.data(dataName, scope);
7611 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) {
7612 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope');
7617 //================================
7619 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective,
7620 previousCompileContext) {
7621 if (!($compileNodes instanceof jqLite)) {
7622 // jquery always rewraps, whereas we need to preserve the original selector so that we can
7624 $compileNodes = jqLite($compileNodes);
7626 // We can not compile top level text elements since text nodes can be merged and we will
7627 // not be able to attach scope data to them, so we will wrap them in <span>
7628 forEach($compileNodes, function(node, index) {
7629 if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) {
7630 $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
7633 var compositeLinkFn =
7634 compileNodes($compileNodes, transcludeFn, $compileNodes,
7635 maxPriority, ignoreDirective, previousCompileContext);
7636 compile.$$addScopeClass($compileNodes);
7637 var namespace = null;
7638 return function publicLinkFn(scope, cloneConnectFn, options) {
7639 assertArg(scope, 'scope');
7641 if (previousCompileContext && previousCompileContext.needsNewScope) {
7642 // A parent directive did a replace and a directive on this element asked
7643 // for transclusion, which caused us to lose a layer of element on which
7644 // we could hold the new transclusion scope, so we will create it manually
7646 scope = scope.$parent.$new();
7649 options = options || {};
7650 var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
7651 transcludeControllers = options.transcludeControllers,
7652 futureParentElement = options.futureParentElement;
7654 // When `parentBoundTranscludeFn` is passed, it is a
7655 // `controllersBoundTransclude` function (it was previously passed
7656 // as `transclude` to directive.link) so we must unwrap it to get
7657 // its `boundTranscludeFn`
7658 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) {
7659 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;
7663 namespace = detectNamespaceForChildElements(futureParentElement);
7666 if (namespace !== 'html') {
7667 // When using a directive with replace:true and templateUrl the $compileNodes
7668 // (or a child element inside of them)
7669 // might change, so we need to recreate the namespace adapted compileNodes
7670 // for call to the link function.
7671 // Note: This will already clone the nodes...
7673 wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html())
7675 } else if (cloneConnectFn) {
7676 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
7677 // and sometimes changes the structure of the DOM.
7678 $linkNode = JQLitePrototype.clone.call($compileNodes);
7680 $linkNode = $compileNodes;
7683 if (transcludeControllers) {
7684 for (var controllerName in transcludeControllers) {
7685 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance);
7689 compile.$$addScopeInfo($linkNode, scope);
7691 if (cloneConnectFn) cloneConnectFn($linkNode, scope);
7692 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);
7697 function detectNamespaceForChildElements(parentElement) {
7698 // TODO: Make this detect MathML as well...
7699 var node = parentElement && parentElement[0];
7703 return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html';
7708 * Compile function matches each node in nodeList against the directives. Once all directives
7709 * for a particular node are collected their compile functions are executed. The compile
7710 * functions return values - the linking functions - are combined into a composite linking
7711 * function, which is the a linking function for the node.
7713 * @param {NodeList} nodeList an array of nodes or NodeList to compile
7714 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
7715 * scope argument is auto-generated to the new child of the transcluded parent scope.
7716 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then
7717 * the rootElement must be set the jqLite collection of the compile root. This is
7718 * needed so that the jqLite collection items can be replaced with widgets.
7719 * @param {number=} maxPriority Max directive priority.
7720 * @returns {Function} A composite linking function of all of the matched directives or null.
7722 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective,
7723 previousCompileContext) {
7725 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound;
7727 for (var i = 0; i < nodeList.length; i++) {
7728 attrs = new Attributes();
7730 // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
7731 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined,
7734 nodeLinkFn = (directives.length)
7735 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement,
7736 null, [], [], previousCompileContext)
7739 if (nodeLinkFn && nodeLinkFn.scope) {
7740 compile.$$addScopeClass(attrs.$$element);
7743 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal ||
7744 !(childNodes = nodeList[i].childNodes) ||
7747 : compileNodes(childNodes,
7749 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement)
7750 && nodeLinkFn.transclude) : transcludeFn);
7752 if (nodeLinkFn || childLinkFn) {
7753 linkFns.push(i, nodeLinkFn, childLinkFn);
7755 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn;
7758 //use the previous context only for the first element in the virtual group
7759 previousCompileContext = null;
7762 // return a linking function if we have found anything, null otherwise
7763 return linkFnFound ? compositeLinkFn : null;
7765 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) {
7766 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn;
7770 if (nodeLinkFnFound) {
7771 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our
7772 // offsets don't get screwed up
7773 var nodeListLength = nodeList.length;
7774 stableNodeList = new Array(nodeListLength);
7776 // create a sparse array by only copying the elements which have a linkFn
7777 for (i = 0; i < linkFns.length; i+=3) {
7779 stableNodeList[idx] = nodeList[idx];
7782 stableNodeList = nodeList;
7785 for (i = 0, ii = linkFns.length; i < ii;) {
7786 node = stableNodeList[linkFns[i++]];
7787 nodeLinkFn = linkFns[i++];
7788 childLinkFn = linkFns[i++];
7791 if (nodeLinkFn.scope) {
7792 childScope = scope.$new();
7793 compile.$$addScopeInfo(jqLite(node), childScope);
7798 if (nodeLinkFn.transcludeOnThisElement) {
7799 childBoundTranscludeFn = createBoundTranscludeFn(
7800 scope, nodeLinkFn.transclude, parentBoundTranscludeFn);
7802 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) {
7803 childBoundTranscludeFn = parentBoundTranscludeFn;
7805 } else if (!parentBoundTranscludeFn && transcludeFn) {
7806 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn);
7809 childBoundTranscludeFn = null;
7812 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
7814 } else if (childLinkFn) {
7815 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
7821 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) {
7823 var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
7825 if (!transcludedScope) {
7826 transcludedScope = scope.$new(false, containingScope);
7827 transcludedScope.$$transcluded = true;
7830 return transcludeFn(transcludedScope, cloneFn, {
7831 parentBoundTranscludeFn: previousBoundTranscludeFn,
7832 transcludeControllers: controllers,
7833 futureParentElement: futureParentElement
7837 return boundTranscludeFn;
7841 * Looks for directives on the given node and adds them to the directive collection which is
7844 * @param node Node to search.
7845 * @param directives An array to which the directives are added to. This array is sorted before
7846 * the function returns.
7847 * @param attrs The shared attrs object which is used to populate the normalized attributes.
7848 * @param {number=} maxPriority Max directive priority.
7850 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
7851 var nodeType = node.nodeType,
7852 attrsMap = attrs.$attr,
7857 case NODE_TYPE_ELEMENT: /* Element */
7858 // use the node name: <directive>
7859 addDirective(directives,
7860 directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective);
7862 // iterate over the attributes
7863 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes,
7864 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
7865 var attrStartName = false;
7866 var attrEndName = false;
7870 value = trim(attr.value);
7872 // support ngAttr attribute binding
7873 ngAttrName = directiveNormalize(name);
7874 if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
7875 name = name.replace(PREFIX_REGEXP, '')
7876 .substr(8).replace(/_(.)/g, function(match, letter) {
7877 return letter.toUpperCase();
7881 var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE);
7882 if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) {
7883 attrStartName = name;
7884 attrEndName = name.substr(0, name.length - 5) + 'end';
7885 name = name.substr(0, name.length - 6);
7888 nName = directiveNormalize(name.toLowerCase());
7889 attrsMap[nName] = name;
7890 if (isNgAttr || !attrs.hasOwnProperty(nName)) {
7891 attrs[nName] = value;
7892 if (getBooleanAttrName(node, nName)) {
7893 attrs[nName] = true; // presence means true
7896 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
7897 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
7901 // use class as directive
7902 className = node.className;
7903 if (isObject(className)) {
7904 // Maybe SVGAnimatedString
7905 className = className.animVal;
7907 if (isString(className) && className !== '') {
7908 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
7909 nName = directiveNormalize(match[2]);
7910 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) {
7911 attrs[nName] = trim(match[3]);
7913 className = className.substr(match.index + match[0].length);
7917 case NODE_TYPE_TEXT: /* Text Node */
7919 // Workaround for #11781
7920 while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) {
7921 node.nodeValue = node.nodeValue + node.nextSibling.nodeValue;
7922 node.parentNode.removeChild(node.nextSibling);
7925 addTextInterpolateDirective(directives, node.nodeValue);
7927 case NODE_TYPE_COMMENT: /* Comment */
7929 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
7931 nName = directiveNormalize(match[1]);
7932 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) {
7933 attrs[nName] = trim(match[2]);
7937 // turns out that under some circumstances IE9 throws errors when one attempts to read
7938 // comment's node value.
7939 // Just ignore it and continue. (Can't seem to reproduce in test case.)
7944 directives.sort(byPriority);
7949 * Given a node with an directive-start it collects all of the siblings until it finds
7956 function groupScan(node, attrStart, attrEnd) {
7959 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
7962 throw $compileMinErr('uterdir',
7963 "Unterminated attribute, found '{0}' but no matching '{1}' found.",
7964 attrStart, attrEnd);
7966 if (node.nodeType == NODE_TYPE_ELEMENT) {
7967 if (node.hasAttribute(attrStart)) depth++;
7968 if (node.hasAttribute(attrEnd)) depth--;
7971 node = node.nextSibling;
7972 } while (depth > 0);
7977 return jqLite(nodes);
7981 * Wrapper for linking function which converts normal linking function into a grouped
7986 * @returns {Function}
7988 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
7989 return function(scope, element, attrs, controllers, transcludeFn) {
7990 element = groupScan(element[0], attrStart, attrEnd);
7991 return linkFn(scope, element, attrs, controllers, transcludeFn);
7996 * Once the directives have been collected, their compile functions are executed. This method
7997 * is responsible for inlining directive templates as well as terminating the application
7998 * of the directives if the terminal directive has been reached.
8000 * @param {Array} directives Array of collected directives to execute their compile function.
8001 * this needs to be pre-sorted by priority order.
8002 * @param {Node} compileNode The raw DOM node to apply the compile functions to
8003 * @param {Object} templateAttrs The shared attribute function
8004 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
8005 * scope argument is auto-generated to the new
8006 * child of the transcluded parent scope.
8007 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this
8008 * argument has the root jqLite array so that we can replace nodes
8010 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when
8011 * compiling the transclusion.
8012 * @param {Array.<Function>} preLinkFns
8013 * @param {Array.<Function>} postLinkFns
8014 * @param {Object} previousCompileContext Context used for previous compilation of the current
8016 * @returns {Function} linkFn
8018 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn,
8019 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns,
8020 previousCompileContext) {
8021 previousCompileContext = previousCompileContext || {};
8023 var terminalPriority = -Number.MAX_VALUE,
8024 newScopeDirective = previousCompileContext.newScopeDirective,
8025 controllerDirectives = previousCompileContext.controllerDirectives,
8026 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,
8027 templateDirective = previousCompileContext.templateDirective,
8028 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
8029 hasTranscludeDirective = false,
8030 hasTemplate = false,
8031 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective,
8032 $compileNode = templateAttrs.$$element = jqLite(compileNode),
8036 replaceDirective = originalReplaceDirective,
8037 childTranscludeFn = transcludeFn,
8041 // executes all directives on the current element
8042 for (var i = 0, ii = directives.length; i < ii; i++) {
8043 directive = directives[i];
8044 var attrStart = directive.$$start;
8045 var attrEnd = directive.$$end;
8047 // collect multiblock sections
8049 $compileNode = groupScan(compileNode, attrStart, attrEnd);
8051 $template = undefined;
8053 if (terminalPriority > directive.priority) {
8054 break; // prevent further processing of directives
8057 if (directiveValue = directive.scope) {
8059 // skip the check for directives with async templates, we'll check the derived sync
8060 // directive when the template arrives
8061 if (!directive.templateUrl) {
8062 if (isObject(directiveValue)) {
8063 // This directive is trying to add an isolated scope.
8064 // Check that there is no scope of any kind already
8065 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective,
8066 directive, $compileNode);
8067 newIsolateScopeDirective = directive;
8069 // This directive is trying to add a child scope.
8070 // Check that there is no isolated scope already
8071 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive,
8076 newScopeDirective = newScopeDirective || directive;
8079 directiveName = directive.name;
8081 if (!directive.templateUrl && directive.controller) {
8082 directiveValue = directive.controller;
8083 controllerDirectives = controllerDirectives || createMap();
8084 assertNoDuplicate("'" + directiveName + "' controller",
8085 controllerDirectives[directiveName], directive, $compileNode);
8086 controllerDirectives[directiveName] = directive;
8089 if (directiveValue = directive.transclude) {
8090 hasTranscludeDirective = true;
8092 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion.
8093 // This option should only be used by directives that know how to safely handle element transclusion,
8094 // where the transcluded nodes are added or replaced after linking.
8095 if (!directive.$$tlb) {
8096 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode);
8097 nonTlbTranscludeDirective = directive;
8100 if (directiveValue == 'element') {
8101 hasElementTranscludeDirective = true;
8102 terminalPriority = directive.priority;
8103 $template = $compileNode;
8104 $compileNode = templateAttrs.$$element =
8105 jqLite(document.createComment(' ' + directiveName + ': ' +
8106 templateAttrs[directiveName] + ' '));
8107 compileNode = $compileNode[0];
8108 replaceWith(jqCollection, sliceArgs($template), compileNode);
8110 childTranscludeFn = compile($template, transcludeFn, terminalPriority,
8111 replaceDirective && replaceDirective.name, {
8113 // - controllerDirectives - otherwise we'll create duplicates controllers
8114 // - newIsolateScopeDirective or templateDirective - combining templates with
8115 // element transclusion doesn't make sense.
8117 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion
8118 // on the same element more than once.
8119 nonTlbTranscludeDirective: nonTlbTranscludeDirective
8122 $template = jqLite(jqLiteClone(compileNode)).contents();
8123 $compileNode.empty(); // clear contents
8124 childTranscludeFn = compile($template, transcludeFn, undefined,
8125 undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope});
8129 if (directive.template) {
8131 assertNoDuplicate('template', templateDirective, directive, $compileNode);
8132 templateDirective = directive;
8134 directiveValue = (isFunction(directive.template))
8135 ? directive.template($compileNode, templateAttrs)
8136 : directive.template;
8138 directiveValue = denormalizeTemplate(directiveValue);
8140 if (directive.replace) {
8141 replaceDirective = directive;
8142 if (jqLiteIsTextNode(directiveValue)) {
8145 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
8147 compileNode = $template[0];
8149 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
8150 throw $compileMinErr('tplrt',
8151 "Template for directive '{0}' must have exactly one root element. {1}",
8155 replaceWith(jqCollection, $compileNode, compileNode);
8157 var newTemplateAttrs = {$attr: {}};
8159 // combine directives from the original node and from the template:
8160 // - take the array of directives for this element
8161 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed)
8162 // - collect directives from the template and sort them by priority
8163 // - combine directives as: processed + template + unprocessed
8164 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs);
8165 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1));
8167 if (newIsolateScopeDirective || newScopeDirective) {
8168 // The original directive caused the current element to be replaced but this element
8169 // also needs to have a new scope, so we need to tell the template directives
8170 // that they would need to get their scope from further up, if they require transclusion
8171 markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective);
8173 directives = directives.concat(templateDirectives).concat(unprocessedDirectives);
8174 mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
8176 ii = directives.length;
8178 $compileNode.html(directiveValue);
8182 if (directive.templateUrl) {
8184 assertNoDuplicate('template', templateDirective, directive, $compileNode);
8185 templateDirective = directive;
8187 if (directive.replace) {
8188 replaceDirective = directive;
8191 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
8192 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, {
8193 controllerDirectives: controllerDirectives,
8194 newScopeDirective: (newScopeDirective !== directive) && newScopeDirective,
8195 newIsolateScopeDirective: newIsolateScopeDirective,
8196 templateDirective: templateDirective,
8197 nonTlbTranscludeDirective: nonTlbTranscludeDirective
8199 ii = directives.length;
8200 } else if (directive.compile) {
8202 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
8203 if (isFunction(linkFn)) {
8204 addLinkFns(null, linkFn, attrStart, attrEnd);
8205 } else if (linkFn) {
8206 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd);
8209 $exceptionHandler(e, startingTag($compileNode));
8213 if (directive.terminal) {
8214 nodeLinkFn.terminal = true;
8215 terminalPriority = Math.max(terminalPriority, directive.priority);
8220 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
8221 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective;
8222 nodeLinkFn.templateOnThisElement = hasTemplate;
8223 nodeLinkFn.transclude = childTranscludeFn;
8225 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective;
8227 // might be normal or delayed nodeLinkFn depending on if templateUrl is present
8230 ////////////////////
8232 function addLinkFns(pre, post, attrStart, attrEnd) {
8234 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd);
8235 pre.require = directive.require;
8236 pre.directiveName = directiveName;
8237 if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
8238 pre = cloneAndAnnotateFn(pre, {isolateScope: true});
8240 preLinkFns.push(pre);
8243 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd);
8244 post.require = directive.require;
8245 post.directiveName = directiveName;
8246 if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
8247 post = cloneAndAnnotateFn(post, {isolateScope: true});
8249 postLinkFns.push(post);
8254 function getControllers(directiveName, require, $element, elementControllers) {
8257 if (isString(require)) {
8258 var match = require.match(REQUIRE_PREFIX_REGEXP);
8259 var name = require.substring(match[0].length);
8260 var inheritType = match[1] || match[3];
8261 var optional = match[2] === '?';
8263 //If only parents then start at the parent element
8264 if (inheritType === '^^') {
8265 $element = $element.parent();
8266 //Otherwise attempt getting the controller from elementControllers in case
8267 //the element is transcluded (and has no data) and to avoid .data if possible
8269 value = elementControllers && elementControllers[name];
8270 value = value && value.instance;
8274 var dataName = '$' + name + 'Controller';
8275 value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName);
8278 if (!value && !optional) {
8279 throw $compileMinErr('ctreq',
8280 "Controller '{0}', required by directive '{1}', can't be found!",
8281 name, directiveName);
8283 } else if (isArray(require)) {
8285 for (var i = 0, ii = require.length; i < ii; i++) {
8286 value[i] = getControllers(directiveName, require[i], $element, elementControllers);
8290 return value || null;
8293 function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope) {
8294 var elementControllers = createMap();
8295 for (var controllerKey in controllerDirectives) {
8296 var directive = controllerDirectives[controllerKey];
8298 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
8301 $transclude: transcludeFn
8304 var controller = directive.controller;
8305 if (controller == '@') {
8306 controller = attrs[directive.name];
8309 var controllerInstance = $controller(controller, locals, true, directive.controllerAs);
8311 // For directives with element transclusion the element is a comment,
8312 // but jQuery .data doesn't support attaching data to comment nodes as it's hard to
8313 // clean up (http://bugs.jquery.com/ticket/8335).
8314 // Instead, we save the controllers for the element in a local hash and attach to .data
8315 // later, once we have the actual element.
8316 elementControllers[directive.name] = controllerInstance;
8317 if (!hasElementTranscludeDirective) {
8318 $element.data('$' + directive.name + 'Controller', controllerInstance.instance);
8321 return elementControllers;
8324 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
8325 var linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element,
8326 attrs, removeScopeBindingWatches, removeControllerBindingWatches;
8328 if (compileNode === linkNode) {
8329 attrs = templateAttrs;
8330 $element = templateAttrs.$$element;
8332 $element = jqLite(linkNode);
8333 attrs = new Attributes($element, templateAttrs);
8336 controllerScope = scope;
8337 if (newIsolateScopeDirective) {
8338 isolateScope = scope.$new(true);
8339 } else if (newScopeDirective) {
8340 controllerScope = scope.$parent;
8343 if (boundTranscludeFn) {
8344 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
8345 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
8346 transcludeFn = controllersBoundTransclude;
8347 transcludeFn.$$boundTransclude = boundTranscludeFn;
8350 if (controllerDirectives) {
8351 elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope);
8354 if (newIsolateScopeDirective) {
8355 // Initialize isolate scope bindings for new isolate scope directive.
8356 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
8357 templateDirective === newIsolateScopeDirective.$$originalDirective)));
8358 compile.$$addScopeClass($element, true);
8359 isolateScope.$$isolateBindings =
8360 newIsolateScopeDirective.$$isolateBindings;
8361 removeScopeBindingWatches = initializeDirectiveBindings(scope, attrs, isolateScope,
8362 isolateScope.$$isolateBindings,
8363 newIsolateScopeDirective);
8364 if (removeScopeBindingWatches) {
8365 isolateScope.$on('$destroy', removeScopeBindingWatches);
8369 // Initialize bindToController bindings
8370 for (var name in elementControllers) {
8371 var controllerDirective = controllerDirectives[name];
8372 var controller = elementControllers[name];
8373 var bindings = controllerDirective.$$bindings.bindToController;
8375 if (controller.identifier && bindings) {
8376 removeControllerBindingWatches =
8377 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
8380 var controllerResult = controller();
8381 if (controllerResult !== controller.instance) {
8382 // If the controller constructor has a return value, overwrite the instance
8383 // from setupControllers
8384 controller.instance = controllerResult;
8385 $element.data('$' + controllerDirective.name + 'Controller', controllerResult);
8386 removeControllerBindingWatches && removeControllerBindingWatches();
8387 removeControllerBindingWatches =
8388 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
8393 for (i = 0, ii = preLinkFns.length; i < ii; i++) {
8394 linkFn = preLinkFns[i];
8395 invokeLinkFn(linkFn,
8396 linkFn.isolateScope ? isolateScope : scope,
8399 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
8405 // We only pass the isolate scope, if the isolate directive has a template,
8406 // otherwise the child elements do not belong to the isolate directive.
8407 var scopeToChild = scope;
8408 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) {
8409 scopeToChild = isolateScope;
8411 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
8414 for (i = postLinkFns.length - 1; i >= 0; i--) {
8415 linkFn = postLinkFns[i];
8416 invokeLinkFn(linkFn,
8417 linkFn.isolateScope ? isolateScope : scope,
8420 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
8425 // This is the function that is injected as `$transclude`.
8426 // Note: all arguments are optional!
8427 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) {
8428 var transcludeControllers;
8430 // No scope passed in:
8431 if (!isScope(scope)) {
8432 futureParentElement = cloneAttachFn;
8433 cloneAttachFn = scope;
8437 if (hasElementTranscludeDirective) {
8438 transcludeControllers = elementControllers;
8440 if (!futureParentElement) {
8441 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
8443 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
8448 // Depending upon the context in which a directive finds itself it might need to have a new isolated
8449 // or child scope created. For instance:
8450 // * if the directive has been pulled into a template because another directive with a higher priority
8451 // asked for element transclusion
8452 // * if the directive itself asks for transclusion but it is at the root of a template and the original
8453 // element was replaced. See https://github.com/angular/angular.js/issues/12936
8454 function markDirectiveScope(directives, isolateScope, newScope) {
8455 for (var j = 0, jj = directives.length; j < jj; j++) {
8456 directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope});
8461 * looks up the directive and decorates it with exception handling and proper parameters. We
8462 * call this the boundDirective.
8464 * @param {string} name name of the directive to look up.
8465 * @param {string} location The directive must be found in specific format.
8466 * String containing any of theses characters:
8468 * * `E`: element name
8472 * @returns {boolean} true if directive was added.
8474 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName,
8476 if (name === ignoreDirective) return null;
8478 if (hasDirectives.hasOwnProperty(name)) {
8479 for (var directive, directives = $injector.get(name + Suffix),
8480 i = 0, ii = directives.length; i < ii; i++) {
8482 directive = directives[i];
8483 if ((isUndefined(maxPriority) || maxPriority > directive.priority) &&
8484 directive.restrict.indexOf(location) != -1) {
8485 if (startAttrName) {
8486 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
8488 tDirectives.push(directive);
8491 } catch (e) { $exceptionHandler(e); }
8499 * looks up the directive and returns true if it is a multi-element directive,
8500 * and therefore requires DOM nodes between -start and -end markers to be grouped
8503 * @param {string} name name of the directive to look up.
8504 * @returns true if directive was registered as multi-element.
8506 function directiveIsMultiElement(name) {
8507 if (hasDirectives.hasOwnProperty(name)) {
8508 for (var directive, directives = $injector.get(name + Suffix),
8509 i = 0, ii = directives.length; i < ii; i++) {
8510 directive = directives[i];
8511 if (directive.multiElement) {
8520 * When the element is replaced with HTML template then the new attributes
8521 * on the template need to be merged with the existing attributes in the DOM.
8522 * The desired effect is to have both of the attributes present.
8524 * @param {object} dst destination attributes (original DOM)
8525 * @param {object} src source attributes (from the directive template)
8527 function mergeTemplateAttributes(dst, src) {
8528 var srcAttr = src.$attr,
8529 dstAttr = dst.$attr,
8530 $element = dst.$$element;
8532 // reapply the old attributes to the new element
8533 forEach(dst, function(value, key) {
8534 if (key.charAt(0) != '$') {
8535 if (src[key] && src[key] !== value) {
8536 value += (key === 'style' ? ';' : ' ') + src[key];
8538 dst.$set(key, value, true, srcAttr[key]);
8542 // copy the new attributes on the old attrs object
8543 forEach(src, function(value, key) {
8544 if (key == 'class') {
8545 safeAddClass($element, value);
8546 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value;
8547 } else if (key == 'style') {
8548 $element.attr('style', $element.attr('style') + ';' + value);
8549 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value;
8550 // `dst` will never contain hasOwnProperty as DOM parser won't let it.
8551 // You will get an "InvalidCharacterError: DOM Exception 5" error if you
8552 // have an attribute like "has-own-property" or "data-has-own-property", etc.
8553 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) {
8555 dstAttr[key] = srcAttr[key];
8561 function compileTemplateUrl(directives, $compileNode, tAttrs,
8562 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) {
8564 afterTemplateNodeLinkFn,
8565 afterTemplateChildLinkFn,
8566 beforeTemplateCompileNode = $compileNode[0],
8567 origAsyncDirective = directives.shift(),
8568 derivedSyncDirective = inherit(origAsyncDirective, {
8569 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
8571 templateUrl = (isFunction(origAsyncDirective.templateUrl))
8572 ? origAsyncDirective.templateUrl($compileNode, tAttrs)
8573 : origAsyncDirective.templateUrl,
8574 templateNamespace = origAsyncDirective.templateNamespace;
8576 $compileNode.empty();
8578 $templateRequest(templateUrl)
8579 .then(function(content) {
8580 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn;
8582 content = denormalizeTemplate(content);
8584 if (origAsyncDirective.replace) {
8585 if (jqLiteIsTextNode(content)) {
8588 $template = removeComments(wrapTemplate(templateNamespace, trim(content)));
8590 compileNode = $template[0];
8592 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
8593 throw $compileMinErr('tplrt',
8594 "Template for directive '{0}' must have exactly one root element. {1}",
8595 origAsyncDirective.name, templateUrl);
8598 tempTemplateAttrs = {$attr: {}};
8599 replaceWith($rootElement, $compileNode, compileNode);
8600 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs);
8602 if (isObject(origAsyncDirective.scope)) {
8603 // the original directive that caused the template to be loaded async required
8605 markDirectiveScope(templateDirectives, true);
8607 directives = templateDirectives.concat(directives);
8608 mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
8610 compileNode = beforeTemplateCompileNode;
8611 $compileNode.html(content);
8614 directives.unshift(derivedSyncDirective);
8616 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs,
8617 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns,
8618 previousCompileContext);
8619 forEach($rootElement, function(node, i) {
8620 if (node == compileNode) {
8621 $rootElement[i] = $compileNode[0];
8624 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
8626 while (linkQueue.length) {
8627 var scope = linkQueue.shift(),
8628 beforeTemplateLinkNode = linkQueue.shift(),
8629 linkRootElement = linkQueue.shift(),
8630 boundTranscludeFn = linkQueue.shift(),
8631 linkNode = $compileNode[0];
8633 if (scope.$$destroyed) continue;
8635 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
8636 var oldClasses = beforeTemplateLinkNode.className;
8638 if (!(previousCompileContext.hasElementTranscludeDirective &&
8639 origAsyncDirective.replace)) {
8640 // it was cloned therefore we have to clone as well.
8641 linkNode = jqLiteClone(compileNode);
8643 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
8645 // Copy in CSS classes from original node
8646 safeAddClass(jqLite(linkNode), oldClasses);
8648 if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
8649 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
8651 childBoundTranscludeFn = boundTranscludeFn;
8653 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
8654 childBoundTranscludeFn);
8659 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
8660 var childBoundTranscludeFn = boundTranscludeFn;
8661 if (scope.$$destroyed) return;
8663 linkQueue.push(scope,
8666 childBoundTranscludeFn);
8668 if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
8669 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
8671 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn);
8678 * Sorting function for bound directives.
8680 function byPriority(a, b) {
8681 var diff = b.priority - a.priority;
8682 if (diff !== 0) return diff;
8683 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1;
8684 return a.index - b.index;
8687 function assertNoDuplicate(what, previousDirective, directive, element) {
8689 function wrapModuleNameIfDefined(moduleName) {
8691 (' (module: ' + moduleName + ')') :
8695 if (previousDirective) {
8696 throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}',
8697 previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName),
8698 directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element));
8703 function addTextInterpolateDirective(directives, text) {
8704 var interpolateFn = $interpolate(text, true);
8705 if (interpolateFn) {
8708 compile: function textInterpolateCompileFn(templateNode) {
8709 var templateNodeParent = templateNode.parent(),
8710 hasCompileParent = !!templateNodeParent.length;
8712 // When transcluding a template that has bindings in the root
8713 // we don't have a parent and thus need to add the class during linking fn.
8714 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent);
8716 return function textInterpolateLinkFn(scope, node) {
8717 var parent = node.parent();
8718 if (!hasCompileParent) compile.$$addBindingClass(parent);
8719 compile.$$addBindingInfo(parent, interpolateFn.expressions);
8720 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
8721 node[0].nodeValue = value;
8730 function wrapTemplate(type, template) {
8731 type = lowercase(type || 'html');
8735 var wrapper = document.createElement('div');
8736 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>';
8737 return wrapper.childNodes[0].childNodes;
8744 function getTrustedContext(node, attrNormalizedName) {
8745 if (attrNormalizedName == "srcdoc") {
8748 var tag = nodeName_(node);
8749 // maction[xlink:href] can source SVG. It's not limited to <maction>.
8750 if (attrNormalizedName == "xlinkHref" ||
8751 (tag == "form" && attrNormalizedName == "action") ||
8752 (tag != "img" && (attrNormalizedName == "src" ||
8753 attrNormalizedName == "ngSrc"))) {
8754 return $sce.RESOURCE_URL;
8759 function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) {
8760 var trustedContext = getTrustedContext(node, name);
8761 allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing;
8763 var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing);
8765 // no interpolation found -> ignore
8766 if (!interpolateFn) return;
8769 if (name === "multiple" && nodeName_(node) === "select") {
8770 throw $compileMinErr("selmulti",
8771 "Binding to the 'multiple' attribute is not supported. Element: {0}",
8777 compile: function() {
8779 pre: function attrInterpolatePreLinkFn(scope, element, attr) {
8780 var $$observers = (attr.$$observers || (attr.$$observers = createMap()));
8782 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
8783 throw $compileMinErr('nodomevents',
8784 "Interpolations for HTML DOM event attributes are disallowed. Please use the " +
8785 "ng- versions (such as ng-click instead of onclick) instead.");
8788 // If the attribute has changed since last $interpolate()ed
8789 var newValue = attr[name];
8790 if (newValue !== value) {
8791 // we need to interpolate again since the attribute value has been updated
8792 // (e.g. by another directive's compile function)
8793 // ensure unset/empty values make interpolateFn falsy
8794 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing);
8798 // if attribute was updated so that there is no interpolation going on we don't want to
8799 // register any observers
8800 if (!interpolateFn) return;
8802 // initialize attr object so that it's ready in case we need the value for isolate
8803 // scope initialization, otherwise the value would not be available from isolate
8804 // directive's linking fn during linking phase
8805 attr[name] = interpolateFn(scope);
8807 ($$observers[name] || ($$observers[name] = [])).$$inter = true;
8808 (attr.$$observers && attr.$$observers[name].$$scope || scope).
8809 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) {
8810 //special case for class attribute addition + removal
8811 //so that class changes can tap into the animation
8812 //hooks provided by the $animate service. Be sure to
8813 //skip animations when the first digest occurs (when
8814 //both the new and the old values are the same) since
8815 //the CSS classes are the non-interpolated values
8816 if (name === 'class' && newValue != oldValue) {
8817 attr.$updateClass(newValue, oldValue);
8819 attr.$set(name, newValue);
8830 * This is a special jqLite.replaceWith, which can replace items which
8831 * have no parents, provided that the containing jqLite collection is provided.
8833 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
8834 * in the root of the tree.
8835 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep
8836 * the shell, but replace its DOM node reference.
8837 * @param {Node} newNode The new DOM node.
8839 function replaceWith($rootElement, elementsToRemove, newNode) {
8840 var firstElementToRemove = elementsToRemove[0],
8841 removeCount = elementsToRemove.length,
8842 parent = firstElementToRemove.parentNode,
8846 for (i = 0, ii = $rootElement.length; i < ii; i++) {
8847 if ($rootElement[i] == firstElementToRemove) {
8848 $rootElement[i++] = newNode;
8849 for (var j = i, j2 = j + removeCount - 1,
8850 jj = $rootElement.length;
8851 j < jj; j++, j2++) {
8853 $rootElement[j] = $rootElement[j2];
8855 delete $rootElement[j];
8858 $rootElement.length -= removeCount - 1;
8860 // If the replaced element is also the jQuery .context then replace it
8861 // .context is a deprecated jQuery api, so we should set it only when jQuery set it
8862 // http://api.jquery.com/context/
8863 if ($rootElement.context === firstElementToRemove) {
8864 $rootElement.context = newNode;
8872 parent.replaceChild(newNode, firstElementToRemove);
8875 // TODO(perf): what's this document fragment for? is it needed? can we at least reuse it?
8876 var fragment = document.createDocumentFragment();
8877 fragment.appendChild(firstElementToRemove);
8879 if (jqLite.hasData(firstElementToRemove)) {
8880 // Copy over user data (that includes Angular's $scope etc.). Don't copy private
8881 // data here because there's no public interface in jQuery to do that and copying over
8882 // event listeners (which is the main use of private data) wouldn't work anyway.
8883 jqLite.data(newNode, jqLite.data(firstElementToRemove));
8885 // Remove data of the replaced element. We cannot just call .remove()
8886 // on the element it since that would deallocate scope that is needed
8887 // for the new node. Instead, remove the data "manually".
8889 delete jqLite.cache[firstElementToRemove[jqLite.expando]];
8891 // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after
8892 // the replaced element. The cleanData version monkey-patched by Angular would cause
8893 // the scope to be trashed and we do need the very same scope to work with the new
8894 // element. However, we cannot just cache the non-patched version and use it here as
8895 // that would break if another library patches the method after Angular does (one
8896 // example is jQuery UI). Instead, set a flag indicating scope destroying should be
8897 // skipped this one time.
8898 skipDestroyOnNextJQueryCleanData = true;
8899 jQuery.cleanData([firstElementToRemove]);
8903 for (var k = 1, kk = elementsToRemove.length; k < kk; k++) {
8904 var element = elementsToRemove[k];
8905 jqLite(element).remove(); // must do this way to clean up expando
8906 fragment.appendChild(element);
8907 delete elementsToRemove[k];
8910 elementsToRemove[0] = newNode;
8911 elementsToRemove.length = 1;
8915 function cloneAndAnnotateFn(fn, annotation) {
8916 return extend(function() { return fn.apply(null, arguments); }, fn, annotation);
8920 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) {
8922 linkFn(scope, $element, attrs, controllers, transcludeFn);
8924 $exceptionHandler(e, startingTag($element));
8929 // Set up $watches for isolate scope and controller bindings. This process
8930 // only occurs for isolate scopes and new scopes with controllerAs.
8931 function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
8932 var removeWatchCollection = [];
8933 forEach(bindings, function(definition, scopeName) {
8934 var attrName = definition.attrName,
8935 optional = definition.optional,
8936 mode = definition.mode, // @, =, or &
8938 parentGet, parentSet, compare;
8943 if (!optional && !hasOwnProperty.call(attrs, attrName)) {
8944 destination[scopeName] = attrs[attrName] = void 0;
8946 attrs.$observe(attrName, function(value) {
8947 if (isString(value)) {
8948 destination[scopeName] = value;
8951 attrs.$$observers[attrName].$$scope = scope;
8952 if (isString(attrs[attrName])) {
8953 // If the attribute has been provided then we trigger an interpolation to ensure
8954 // the value is there for use in the link fn
8955 destination[scopeName] = $interpolate(attrs[attrName])(scope);
8960 if (!hasOwnProperty.call(attrs, attrName)) {
8961 if (optional) break;
8962 attrs[attrName] = void 0;
8964 if (optional && !attrs[attrName]) break;
8966 parentGet = $parse(attrs[attrName]);
8967 if (parentGet.literal) {
8970 compare = function(a, b) { return a === b || (a !== a && b !== b); };
8972 parentSet = parentGet.assign || function() {
8973 // reset the change, or we will throw this exception on every $digest
8974 lastValue = destination[scopeName] = parentGet(scope);
8975 throw $compileMinErr('nonassign',
8976 "Expression '{0}' used with directive '{1}' is non-assignable!",
8977 attrs[attrName], directive.name);
8979 lastValue = destination[scopeName] = parentGet(scope);
8980 var parentValueWatch = function parentValueWatch(parentValue) {
8981 if (!compare(parentValue, destination[scopeName])) {
8982 // we are out of sync and need to copy
8983 if (!compare(parentValue, lastValue)) {
8984 // parent changed and it has precedence
8985 destination[scopeName] = parentValue;
8987 // if the parent can be assigned then do so
8988 parentSet(scope, parentValue = destination[scopeName]);
8991 return lastValue = parentValue;
8993 parentValueWatch.$stateful = true;
8995 if (definition.collection) {
8996 removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
8998 removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
9000 removeWatchCollection.push(removeWatch);
9004 // Don't assign Object.prototype method to scope
9005 parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;
9007 // Don't assign noop to destination if expression is not valid
9008 if (parentGet === noop && optional) break;
9010 destination[scopeName] = function(locals) {
9011 return parentGet(scope, locals);
9017 return removeWatchCollection.length && function removeWatches() {
9018 for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) {
9019 removeWatchCollection[i]();
9026 var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
9028 * Converts all accepted directives format into proper directive name.
9029 * @param name Name to normalize
9031 function directiveNormalize(name) {
9032 return camelCase(name.replace(PREFIX_REGEXP, ''));
9037 * @name $compile.directive.Attributes
9040 * A shared object between directive compile / linking functions which contains normalized DOM
9041 * element attributes. The values reflect current binding state `{{ }}`. The normalization is
9042 * needed since all of these are treated as equivalent in Angular:
9045 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
9051 * @name $compile.directive.Attributes#$attr
9054 * A map of DOM element attribute names to the normalized name. This is
9055 * needed to do reverse lookup from normalized name back to actual name.
9061 * @name $compile.directive.Attributes#$set
9065 * Set DOM element attribute value.
9068 * @param {string} name Normalized element attribute name of the property to modify. The name is
9069 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
9070 * property to the original name.
9071 * @param {string} value Value to set the attribute to. The value can be an interpolated string.
9077 * Closure compiler type information
9080 function nodesetLinkingFn(
9081 /* angular.Scope */ scope,
9082 /* NodeList */ nodeList,
9083 /* Element */ rootElement,
9084 /* function(Function) */ boundTranscludeFn
9087 function directiveLinkingFn(
9088 /* nodesetLinkingFn */ nodesetLinkingFn,
9089 /* angular.Scope */ scope,
9091 /* Element */ rootElement,
9092 /* function(Function) */ boundTranscludeFn
9095 function tokenDifference(str1, str2) {
9097 tokens1 = str1.split(/\s+/),
9098 tokens2 = str2.split(/\s+/);
9101 for (var i = 0; i < tokens1.length; i++) {
9102 var token = tokens1[i];
9103 for (var j = 0; j < tokens2.length; j++) {
9104 if (token == tokens2[j]) continue outer;
9106 values += (values.length > 0 ? ' ' : '') + token;
9111 function removeComments(jqNodes) {
9112 jqNodes = jqLite(jqNodes);
9113 var i = jqNodes.length;
9120 var node = jqNodes[i];
9121 if (node.nodeType === NODE_TYPE_COMMENT) {
9122 splice.call(jqNodes, i, 1);
9128 var $controllerMinErr = minErr('$controller');
9131 var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
9132 function identifierForController(controller, ident) {
9133 if (ident && isString(ident)) return ident;
9134 if (isString(controller)) {
9135 var match = CNTRL_REG.exec(controller);
9136 if (match) return match[3];
9143 * @name $controllerProvider
9145 * The {@link ng.$controller $controller service} is used by Angular to create new
9148 * This provider allows controller registration via the
9149 * {@link ng.$controllerProvider#register register} method.
9151 function $ControllerProvider() {
9152 var controllers = {},
9157 * @name $controllerProvider#register
9158 * @param {string|Object} name Controller name, or an object map of controllers where the keys are
9159 * the names and the values are the constructors.
9160 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI
9161 * annotations in the array notation).
9163 this.register = function(name, constructor) {
9164 assertNotHasOwnProperty(name, 'controller');
9165 if (isObject(name)) {
9166 extend(controllers, name);
9168 controllers[name] = constructor;
9174 * @name $controllerProvider#allowGlobals
9175 * @description If called, allows `$controller` to find controller constructors on `window`
9177 this.allowGlobals = function() {
9182 this.$get = ['$injector', '$window', function($injector, $window) {
9187 * @requires $injector
9189 * @param {Function|string} constructor If called with a function then it's considered to be the
9190 * controller constructor function. Otherwise it's considered to be a string which is used
9191 * to retrieve the controller constructor using the following steps:
9193 * * check if a controller with given name is registered via `$controllerProvider`
9194 * * check if evaluating the string on the current scope returns a constructor
9195 * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
9196 * `window` object (not recommended)
9198 * The string can use the `controller as property` syntax, where the controller instance is published
9199 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this
9200 * to work correctly.
9202 * @param {Object} locals Injection locals for Controller.
9203 * @return {Object} Instance of given controller.
9206 * `$controller` service is responsible for instantiating controllers.
9208 * It's just a simple call to {@link auto.$injector $injector}, but extracted into
9209 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788).
9211 return function(expression, locals, later, ident) {
9213 // param `later` --- indicates that the controller's constructor is invoked at a later time.
9214 // If true, $controller will allocate the object with the correct
9215 // prototype chain, but will not invoke the controller until a returned
9216 // callback is invoked.
9217 // param `ident` --- An optional label which overrides the label parsed from the controller
9218 // expression, if any.
9219 var instance, match, constructor, identifier;
9220 later = later === true;
9221 if (ident && isString(ident)) {
9225 if (isString(expression)) {
9226 match = expression.match(CNTRL_REG);
9228 throw $controllerMinErr('ctrlfmt',
9229 "Badly formed controller string '{0}'. " +
9230 "Must match `__name__ as __id__` or `__name__`.", expression);
9232 constructor = match[1],
9233 identifier = identifier || match[3];
9234 expression = controllers.hasOwnProperty(constructor)
9235 ? controllers[constructor]
9236 : getter(locals.$scope, constructor, true) ||
9237 (globals ? getter($window, constructor, true) : undefined);
9239 assertArgFn(expression, constructor, true);
9243 // Instantiate controller later:
9244 // This machinery is used to create an instance of the object before calling the
9245 // controller's constructor itself.
9247 // This allows properties to be added to the controller before the constructor is
9248 // invoked. Primarily, this is used for isolate scope bindings in $compile.
9250 // This feature is not intended for use by applications, and is thus not documented
9252 // Object creation: http://jsperf.com/create-constructor/2
9253 var controllerPrototype = (isArray(expression) ?
9254 expression[expression.length - 1] : expression).prototype;
9255 instance = Object.create(controllerPrototype || null);
9258 addIdentifier(locals, identifier, instance, constructor || expression.name);
9262 return instantiate = extend(function() {
9263 var result = $injector.invoke(expression, instance, locals, constructor);
9264 if (result !== instance && (isObject(result) || isFunction(result))) {
9267 // If result changed, re-assign controllerAs value to scope.
9268 addIdentifier(locals, identifier, instance, constructor || expression.name);
9274 identifier: identifier
9278 instance = $injector.instantiate(expression, locals, constructor);
9281 addIdentifier(locals, identifier, instance, constructor || expression.name);
9287 function addIdentifier(locals, identifier, instance, name) {
9288 if (!(locals && isObject(locals.$scope))) {
9289 throw minErr('$controller')('noscp',
9290 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.",
9294 locals.$scope[identifier] = instance;
9305 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object.
9308 <example module="documentExample">
9309 <file name="index.html">
9310 <div ng-controller="ExampleController">
9311 <p>$document title: <b ng-bind="title"></b></p>
9312 <p>window.document title: <b ng-bind="windowTitle"></b></p>
9315 <file name="script.js">
9316 angular.module('documentExample', [])
9317 .controller('ExampleController', ['$scope', '$document', function($scope, $document) {
9318 $scope.title = $document[0].title;
9319 $scope.windowTitle = angular.element(window.document)[0].title;
9324 function $DocumentProvider() {
9325 this.$get = ['$window', function(window) {
9326 return jqLite(window.document);
9332 * @name $exceptionHandler
9336 * Any uncaught exception in angular expressions is delegated to this service.
9337 * The default implementation simply delegates to `$log.error` which logs it into
9338 * the browser console.
9340 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by
9341 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing.
9346 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() {
9347 * return function(exception, cause) {
9348 * exception.message += ' (caused by "' + cause + '")';
9354 * This example will override the normal action of `$exceptionHandler`, to make angular
9355 * exceptions fail hard when they happen, instead of just logging to the console.
9358 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind`
9359 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler}
9360 * (unless executed during a digest).
9362 * If you wish, you can manually delegate exceptions, e.g.
9363 * `try { ... } catch(e) { $exceptionHandler(e); }`
9365 * @param {Error} exception Exception associated with the error.
9366 * @param {string=} cause optional information about the context in which
9367 * the error was thrown.
9370 function $ExceptionHandlerProvider() {
9371 this.$get = ['$log', function($log) {
9372 return function(exception, cause) {
9373 $log.error.apply($log, arguments);
9378 var $$ForceReflowProvider = function() {
9379 this.$get = ['$document', function($document) {
9380 return function(domNode) {
9381 //the line below will force the browser to perform a repaint so
9382 //that all the animated elements within the animation frame will
9383 //be properly updated and drawn on screen. This is required to
9384 //ensure that the preparation animation is properly flushed so that
9385 //the active state picks up from there. DO NOT REMOVE THIS LINE.
9386 //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH
9387 //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND
9388 //WILL TAKE YEARS AWAY FROM YOUR LIFE.
9390 if (!domNode.nodeType && domNode instanceof jqLite) {
9391 domNode = domNode[0];
9394 domNode = $document[0].body;
9396 return domNode.offsetWidth + 1;
9401 var APPLICATION_JSON = 'application/json';
9402 var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
9403 var JSON_START = /^\[|^\{(?!\{)/;
9408 var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
9409 var $httpMinErr = minErr('$http');
9410 var $httpMinErrLegacyFn = function(method) {
9412 throw $httpMinErr('legacy', 'The method `{0}` on the promise returned from `$http` has been disabled.', method);
9416 function serializeValue(v) {
9418 return isDate(v) ? v.toISOString() : toJson(v);
9424 function $HttpParamSerializerProvider() {
9427 * @name $httpParamSerializer
9430 * Default {@link $http `$http`} params serializer that converts objects to strings
9431 * according to the following rules:
9433 * * `{'foo': 'bar'}` results in `foo=bar`
9434 * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object)
9435 * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element)
9436 * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object)
9438 * Note that serializer will sort the request parameters alphabetically.
9441 this.$get = function() {
9442 return function ngParamSerializer(params) {
9443 if (!params) return '';
9445 forEachSorted(params, function(value, key) {
9446 if (value === null || isUndefined(value)) return;
9447 if (isArray(value)) {
9448 forEach(value, function(v, k) {
9449 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v)));
9452 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
9456 return parts.join('&');
9461 function $HttpParamSerializerJQLikeProvider() {
9464 * @name $httpParamSerializerJQLike
9467 * Alternative {@link $http `$http`} params serializer that follows
9468 * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic.
9469 * The serializer will also sort the params alphabetically.
9471 * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property:
9478 * paramSerializer: '$httpParamSerializerJQLike'
9482 * It is also possible to set it as the default `paramSerializer` in the
9483 * {@link $httpProvider#defaults `$httpProvider`}.
9485 * Additionally, you can inject the serializer and use it explicitly, for example to serialize
9486 * form data for submission:
9489 * .controller(function($http, $httpParamSerializerJQLike) {
9495 * data: $httpParamSerializerJQLike(myData),
9497 * 'Content-Type': 'application/x-www-form-urlencoded'
9505 this.$get = function() {
9506 return function jQueryLikeParamSerializer(params) {
9507 if (!params) return '';
9509 serialize(params, '', true);
9510 return parts.join('&');
9512 function serialize(toSerialize, prefix, topLevel) {
9513 if (toSerialize === null || isUndefined(toSerialize)) return;
9514 if (isArray(toSerialize)) {
9515 forEach(toSerialize, function(value, index) {
9516 serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']');
9518 } else if (isObject(toSerialize) && !isDate(toSerialize)) {
9519 forEachSorted(toSerialize, function(value, key) {
9520 serialize(value, prefix +
9521 (topLevel ? '' : '[') +
9523 (topLevel ? '' : ']'));
9526 parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize)));
9533 function defaultHttpResponseTransform(data, headers) {
9534 if (isString(data)) {
9535 // Strip json vulnerability protection prefix and trim whitespace
9536 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim();
9539 var contentType = headers('Content-Type');
9540 if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) {
9541 data = fromJson(tempData);
9549 function isJsonLike(str) {
9550 var jsonStart = str.match(JSON_START);
9551 return jsonStart && JSON_ENDS[jsonStart[0]].test(str);
9555 * Parse headers into key value object
9557 * @param {string} headers Raw headers as a string
9558 * @returns {Object} Parsed headers as key value object
9560 function parseHeaders(headers) {
9561 var parsed = createMap(), i;
9563 function fillInParsed(key, val) {
9565 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
9569 if (isString(headers)) {
9570 forEach(headers.split('\n'), function(line) {
9571 i = line.indexOf(':');
9572 fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1)));
9574 } else if (isObject(headers)) {
9575 forEach(headers, function(headerVal, headerKey) {
9576 fillInParsed(lowercase(headerKey), trim(headerVal));
9585 * Returns a function that provides access to parsed headers.
9587 * Headers are lazy parsed when first requested.
9590 * @param {(string|Object)} headers Headers to provide access to.
9591 * @returns {function(string=)} Returns a getter function which if called with:
9593 * - if called with single an argument returns a single header value or null
9594 * - if called with no arguments returns an object containing all headers.
9596 function headersGetter(headers) {
9599 return function(name) {
9600 if (!headersObj) headersObj = parseHeaders(headers);
9603 var value = headersObj[lowercase(name)];
9604 if (value === void 0) {
9616 * Chain all given functions
9618 * This function is used for both request and response transforming
9620 * @param {*} data Data to transform.
9621 * @param {function(string=)} headers HTTP headers getter fn.
9622 * @param {number} status HTTP status code of the response.
9623 * @param {(Function|Array.<Function>)} fns Function or an array of functions.
9624 * @returns {*} Transformed data.
9626 function transformData(data, headers, status, fns) {
9627 if (isFunction(fns)) {
9628 return fns(data, headers, status);
9631 forEach(fns, function(fn) {
9632 data = fn(data, headers, status);
9639 function isSuccess(status) {
9640 return 200 <= status && status < 300;
9646 * @name $httpProvider
9648 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service.
9650 function $HttpProvider() {
9653 * @name $httpProvider#defaults
9656 * Object containing default values for all {@link ng.$http $http} requests.
9658 * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`}
9659 * that will provide the cache for all requests who set their `cache` property to `true`.
9660 * If you set the `defaults.cache = false` then only requests that specify their own custom
9661 * cache object will be cached. See {@link $http#caching $http Caching} for more information.
9663 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token.
9664 * Defaults value is `'XSRF-TOKEN'`.
9666 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the
9667 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`.
9669 * - **`defaults.headers`** - {Object} - Default headers for all $http requests.
9670 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on
9671 * setting default headers.
9672 * - **`defaults.headers.common`**
9673 * - **`defaults.headers.post`**
9674 * - **`defaults.headers.put`**
9675 * - **`defaults.headers.patch`**
9678 * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function
9679 * used to the prepare string representation of request parameters (specified as an object).
9680 * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}.
9681 * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}.
9684 var defaults = this.defaults = {
9685 // transform incoming response data
9686 transformResponse: [defaultHttpResponseTransform],
9688 // transform outgoing request data
9689 transformRequest: [function(d) {
9690 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d;
9696 'Accept': 'application/json, text/plain, */*'
9698 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
9699 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
9700 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON)
9703 xsrfCookieName: 'XSRF-TOKEN',
9704 xsrfHeaderName: 'X-XSRF-TOKEN',
9706 paramSerializer: '$httpParamSerializer'
9709 var useApplyAsync = false;
9712 * @name $httpProvider#useApplyAsync
9715 * Configure $http service to combine processing of multiple http responses received at around
9716 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in
9717 * significant performance improvement for bigger applications that make many HTTP requests
9718 * concurrently (common during application bootstrap).
9720 * Defaults to false. If no value is specified, returns the current configured value.
9722 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred
9723 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window
9724 * to load and share the same digest cycle.
9726 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
9727 * otherwise, returns the current configured value.
9729 this.useApplyAsync = function(value) {
9730 if (isDefined(value)) {
9731 useApplyAsync = !!value;
9734 return useApplyAsync;
9737 var useLegacyPromise = true;
9740 * @name $httpProvider#useLegacyPromiseExtensions
9743 * Configure `$http` service to return promises without the shorthand methods `success` and `error`.
9744 * This should be used to make sure that applications work without these methods.
9746 * Defaults to true. If no value is specified, returns the current configured value.
9748 * @param {boolean=} value If true, `$http` will return a promise with the deprecated legacy `success` and `error` methods.
9750 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
9751 * otherwise, returns the current configured value.
9753 this.useLegacyPromiseExtensions = function(value) {
9754 if (isDefined(value)) {
9755 useLegacyPromise = !!value;
9758 return useLegacyPromise;
9763 * @name $httpProvider#interceptors
9766 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http}
9767 * pre-processing of request or postprocessing of responses.
9769 * These service factories are ordered by request, i.e. they are applied in the same order as the
9770 * array, on request, but reverse order, on response.
9772 * {@link ng.$http#interceptors Interceptors detailed info}
9774 var interceptorFactories = this.interceptors = [];
9776 this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector',
9777 function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) {
9779 var defaultCache = $cacheFactory('$http');
9782 * Make sure that default param serializer is exposed as a function
9784 defaults.paramSerializer = isString(defaults.paramSerializer) ?
9785 $injector.get(defaults.paramSerializer) : defaults.paramSerializer;
9788 * Interceptors stored in reverse order. Inner interceptors before outer interceptors.
9789 * The reversal is needed so that we can build up the interception chain around the
9792 var reversedInterceptors = [];
9794 forEach(interceptorFactories, function(interceptorFactory) {
9795 reversedInterceptors.unshift(isString(interceptorFactory)
9796 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory));
9803 * @requires ng.$httpBackend
9804 * @requires $cacheFactory
9805 * @requires $rootScope
9807 * @requires $injector
9810 * The `$http` service is a core Angular service that facilitates communication with the remote
9811 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest)
9812 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP).
9814 * For unit testing applications that use `$http` service, see
9815 * {@link ngMock.$httpBackend $httpBackend mock}.
9817 * For a higher level of abstraction, please check out the {@link ngResource.$resource
9818 * $resource} service.
9820 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by
9821 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage
9822 * it is important to familiarize yourself with these APIs and the guarantees they provide.
9826 * The `$http` service is a function which takes a single argument — a {@link $http#usage configuration object} —
9827 * that is used to generate an HTTP request and returns a {@link ng.$q promise}.
9830 * // Simple GET request example:
9834 * }).then(function successCallback(response) {
9835 * // this callback will be called asynchronously
9836 * // when the response is available
9837 * }, function errorCallback(response) {
9838 * // called asynchronously if an error occurs
9839 * // or server returns response with an error status.
9843 * The response object has these properties:
9845 * - **data** – `{string|Object}` – The response body transformed with the transform
9847 * - **status** – `{number}` – HTTP status code of the response.
9848 * - **headers** – `{function([headerName])}` – Header getter function.
9849 * - **config** – `{Object}` – The configuration object that was used to generate the request.
9850 * - **statusText** – `{string}` – HTTP status text of the response.
9852 * A response status code between 200 and 299 is considered a success status and
9853 * will result in the success callback being called. Note that if the response is a redirect,
9854 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be
9855 * called for such responses.
9858 * ## Shortcut methods
9860 * Shortcut methods are also available. All shortcut methods require passing in the URL, and
9861 * request data must be passed in for POST/PUT requests. An optional config can be passed as the
9865 * $http.get('/someUrl', config).then(successCallback, errorCallback);
9866 * $http.post('/someUrl', data, config).then(successCallback, errorCallback);
9869 * Complete list of shortcut methods:
9871 * - {@link ng.$http#get $http.get}
9872 * - {@link ng.$http#head $http.head}
9873 * - {@link ng.$http#post $http.post}
9874 * - {@link ng.$http#put $http.put}
9875 * - {@link ng.$http#delete $http.delete}
9876 * - {@link ng.$http#jsonp $http.jsonp}
9877 * - {@link ng.$http#patch $http.patch}
9880 * ## Writing Unit Tests that use $http
9881 * When unit testing (using {@link ngMock ngMock}), it is necessary to call
9882 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending
9883 * request using trained responses.
9886 * $httpBackend.expectGET(...);
9888 * $httpBackend.flush();
9891 * ## Deprecation Notice
9892 * <div class="alert alert-danger">
9893 * The `$http` legacy promise methods `success` and `error` have been deprecated.
9894 * Use the standard `then` method instead.
9895 * If {@link $httpProvider#useLegacyPromiseExtensions `$httpProvider.useLegacyPromiseExtensions`} is set to
9896 * `false` then these methods will throw {@link $http:legacy `$http/legacy`} error.
9899 * ## Setting HTTP Headers
9901 * The $http service will automatically add certain HTTP headers to all requests. These defaults
9902 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration
9903 * object, which currently contains this default configuration:
9905 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
9906 * - `Accept: application/json, text/plain, * / *`
9907 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests)
9908 * - `Content-Type: application/json`
9909 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests)
9910 * - `Content-Type: application/json`
9912 * To add or overwrite these defaults, simply add or remove a property from these configuration
9913 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
9914 * with the lowercased HTTP method name as the key, e.g.
9915 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`.
9917 * The defaults can also be set at runtime via the `$http.defaults` object in the same
9918 * fashion. For example:
9921 * module.run(function($http) {
9922 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'
9926 * In addition, you can supply a `headers` property in the config object passed when
9927 * calling `$http(config)`, which overrides the defaults without changing them globally.
9929 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis,
9930 * Use the `headers` property, setting the desired header to `undefined`. For example:
9935 * url: 'http://example.com',
9937 * 'Content-Type': undefined
9939 * data: { test: 'test' }
9942 * $http(req).then(function(){...}, function(){...});
9945 * ## Transforming Requests and Responses
9947 * Both requests and responses can be transformed using transformation functions: `transformRequest`
9948 * and `transformResponse`. These properties can be a single function that returns
9949 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions,
9950 * which allows you to `push` or `unshift` a new transformation function into the transformation chain.
9952 * ### Default Transformations
9954 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and
9955 * `defaults.transformResponse` properties. If a request does not provide its own transformations
9956 * then these will be applied.
9958 * You can augment or replace the default transformations by modifying these properties by adding to or
9959 * replacing the array.
9961 * Angular provides the following default transformations:
9963 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`):
9965 * - If the `data` property of the request configuration object contains an object, serialize it
9968 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`):
9970 * - If XSRF prefix is detected, strip it (see Security Considerations section below).
9971 * - If JSON response is detected, deserialize it using a JSON parser.
9974 * ### Overriding the Default Transformations Per Request
9976 * If you wish override the request/response transformations only for a single request then provide
9977 * `transformRequest` and/or `transformResponse` properties on the configuration object passed
9980 * Note that if you provide these properties on the config object the default transformations will be
9981 * overwritten. If you wish to augment the default transformations then you must include them in your
9982 * local transformation array.
9984 * The following code demonstrates adding a new response transformation to be run after the default response
9985 * transformations have been run.
9988 * function appendTransform(defaults, transform) {
9990 * // We can't guarantee that the default transformation is an array
9991 * defaults = angular.isArray(defaults) ? defaults : [defaults];
9993 * // Append the new transformation to the defaults
9994 * return defaults.concat(transform);
10000 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) {
10001 * return doTransform(value);
10009 * To enable caching, set the request configuration `cache` property to `true` (to use default
10010 * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}).
10011 * When the cache is enabled, `$http` stores the response from the server in the specified
10012 * cache. The next time the same request is made, the response is served from the cache without
10013 * sending a request to the server.
10015 * Note that even if the response is served from cache, delivery of the data is asynchronous in
10016 * the same way that real requests are.
10018 * If there are multiple GET requests for the same URL that should be cached using the same
10019 * cache, but the cache is not populated yet, only one request to the server will be made and
10020 * the remaining requests will be fulfilled using the response from the first request.
10022 * You can change the default cache to a new object (built with
10023 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the
10024 * {@link ng.$http#defaults `$http.defaults.cache`} property. All requests who set
10025 * their `cache` property to `true` will now use this cache object.
10027 * If you set the default cache to `false` then only requests that specify their own custom
10028 * cache object will be cached.
10032 * Before you start creating interceptors, be sure to understand the
10033 * {@link ng.$q $q and deferred/promise APIs}.
10035 * For purposes of global error handling, authentication, or any kind of synchronous or
10036 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be
10037 * able to intercept requests before they are handed to the server and
10038 * responses before they are handed over to the application code that
10039 * initiated these requests. The interceptors leverage the {@link ng.$q
10040 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing.
10042 * The interceptors are service factories that are registered with the `$httpProvider` by
10043 * adding them to the `$httpProvider.interceptors` array. The factory is called and
10044 * injected with dependencies (if specified) and returns the interceptor.
10046 * There are two kinds of interceptors (and two kinds of rejection interceptors):
10048 * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to
10049 * modify the `config` object or create a new one. The function needs to return the `config`
10050 * object directly, or a promise containing the `config` or a new `config` object.
10051 * * `requestError`: interceptor gets called when a previous interceptor threw an error or
10052 * resolved with a rejection.
10053 * * `response`: interceptors get called with http `response` object. The function is free to
10054 * modify the `response` object or create a new one. The function needs to return the `response`
10055 * object directly, or as a promise containing the `response` or a new `response` object.
10056 * * `responseError`: interceptor gets called when a previous interceptor threw an error or
10057 * resolved with a rejection.
10061 * // register the interceptor as a service
10062 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
10064 * // optional method
10065 * 'request': function(config) {
10066 * // do something on success
10070 * // optional method
10071 * 'requestError': function(rejection) {
10072 * // do something on error
10073 * if (canRecover(rejection)) {
10074 * return responseOrNewPromise
10076 * return $q.reject(rejection);
10081 * // optional method
10082 * 'response': function(response) {
10083 * // do something on success
10087 * // optional method
10088 * 'responseError': function(rejection) {
10089 * // do something on error
10090 * if (canRecover(rejection)) {
10091 * return responseOrNewPromise
10093 * return $q.reject(rejection);
10098 * $httpProvider.interceptors.push('myHttpInterceptor');
10101 * // alternatively, register the interceptor via an anonymous factory
10102 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) {
10104 * 'request': function(config) {
10108 * 'response': function(response) {
10115 * ## Security Considerations
10117 * When designing web applications, consider security threats from:
10119 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
10120 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery)
10122 * Both server and the client must cooperate in order to eliminate these threats. Angular comes
10123 * pre-configured with strategies that address these issues, but for this to work backend server
10124 * cooperation is required.
10126 * ### JSON Vulnerability Protection
10128 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
10129 * allows third party website to turn your JSON resource URL into
10130 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To
10131 * counter this your server can prefix all JSON requests with following string `")]}',\n"`.
10132 * Angular will automatically strip the prefix before processing it as JSON.
10134 * For example if your server needs to return:
10139 * which is vulnerable to attack, your server can return:
10145 * Angular will strip the prefix, before processing the JSON.
10148 * ### Cross Site Request Forgery (XSRF) Protection
10150 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which
10151 * an unauthorized site can gain your user's private data. Angular provides a mechanism
10152 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie
10153 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only
10154 * JavaScript that runs on your domain could read the cookie, your server can be assured that
10155 * the XHR came from JavaScript running on your domain. The header will not be set for
10156 * cross-domain requests.
10158 * To take advantage of this, your server needs to set a token in a JavaScript readable session
10159 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the
10160 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure
10161 * that only JavaScript running on your domain could have sent the request. The token must be
10162 * unique for each user and must be verifiable by the server (to prevent the JavaScript from
10163 * making up its own tokens). We recommend that the token is a digest of your site's
10164 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography))
10165 * for added security.
10167 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName
10168 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time,
10169 * or the per-request config object.
10171 * In order to prevent collisions in environments where multiple Angular apps share the
10172 * same domain or subdomain, we recommend that each application uses unique cookie name.
10174 * @param {object} config Object describing the request to be made and how it should be
10175 * processed. The object has following properties:
10177 * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
10178 * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested.
10179 * - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be serialized
10180 * with the `paramSerializer` and appended as GET parameters.
10181 * - **data** – `{string|Object}` – Data to be sent as the request message data.
10182 * - **headers** – `{Object}` – Map of strings or functions which return strings representing
10183 * HTTP headers to send to the server. If the return value of a function is null, the
10184 * header will not be sent. Functions accept a config object as an argument.
10185 * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token.
10186 * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token.
10187 * - **transformRequest** –
10188 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
10189 * transform function or an array of such functions. The transform function takes the http
10190 * request body and headers and returns its transformed (typically serialized) version.
10191 * See {@link ng.$http#overriding-the-default-transformations-per-request
10192 * Overriding the Default Transformations}
10193 * - **transformResponse** –
10194 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` –
10195 * transform function or an array of such functions. The transform function takes the http
10196 * response body, headers and status and returns its transformed (typically deserialized) version.
10197 * See {@link ng.$http#overriding-the-default-transformations-per-request
10198 * Overriding the Default TransformationjqLiks}
10199 * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to
10200 * prepare the string representation of request parameters (specified as an object).
10201 * If specified as string, it is interpreted as function registered with the
10202 * {@link $injector $injector}, which means you can create your own serializer
10203 * by registering it as a {@link auto.$provide#service service}.
10204 * The default serializer is the {@link $httpParamSerializer $httpParamSerializer};
10205 * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike}
10206 * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
10207 * GET request, otherwise if a cache instance built with
10208 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
10210 * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise}
10211 * that should abort the request when resolved.
10212 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the
10213 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials)
10214 * for more information.
10215 * - **responseType** - `{string}` - see
10216 * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype).
10218 * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object
10219 * when the request succeeds or fails.
10222 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending
10223 * requests. This is primarily meant to be used for debugging purposes.
10227 <example module="httpExample">
10228 <file name="index.html">
10229 <div ng-controller="FetchController">
10230 <select ng-model="method" aria-label="Request method">
10231 <option>GET</option>
10232 <option>JSONP</option>
10234 <input type="text" ng-model="url" size="80" aria-label="URL" />
10235 <button id="fetchbtn" ng-click="fetch()">fetch</button><br>
10236 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
10237 <button id="samplejsonpbtn"
10238 ng-click="updateModel('JSONP',
10239 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')">
10242 <button id="invalidjsonpbtn"
10243 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')">
10246 <pre>http status code: {{status}}</pre>
10247 <pre>http response data: {{data}}</pre>
10250 <file name="script.js">
10251 angular.module('httpExample', [])
10252 .controller('FetchController', ['$scope', '$http', '$templateCache',
10253 function($scope, $http, $templateCache) {
10254 $scope.method = 'GET';
10255 $scope.url = 'http-hello.html';
10257 $scope.fetch = function() {
10258 $scope.code = null;
10259 $scope.response = null;
10261 $http({method: $scope.method, url: $scope.url, cache: $templateCache}).
10262 then(function(response) {
10263 $scope.status = response.status;
10264 $scope.data = response.data;
10265 }, function(response) {
10266 $scope.data = response.data || "Request failed";
10267 $scope.status = response.status;
10271 $scope.updateModel = function(method, url) {
10272 $scope.method = method;
10277 <file name="http-hello.html">
10280 <file name="protractor.js" type="protractor">
10281 var status = element(by.binding('status'));
10282 var data = element(by.binding('data'));
10283 var fetchBtn = element(by.id('fetchbtn'));
10284 var sampleGetBtn = element(by.id('samplegetbtn'));
10285 var sampleJsonpBtn = element(by.id('samplejsonpbtn'));
10286 var invalidJsonpBtn = element(by.id('invalidjsonpbtn'));
10288 it('should make an xhr GET request', function() {
10289 sampleGetBtn.click();
10291 expect(status.getText()).toMatch('200');
10292 expect(data.getText()).toMatch(/Hello, \$http!/);
10295 // Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185
10296 // it('should make a JSONP request to angularjs.org', function() {
10297 // sampleJsonpBtn.click();
10298 // fetchBtn.click();
10299 // expect(status.getText()).toMatch('200');
10300 // expect(data.getText()).toMatch(/Super Hero!/);
10303 it('should make JSONP request to invalid URL and invoke the error handler',
10305 invalidJsonpBtn.click();
10307 expect(status.getText()).toMatch('0');
10308 expect(data.getText()).toMatch('Request failed');
10313 function $http(requestConfig) {
10315 if (!angular.isObject(requestConfig)) {
10316 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig);
10319 var config = extend({
10321 transformRequest: defaults.transformRequest,
10322 transformResponse: defaults.transformResponse,
10323 paramSerializer: defaults.paramSerializer
10326 config.headers = mergeHeaders(requestConfig);
10327 config.method = uppercase(config.method);
10328 config.paramSerializer = isString(config.paramSerializer) ?
10329 $injector.get(config.paramSerializer) : config.paramSerializer;
10331 var serverRequest = function(config) {
10332 var headers = config.headers;
10333 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest);
10335 // strip content-type if data is undefined
10336 if (isUndefined(reqData)) {
10337 forEach(headers, function(value, header) {
10338 if (lowercase(header) === 'content-type') {
10339 delete headers[header];
10344 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
10345 config.withCredentials = defaults.withCredentials;
10349 return sendReq(config, reqData).then(transformResponse, transformResponse);
10352 var chain = [serverRequest, undefined];
10353 var promise = $q.when(config);
10355 // apply interceptors
10356 forEach(reversedInterceptors, function(interceptor) {
10357 if (interceptor.request || interceptor.requestError) {
10358 chain.unshift(interceptor.request, interceptor.requestError);
10360 if (interceptor.response || interceptor.responseError) {
10361 chain.push(interceptor.response, interceptor.responseError);
10365 while (chain.length) {
10366 var thenFn = chain.shift();
10367 var rejectFn = chain.shift();
10369 promise = promise.then(thenFn, rejectFn);
10372 if (useLegacyPromise) {
10373 promise.success = function(fn) {
10374 assertArgFn(fn, 'fn');
10376 promise.then(function(response) {
10377 fn(response.data, response.status, response.headers, config);
10382 promise.error = function(fn) {
10383 assertArgFn(fn, 'fn');
10385 promise.then(null, function(response) {
10386 fn(response.data, response.status, response.headers, config);
10391 promise.success = $httpMinErrLegacyFn('success');
10392 promise.error = $httpMinErrLegacyFn('error');
10397 function transformResponse(response) {
10398 // make a copy since the response must be cacheable
10399 var resp = extend({}, response);
10400 resp.data = transformData(response.data, response.headers, response.status,
10401 config.transformResponse);
10402 return (isSuccess(response.status))
10407 function executeHeaderFns(headers, config) {
10408 var headerContent, processedHeaders = {};
10410 forEach(headers, function(headerFn, header) {
10411 if (isFunction(headerFn)) {
10412 headerContent = headerFn(config);
10413 if (headerContent != null) {
10414 processedHeaders[header] = headerContent;
10417 processedHeaders[header] = headerFn;
10421 return processedHeaders;
10424 function mergeHeaders(config) {
10425 var defHeaders = defaults.headers,
10426 reqHeaders = extend({}, config.headers),
10427 defHeaderName, lowercaseDefHeaderName, reqHeaderName;
10429 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]);
10431 // using for-in instead of forEach to avoid unecessary iteration after header has been found
10432 defaultHeadersIteration:
10433 for (defHeaderName in defHeaders) {
10434 lowercaseDefHeaderName = lowercase(defHeaderName);
10436 for (reqHeaderName in reqHeaders) {
10437 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) {
10438 continue defaultHeadersIteration;
10442 reqHeaders[defHeaderName] = defHeaders[defHeaderName];
10445 // execute if header value is a function for merged headers
10446 return executeHeaderFns(reqHeaders, shallowCopy(config));
10450 $http.pendingRequests = [];
10457 * Shortcut method to perform `GET` request.
10459 * @param {string} url Relative or absolute URL specifying the destination of the request
10460 * @param {Object=} config Optional configuration object
10461 * @returns {HttpPromise} Future object
10466 * @name $http#delete
10469 * Shortcut method to perform `DELETE` request.
10471 * @param {string} url Relative or absolute URL specifying the destination of the request
10472 * @param {Object=} config Optional configuration object
10473 * @returns {HttpPromise} Future object
10481 * Shortcut method to perform `HEAD` request.
10483 * @param {string} url Relative or absolute URL specifying the destination of the request
10484 * @param {Object=} config Optional configuration object
10485 * @returns {HttpPromise} Future object
10490 * @name $http#jsonp
10493 * Shortcut method to perform `JSONP` request.
10495 * @param {string} url Relative or absolute URL specifying the destination of the request.
10496 * The name of the callback should be the string `JSON_CALLBACK`.
10497 * @param {Object=} config Optional configuration object
10498 * @returns {HttpPromise} Future object
10500 createShortMethods('get', 'delete', 'head', 'jsonp');
10507 * Shortcut method to perform `POST` request.
10509 * @param {string} url Relative or absolute URL specifying the destination of the request
10510 * @param {*} data Request content
10511 * @param {Object=} config Optional configuration object
10512 * @returns {HttpPromise} Future object
10520 * Shortcut method to perform `PUT` request.
10522 * @param {string} url Relative or absolute URL specifying the destination of the request
10523 * @param {*} data Request content
10524 * @param {Object=} config Optional configuration object
10525 * @returns {HttpPromise} Future object
10530 * @name $http#patch
10533 * Shortcut method to perform `PATCH` request.
10535 * @param {string} url Relative or absolute URL specifying the destination of the request
10536 * @param {*} data Request content
10537 * @param {Object=} config Optional configuration object
10538 * @returns {HttpPromise} Future object
10540 createShortMethodsWithData('post', 'put', 'patch');
10544 * @name $http#defaults
10547 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
10548 * default headers, withCredentials as well as request and response transformations.
10550 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
10552 $http.defaults = defaults;
10558 function createShortMethods(names) {
10559 forEach(arguments, function(name) {
10560 $http[name] = function(url, config) {
10561 return $http(extend({}, config || {}, {
10570 function createShortMethodsWithData(name) {
10571 forEach(arguments, function(name) {
10572 $http[name] = function(url, data, config) {
10573 return $http(extend({}, config || {}, {
10584 * Makes the request.
10586 * !!! ACCESSES CLOSURE VARS:
10587 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests
10589 function sendReq(config, reqData) {
10590 var deferred = $q.defer(),
10591 promise = deferred.promise,
10594 reqHeaders = config.headers,
10595 url = buildUrl(config.url, config.paramSerializer(config.params));
10597 $http.pendingRequests.push(config);
10598 promise.then(removePendingReq, removePendingReq);
10601 if ((config.cache || defaults.cache) && config.cache !== false &&
10602 (config.method === 'GET' || config.method === 'JSONP')) {
10603 cache = isObject(config.cache) ? config.cache
10604 : isObject(defaults.cache) ? defaults.cache
10609 cachedResp = cache.get(url);
10610 if (isDefined(cachedResp)) {
10611 if (isPromiseLike(cachedResp)) {
10612 // cached request has already been sent, but there is no response yet
10613 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult);
10615 // serving from cache
10616 if (isArray(cachedResp)) {
10617 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]);
10619 resolvePromise(cachedResp, 200, {}, 'OK');
10623 // put the promise for the non-transformed response into cache as a placeholder
10624 cache.put(url, promise);
10629 // if we won't have the response in cache, set the xsrf headers and
10630 // send the request to the backend
10631 if (isUndefined(cachedResp)) {
10632 var xsrfValue = urlIsSameOrigin(config.url)
10633 ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName]
10636 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
10639 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
10640 config.withCredentials, config.responseType);
10647 * Callback registered to $httpBackend():
10648 * - caches the response if desired
10649 * - resolves the raw $http promise
10652 function done(status, response, headersString, statusText) {
10654 if (isSuccess(status)) {
10655 cache.put(url, [status, response, parseHeaders(headersString), statusText]);
10657 // remove promise from the cache
10662 function resolveHttpPromise() {
10663 resolvePromise(response, status, headersString, statusText);
10666 if (useApplyAsync) {
10667 $rootScope.$applyAsync(resolveHttpPromise);
10669 resolveHttpPromise();
10670 if (!$rootScope.$$phase) $rootScope.$apply();
10676 * Resolves the raw $http promise.
10678 function resolvePromise(response, status, headers, statusText) {
10679 //status: HTTP response status code, 0, -1 (aborted by timeout / promise)
10680 status = status >= -1 ? status : 0;
10682 (isSuccess(status) ? deferred.resolve : deferred.reject)({
10685 headers: headersGetter(headers),
10687 statusText: statusText
10691 function resolvePromiseWithResult(result) {
10692 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText);
10695 function removePendingReq() {
10696 var idx = $http.pendingRequests.indexOf(config);
10697 if (idx !== -1) $http.pendingRequests.splice(idx, 1);
10702 function buildUrl(url, serializedParams) {
10703 if (serializedParams.length > 0) {
10704 url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams;
10713 * @name $xhrFactory
10716 * Factory function used to create XMLHttpRequest objects.
10718 * Replace or decorate this service to create your own custom XMLHttpRequest objects.
10721 * angular.module('myApp', [])
10722 * .factory('$xhrFactory', function() {
10723 * return function createXhr(method, url) {
10724 * return new window.XMLHttpRequest({mozSystem: true});
10729 * @param {string} method HTTP method of the request (GET, POST, PUT, ..)
10730 * @param {string} url URL of the request.
10732 function $xhrFactoryProvider() {
10733 this.$get = function() {
10734 return function createXhr() {
10735 return new window.XMLHttpRequest();
10742 * @name $httpBackend
10743 * @requires $window
10744 * @requires $document
10745 * @requires $xhrFactory
10748 * HTTP backend used by the {@link ng.$http service} that delegates to
10749 * XMLHttpRequest object or JSONP and deals with browser incompatibilities.
10751 * You should never need to use this service directly, instead use the higher-level abstractions:
10752 * {@link ng.$http $http} or {@link ngResource.$resource $resource}.
10754 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock
10755 * $httpBackend} which can be trained with responses.
10757 function $HttpBackendProvider() {
10758 this.$get = ['$browser', '$window', '$document', '$xhrFactory', function($browser, $window, $document, $xhrFactory) {
10759 return createHttpBackend($browser, $xhrFactory, $browser.defer, $window.angular.callbacks, $document[0]);
10763 function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
10764 // TODO(vojta): fix the signature
10765 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
10766 $browser.$$incOutstandingRequestCount();
10767 url = url || $browser.url();
10769 if (lowercase(method) == 'jsonp') {
10770 var callbackId = '_' + (callbacks.counter++).toString(36);
10771 callbacks[callbackId] = function(data) {
10772 callbacks[callbackId].data = data;
10773 callbacks[callbackId].called = true;
10776 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId),
10777 callbackId, function(status, text) {
10778 completeRequest(callback, status, callbacks[callbackId].data, "", text);
10779 callbacks[callbackId] = noop;
10783 var xhr = createXhr(method, url);
10785 xhr.open(method, url, true);
10786 forEach(headers, function(value, key) {
10787 if (isDefined(value)) {
10788 xhr.setRequestHeader(key, value);
10792 xhr.onload = function requestLoaded() {
10793 var statusText = xhr.statusText || '';
10795 // responseText is the old-school way of retrieving response (supported by IE9)
10796 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
10797 var response = ('response' in xhr) ? xhr.response : xhr.responseText;
10799 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
10800 var status = xhr.status === 1223 ? 204 : xhr.status;
10802 // fix status code when it is 0 (0 status is undocumented).
10803 // Occurs when accessing file resources or on Android 4.1 stock browser
10804 // while retrieving files from application cache.
10805 if (status === 0) {
10806 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0;
10809 completeRequest(callback,
10812 xhr.getAllResponseHeaders(),
10816 var requestError = function() {
10817 // The response is always empty
10818 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
10819 completeRequest(callback, -1, null, null, '');
10822 xhr.onerror = requestError;
10823 xhr.onabort = requestError;
10825 if (withCredentials) {
10826 xhr.withCredentials = true;
10829 if (responseType) {
10831 xhr.responseType = responseType;
10833 // WebKit added support for the json responseType value on 09/03/2013
10834 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are
10835 // known to throw when setting the value "json" as the response type. Other older
10836 // browsers implementing the responseType
10838 // The json response type can be ignored if not supported, because JSON payloads are
10839 // parsed on the client-side regardless.
10840 if (responseType !== 'json') {
10846 xhr.send(isUndefined(post) ? null : post);
10850 var timeoutId = $browserDefer(timeoutRequest, timeout);
10851 } else if (isPromiseLike(timeout)) {
10852 timeout.then(timeoutRequest);
10856 function timeoutRequest() {
10857 jsonpDone && jsonpDone();
10858 xhr && xhr.abort();
10861 function completeRequest(callback, status, response, headersString, statusText) {
10862 // cancel timeout and subsequent timeout promise resolution
10863 if (isDefined(timeoutId)) {
10864 $browserDefer.cancel(timeoutId);
10866 jsonpDone = xhr = null;
10868 callback(status, response, headersString, statusText);
10869 $browser.$$completeOutstandingRequest(noop);
10873 function jsonpReq(url, callbackId, done) {
10874 // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.:
10875 // - fetches local scripts via XHR and evals them
10876 // - adds and immediately removes script elements from the document
10877 var script = rawDocument.createElement('script'), callback = null;
10878 script.type = "text/javascript";
10880 script.async = true;
10882 callback = function(event) {
10883 removeEventListenerFn(script, "load", callback);
10884 removeEventListenerFn(script, "error", callback);
10885 rawDocument.body.removeChild(script);
10888 var text = "unknown";
10891 if (event.type === "load" && !callbacks[callbackId].called) {
10892 event = { type: "error" };
10895 status = event.type === "error" ? 404 : 200;
10899 done(status, text);
10903 addEventListenerFn(script, "load", callback);
10904 addEventListenerFn(script, "error", callback);
10905 rawDocument.body.appendChild(script);
10910 var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate');
10911 $interpolateMinErr.throwNoconcat = function(text) {
10912 throw $interpolateMinErr('noconcat',
10913 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
10914 "interpolations that concatenate multiple expressions when a trusted value is " +
10915 "required. See http://docs.angularjs.org/api/ng.$sce", text);
10918 $interpolateMinErr.interr = function(text, err) {
10919 return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString());
10924 * @name $interpolateProvider
10928 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
10931 <example module="customInterpolationApp">
10932 <file name="index.html">
10934 var customInterpolationApp = angular.module('customInterpolationApp', []);
10936 customInterpolationApp.config(function($interpolateProvider) {
10937 $interpolateProvider.startSymbol('//');
10938 $interpolateProvider.endSymbol('//');
10942 customInterpolationApp.controller('DemoController', function() {
10943 this.label = "This binding is brought you by // interpolation symbols.";
10946 <div ng-app="App" ng-controller="DemoController as demo">
10950 <file name="protractor.js" type="protractor">
10951 it('should interpolate binding with custom symbols', function() {
10952 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
10957 function $InterpolateProvider() {
10958 var startSymbol = '{{';
10959 var endSymbol = '}}';
10963 * @name $interpolateProvider#startSymbol
10965 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
10967 * @param {string=} value new value to set the starting symbol to.
10968 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
10970 this.startSymbol = function(value) {
10972 startSymbol = value;
10975 return startSymbol;
10981 * @name $interpolateProvider#endSymbol
10983 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
10985 * @param {string=} value new value to set the ending symbol to.
10986 * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
10988 this.endSymbol = function(value) {
10998 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
10999 var startSymbolLength = startSymbol.length,
11000 endSymbolLength = endSymbol.length,
11001 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'),
11002 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g');
11004 function escape(ch) {
11005 return '\\\\\\' + ch;
11008 function unescapeText(text) {
11009 return text.replace(escapedStartRegexp, startSymbol).
11010 replace(escapedEndRegexp, endSymbol);
11013 function stringify(value) {
11014 if (value == null) { // null || undefined
11017 switch (typeof value) {
11021 value = '' + value;
11024 value = toJson(value);
11032 * @name $interpolate
11040 * Compiles a string with markup into an interpolation function. This service is used by the
11041 * HTML {@link ng.$compile $compile} service for data binding. See
11042 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
11043 * interpolation markup.
11047 * var $interpolate = ...; // injected
11048 * var exp = $interpolate('Hello {{name | uppercase}}!');
11049 * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!');
11052 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is
11053 * `true`, the interpolation function will return `undefined` unless all embedded expressions
11054 * evaluate to a value other than `undefined`.
11057 * var $interpolate = ...; // injected
11058 * var context = {greeting: 'Hello', name: undefined };
11060 * // default "forgiving" mode
11061 * var exp = $interpolate('{{greeting}} {{name}}!');
11062 * expect(exp(context)).toEqual('Hello !');
11064 * // "allOrNothing" mode
11065 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true);
11066 * expect(exp(context)).toBeUndefined();
11067 * context.name = 'Angular';
11068 * expect(exp(context)).toEqual('Hello Angular!');
11071 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior.
11073 * ####Escaped Interpolation
11074 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers
11075 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash).
11076 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression
11079 * This enables web-servers to prevent script injection attacks and defacing attacks, to some
11080 * degree, while also enabling code examples to work without relying on the
11081 * {@link ng.directive:ngNonBindable ngNonBindable} directive.
11083 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data,
11084 * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all
11085 * interpolation start/end markers with their escaped counterparts.**
11087 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered
11088 * output when the $interpolate service processes the text. So, for HTML elements interpolated
11089 * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter
11090 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such,
11091 * this is typically useful only when user-data is used in rendering a template from the server, or
11092 * when otherwise untrusted data is used by a directive.
11095 * <file name="index.html">
11096 * <div ng-init="username='A user'">
11097 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\}
11099 * <p><strong>{{username}}</strong> attempts to inject code which will deface the
11100 * application, but fails to accomplish their task, because the server has correctly
11101 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash)
11103 * <p>Instead, the result of the attempted script injection is visible, and can be removed
11104 * from the database by an administrator.</p>
11109 * @param {string} text The text with markup to interpolate.
11110 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
11111 * embedded expression in order to return an interpolation function. Strings with no
11112 * embedded expression will return null for the interpolation function.
11113 * @param {string=} trustedContext when provided, the returned function passes the interpolated
11114 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult,
11115 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that
11116 * provides Strict Contextual Escaping for details.
11117 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined
11118 * unless all embedded expressions evaluate to a value other than `undefined`.
11119 * @returns {function(context)} an interpolation function which is used to compute the
11120 * interpolated string. The function has these parameters:
11122 * - `context`: evaluation context for all expressions embedded in the interpolated text
11124 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
11125 allOrNothing = !!allOrNothing;
11131 textLength = text.length,
11134 expressionPositions = [];
11136 while (index < textLength) {
11137 if (((startIndex = text.indexOf(startSymbol, index)) != -1) &&
11138 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) {
11139 if (index !== startIndex) {
11140 concat.push(unescapeText(text.substring(index, startIndex)));
11142 exp = text.substring(startIndex + startSymbolLength, endIndex);
11143 expressions.push(exp);
11144 parseFns.push($parse(exp, parseStringifyInterceptor));
11145 index = endIndex + endSymbolLength;
11146 expressionPositions.push(concat.length);
11149 // we did not find an interpolation, so we have to add the remainder to the separators array
11150 if (index !== textLength) {
11151 concat.push(unescapeText(text.substring(index)));
11157 // Concatenating expressions makes it hard to reason about whether some combination of
11158 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a
11159 // single expression be used for iframe[src], object[src], etc., we ensure that the value
11160 // that's used is assigned or constructed by some JS code somewhere that is more testable or
11161 // make it obvious that you bound the value to some user controlled value. This helps reduce
11162 // the load when auditing for XSS issues.
11163 if (trustedContext && concat.length > 1) {
11164 $interpolateMinErr.throwNoconcat(text);
11167 if (!mustHaveExpression || expressions.length) {
11168 var compute = function(values) {
11169 for (var i = 0, ii = expressions.length; i < ii; i++) {
11170 if (allOrNothing && isUndefined(values[i])) return;
11171 concat[expressionPositions[i]] = values[i];
11173 return concat.join('');
11176 var getValue = function(value) {
11177 return trustedContext ?
11178 $sce.getTrusted(trustedContext, value) :
11179 $sce.valueOf(value);
11182 return extend(function interpolationFn(context) {
11184 var ii = expressions.length;
11185 var values = new Array(ii);
11188 for (; i < ii; i++) {
11189 values[i] = parseFns[i](context);
11192 return compute(values);
11194 $exceptionHandler($interpolateMinErr.interr(text, err));
11198 // all of these properties are undocumented for now
11199 exp: text, //just for compatibility with regular watchers created via $watch
11200 expressions: expressions,
11201 $$watchDelegate: function(scope, listener) {
11203 return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) {
11204 var currValue = compute(values);
11205 if (isFunction(listener)) {
11206 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
11208 lastValue = currValue;
11214 function parseStringifyInterceptor(value) {
11216 value = getValue(value);
11217 return allOrNothing && !isDefined(value) ? value : stringify(value);
11219 $exceptionHandler($interpolateMinErr.interr(text, err));
11227 * @name $interpolate#startSymbol
11229 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
11231 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change
11234 * @returns {string} start symbol.
11236 $interpolate.startSymbol = function() {
11237 return startSymbol;
11243 * @name $interpolate#endSymbol
11245 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
11247 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change
11250 * @returns {string} end symbol.
11252 $interpolate.endSymbol = function() {
11256 return $interpolate;
11260 function $IntervalProvider() {
11261 this.$get = ['$rootScope', '$window', '$q', '$$q',
11262 function($rootScope, $window, $q, $$q) {
11263 var intervals = {};
11271 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay`
11274 * The return value of registering an interval function is a promise. This promise will be
11275 * notified upon each tick of the interval, and will be resolved after `count` iterations, or
11276 * run indefinitely if `count` is not defined. The value of the notification will be the
11277 * number of iterations that have run.
11278 * To cancel an interval, call `$interval.cancel(promise)`.
11280 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to
11281 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that
11284 * <div class="alert alert-warning">
11285 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished
11286 * with them. In particular they are not automatically destroyed when a controller's scope or a
11287 * directive's element are destroyed.
11288 * You should take this into consideration and make sure to always cancel the interval at the
11289 * appropriate moment. See the example below for more details on how and when to do this.
11292 * @param {function()} fn A function that should be called repeatedly.
11293 * @param {number} delay Number of milliseconds between each function call.
11294 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
11296 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
11297 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
11298 * @param {...*=} Pass additional parameters to the executed function.
11299 * @returns {promise} A promise which will be notified on each iteration.
11302 * <example module="intervalExample">
11303 * <file name="index.html">
11305 * angular.module('intervalExample', [])
11306 * .controller('ExampleController', ['$scope', '$interval',
11307 * function($scope, $interval) {
11308 * $scope.format = 'M/d/yy h:mm:ss a';
11309 * $scope.blood_1 = 100;
11310 * $scope.blood_2 = 120;
11313 * $scope.fight = function() {
11314 * // Don't start a new fight if we are already fighting
11315 * if ( angular.isDefined(stop) ) return;
11317 * stop = $interval(function() {
11318 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
11319 * $scope.blood_1 = $scope.blood_1 - 3;
11320 * $scope.blood_2 = $scope.blood_2 - 4;
11322 * $scope.stopFight();
11327 * $scope.stopFight = function() {
11328 * if (angular.isDefined(stop)) {
11329 * $interval.cancel(stop);
11330 * stop = undefined;
11334 * $scope.resetFight = function() {
11335 * $scope.blood_1 = 100;
11336 * $scope.blood_2 = 120;
11339 * $scope.$on('$destroy', function() {
11340 * // Make sure that the interval is destroyed too
11341 * $scope.stopFight();
11344 * // Register the 'myCurrentTime' directive factory method.
11345 * // We inject $interval and dateFilter service since the factory method is DI.
11346 * .directive('myCurrentTime', ['$interval', 'dateFilter',
11347 * function($interval, dateFilter) {
11348 * // return the directive link function. (compile function not needed)
11349 * return function(scope, element, attrs) {
11350 * var format, // date format
11351 * stopTime; // so that we can cancel the time updates
11353 * // used to update the UI
11354 * function updateTime() {
11355 * element.text(dateFilter(new Date(), format));
11358 * // watch the expression, and update the UI on change.
11359 * scope.$watch(attrs.myCurrentTime, function(value) {
11364 * stopTime = $interval(updateTime, 1000);
11366 * // listen on DOM destroy (removal) event, and cancel the next UI update
11367 * // to prevent updating time after the DOM element was removed.
11368 * element.on('$destroy', function() {
11369 * $interval.cancel(stopTime);
11376 * <div ng-controller="ExampleController">
11377 * <label>Date format: <input ng-model="format"></label> <hr/>
11378 * Current time is: <span my-current-time="format"></span>
11380 * Blood 1 : <font color='red'>{{blood_1}}</font>
11381 * Blood 2 : <font color='red'>{{blood_2}}</font>
11382 * <button type="button" data-ng-click="fight()">Fight</button>
11383 * <button type="button" data-ng-click="stopFight()">StopFight</button>
11384 * <button type="button" data-ng-click="resetFight()">resetFight</button>
11391 function interval(fn, delay, count, invokeApply) {
11392 var hasParams = arguments.length > 4,
11393 args = hasParams ? sliceArgs(arguments, 4) : [],
11394 setInterval = $window.setInterval,
11395 clearInterval = $window.clearInterval,
11397 skipApply = (isDefined(invokeApply) && !invokeApply),
11398 deferred = (skipApply ? $$q : $q).defer(),
11399 promise = deferred.promise;
11401 count = isDefined(count) ? count : 0;
11403 promise.then(null, null, (!hasParams) ? fn : function() {
11404 fn.apply(null, args);
11407 promise.$$intervalId = setInterval(function tick() {
11408 deferred.notify(iteration++);
11410 if (count > 0 && iteration >= count) {
11411 deferred.resolve(iteration);
11412 clearInterval(promise.$$intervalId);
11413 delete intervals[promise.$$intervalId];
11416 if (!skipApply) $rootScope.$apply();
11420 intervals[promise.$$intervalId] = deferred;
11428 * @name $interval#cancel
11431 * Cancels a task associated with the `promise`.
11433 * @param {Promise=} promise returned by the `$interval` function.
11434 * @returns {boolean} Returns `true` if the task was successfully canceled.
11436 interval.cancel = function(promise) {
11437 if (promise && promise.$$intervalId in intervals) {
11438 intervals[promise.$$intervalId].reject('canceled');
11439 $window.clearInterval(promise.$$intervalId);
11440 delete intervals[promise.$$intervalId];
11455 * $locale service provides localization rules for various Angular components. As of right now the
11456 * only public api is:
11458 * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
11461 var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/,
11462 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
11463 var $locationMinErr = minErr('$location');
11467 * Encode path using encodeUriSegment, ignoring forward slashes
11469 * @param {string} path Path to encode
11470 * @returns {string}
11472 function encodePath(path) {
11473 var segments = path.split('/'),
11474 i = segments.length;
11477 segments[i] = encodeUriSegment(segments[i]);
11480 return segments.join('/');
11483 function parseAbsoluteUrl(absoluteUrl, locationObj) {
11484 var parsedUrl = urlResolve(absoluteUrl);
11486 locationObj.$$protocol = parsedUrl.protocol;
11487 locationObj.$$host = parsedUrl.hostname;
11488 locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
11492 function parseAppUrl(relativeUrl, locationObj) {
11493 var prefixed = (relativeUrl.charAt(0) !== '/');
11495 relativeUrl = '/' + relativeUrl;
11497 var match = urlResolve(relativeUrl);
11498 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ?
11499 match.pathname.substring(1) : match.pathname);
11500 locationObj.$$search = parseKeyValue(match.search);
11501 locationObj.$$hash = decodeURIComponent(match.hash);
11503 // make sure path starts with '/';
11504 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') {
11505 locationObj.$$path = '/' + locationObj.$$path;
11512 * @param {string} begin
11513 * @param {string} whole
11514 * @returns {string} returns text from whole after begin or undefined if it does not begin with
11517 function beginsWith(begin, whole) {
11518 if (whole.indexOf(begin) === 0) {
11519 return whole.substr(begin.length);
11524 function stripHash(url) {
11525 var index = url.indexOf('#');
11526 return index == -1 ? url : url.substr(0, index);
11529 function trimEmptyHash(url) {
11530 return url.replace(/(#.+)|#$/, '$1');
11534 function stripFile(url) {
11535 return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
11538 /* return the server only (scheme://host:port) */
11539 function serverBase(url) {
11540 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2));
11545 * LocationHtml5Url represents an url
11546 * This object is exposed as $location service when HTML5 mode is enabled and supported
11549 * @param {string} appBase application base URL
11550 * @param {string} appBaseNoFile application base URL stripped of any filename
11551 * @param {string} basePrefix url path prefix
11553 function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
11554 this.$$html5 = true;
11555 basePrefix = basePrefix || '';
11556 parseAbsoluteUrl(appBase, this);
11560 * Parse given html5 (regular) url string into properties
11561 * @param {string} url HTML5 url
11564 this.$$parse = function(url) {
11565 var pathUrl = beginsWith(appBaseNoFile, url);
11566 if (!isString(pathUrl)) {
11567 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url,
11571 parseAppUrl(pathUrl, this);
11573 if (!this.$$path) {
11581 * Compose url and update `absUrl` property
11584 this.$$compose = function() {
11585 var search = toKeyValue(this.$$search),
11586 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11588 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11589 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/'
11592 this.$$parseLinkUrl = function(url, relHref) {
11593 if (relHref && relHref[0] === '#') {
11594 // special case for links to hash fragments:
11595 // keep the old url and only replace the hash fragment
11596 this.hash(relHref.slice(1));
11599 var appUrl, prevAppUrl;
11602 if (isDefined(appUrl = beginsWith(appBase, url))) {
11603 prevAppUrl = appUrl;
11604 if (isDefined(appUrl = beginsWith(basePrefix, appUrl))) {
11605 rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
11607 rewrittenUrl = appBase + prevAppUrl;
11609 } else if (isDefined(appUrl = beginsWith(appBaseNoFile, url))) {
11610 rewrittenUrl = appBaseNoFile + appUrl;
11611 } else if (appBaseNoFile == url + '/') {
11612 rewrittenUrl = appBaseNoFile;
11614 if (rewrittenUrl) {
11615 this.$$parse(rewrittenUrl);
11617 return !!rewrittenUrl;
11623 * LocationHashbangUrl represents url
11624 * This object is exposed as $location service when developer doesn't opt into html5 mode.
11625 * It also serves as the base class for html5 mode fallback on legacy browsers.
11628 * @param {string} appBase application base URL
11629 * @param {string} appBaseNoFile application base URL stripped of any filename
11630 * @param {string} hashPrefix hashbang prefix
11632 function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
11634 parseAbsoluteUrl(appBase, this);
11638 * Parse given hashbang url into properties
11639 * @param {string} url Hashbang url
11642 this.$$parse = function(url) {
11643 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
11644 var withoutHashUrl;
11646 if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') {
11648 // The rest of the url starts with a hash so we have
11649 // got either a hashbang path or a plain hash fragment
11650 withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl);
11651 if (isUndefined(withoutHashUrl)) {
11652 // There was no hashbang prefix so we just have a hash fragment
11653 withoutHashUrl = withoutBaseUrl;
11657 // There was no hashbang path nor hash fragment:
11658 // If we are in HTML5 mode we use what is left as the path;
11659 // Otherwise we ignore what is left
11660 if (this.$$html5) {
11661 withoutHashUrl = withoutBaseUrl;
11663 withoutHashUrl = '';
11664 if (isUndefined(withoutBaseUrl)) {
11671 parseAppUrl(withoutHashUrl, this);
11673 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
11678 * In Windows, on an anchor node on documents loaded from
11679 * the filesystem, the browser will return a pathname
11680 * prefixed with the drive name ('/C:/path') when a
11681 * pathname without a drive is set:
11682 * * a.setAttribute('href', '/foo')
11683 * * a.pathname === '/C:/foo' //true
11685 * Inside of Angular, we're always using pathnames that
11686 * do not include drive names for routing.
11688 function removeWindowsDriveName(path, url, base) {
11690 Matches paths for file protocol on windows,
11691 such as /C:/foo/bar, and captures only /foo/bar.
11693 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/;
11695 var firstPathSegmentMatch;
11697 //Get the relative path from the input URL.
11698 if (url.indexOf(base) === 0) {
11699 url = url.replace(base, '');
11702 // The input URL intentionally contains a first path segment that ends with a colon.
11703 if (windowsFilePathExp.exec(url)) {
11707 firstPathSegmentMatch = windowsFilePathExp.exec(path);
11708 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
11713 * Compose hashbang url and update `absUrl` property
11716 this.$$compose = function() {
11717 var search = toKeyValue(this.$$search),
11718 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11720 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11721 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : '');
11724 this.$$parseLinkUrl = function(url, relHref) {
11725 if (stripHash(appBase) == stripHash(url)) {
11735 * LocationHashbangUrl represents url
11736 * This object is exposed as $location service when html5 history api is enabled but the browser
11737 * does not support it.
11740 * @param {string} appBase application base URL
11741 * @param {string} appBaseNoFile application base URL stripped of any filename
11742 * @param {string} hashPrefix hashbang prefix
11744 function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
11745 this.$$html5 = true;
11746 LocationHashbangUrl.apply(this, arguments);
11748 this.$$parseLinkUrl = function(url, relHref) {
11749 if (relHref && relHref[0] === '#') {
11750 // special case for links to hash fragments:
11751 // keep the old url and only replace the hash fragment
11752 this.hash(relHref.slice(1));
11759 if (appBase == stripHash(url)) {
11760 rewrittenUrl = url;
11761 } else if ((appUrl = beginsWith(appBaseNoFile, url))) {
11762 rewrittenUrl = appBase + hashPrefix + appUrl;
11763 } else if (appBaseNoFile === url + '/') {
11764 rewrittenUrl = appBaseNoFile;
11766 if (rewrittenUrl) {
11767 this.$$parse(rewrittenUrl);
11769 return !!rewrittenUrl;
11772 this.$$compose = function() {
11773 var search = toKeyValue(this.$$search),
11774 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
11776 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
11777 // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#'
11778 this.$$absUrl = appBase + hashPrefix + this.$$url;
11784 var locationPrototype = {
11787 * Are we in html5 mode?
11793 * Has any change been replacing?
11800 * @name $location#absUrl
11803 * This method is getter only.
11805 * Return full url representation with all segments encoded according to rules specified in
11806 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
11810 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11811 * var absUrl = $location.absUrl();
11812 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
11815 * @return {string} full url
11817 absUrl: locationGetter('$$absUrl'),
11821 * @name $location#url
11824 * This method is getter / setter.
11826 * Return url (e.g. `/path?a=b#hash`) when called without any parameter.
11828 * Change path, search and hash, when called with parameter and return `$location`.
11832 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11833 * var url = $location.url();
11834 * // => "/some/path?foo=bar&baz=xoxo"
11837 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
11838 * @return {string} url
11840 url: function(url) {
11841 if (isUndefined(url)) {
11845 var match = PATH_MATCH.exec(url);
11846 if (match[1] || url === '') this.path(decodeURIComponent(match[1]));
11847 if (match[2] || match[1] || url === '') this.search(match[3] || '');
11848 this.hash(match[5] || '');
11855 * @name $location#protocol
11858 * This method is getter only.
11860 * Return protocol of current url.
11864 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11865 * var protocol = $location.protocol();
11869 * @return {string} protocol of current url
11871 protocol: locationGetter('$$protocol'),
11875 * @name $location#host
11878 * This method is getter only.
11880 * Return host of current url.
11882 * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.
11886 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11887 * var host = $location.host();
11888 * // => "example.com"
11890 * // given url http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
11891 * host = $location.host();
11892 * // => "example.com"
11893 * host = location.host;
11894 * // => "example.com:8080"
11897 * @return {string} host of current url.
11899 host: locationGetter('$$host'),
11903 * @name $location#port
11906 * This method is getter only.
11908 * Return port of current url.
11912 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11913 * var port = $location.port();
11917 * @return {Number} port
11919 port: locationGetter('$$port'),
11923 * @name $location#path
11926 * This method is getter / setter.
11928 * Return path of current url when called without any parameter.
11930 * Change path when called with parameter and return `$location`.
11932 * Note: Path should always begin with forward slash (/), this method will add the forward slash
11933 * if it is missing.
11937 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11938 * var path = $location.path();
11939 * // => "/some/path"
11942 * @param {(string|number)=} path New path
11943 * @return {string} path
11945 path: locationGetterSetter('$$path', function(path) {
11946 path = path !== null ? path.toString() : '';
11947 return path.charAt(0) == '/' ? path : '/' + path;
11952 * @name $location#search
11955 * This method is getter / setter.
11957 * Return search part (as object) of current url when called without any parameter.
11959 * Change search part when called with parameter and return `$location`.
11963 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
11964 * var searchObject = $location.search();
11965 * // => {foo: 'bar', baz: 'xoxo'}
11967 * // set foo to 'yipee'
11968 * $location.search('foo', 'yipee');
11969 * // $location.search() => {foo: 'yipee', baz: 'xoxo'}
11972 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or
11975 * When called with a single argument the method acts as a setter, setting the `search` component
11976 * of `$location` to the specified value.
11978 * If the argument is a hash object containing an array of values, these values will be encoded
11979 * as duplicate search parameters in the url.
11981 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue`
11982 * will override only a single search property.
11984 * If `paramValue` is an array, it will override the property of the `search` component of
11985 * `$location` specified via the first argument.
11987 * If `paramValue` is `null`, the property specified via the first argument will be deleted.
11989 * If `paramValue` is `true`, the property specified via the first argument will be added with no
11990 * value nor trailing equal sign.
11992 * @return {Object} If called with no arguments returns the parsed `search` object. If called with
11993 * one or more arguments returns `$location` object itself.
11995 search: function(search, paramValue) {
11996 switch (arguments.length) {
11998 return this.$$search;
12000 if (isString(search) || isNumber(search)) {
12001 search = search.toString();
12002 this.$$search = parseKeyValue(search);
12003 } else if (isObject(search)) {
12004 search = copy(search, {});
12005 // remove object undefined or null properties
12006 forEach(search, function(value, key) {
12007 if (value == null) delete search[key];
12010 this.$$search = search;
12012 throw $locationMinErr('isrcharg',
12013 'The first argument of the `$location#search()` call must be a string or an object.');
12017 if (isUndefined(paramValue) || paramValue === null) {
12018 delete this.$$search[search];
12020 this.$$search[search] = paramValue;
12030 * @name $location#hash
12033 * This method is getter / setter.
12035 * Returns the hash fragment when called without any parameters.
12037 * Changes the hash fragment when called with a parameter and returns `$location`.
12041 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
12042 * var hash = $location.hash();
12043 * // => "hashValue"
12046 * @param {(string|number)=} hash New hash fragment
12047 * @return {string} hash
12049 hash: locationGetterSetter('$$hash', function(hash) {
12050 return hash !== null ? hash.toString() : '';
12055 * @name $location#replace
12058 * If called, all changes to $location during the current `$digest` will replace the current history
12059 * record, instead of adding a new one.
12061 replace: function() {
12062 this.$$replace = true;
12067 forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) {
12068 Location.prototype = Object.create(locationPrototype);
12072 * @name $location#state
12075 * This method is getter / setter.
12077 * Return the history state object when called without any parameter.
12079 * Change the history state object when called with one parameter and return `$location`.
12080 * The state object is later passed to `pushState` or `replaceState`.
12082 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting
12083 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
12084 * older browsers (like IE9 or Android < 4.0), don't use this method.
12086 * @param {object=} state State object for pushState or replaceState
12087 * @return {object} state
12089 Location.prototype.state = function(state) {
12090 if (!arguments.length) {
12091 return this.$$state;
12094 if (Location !== LocationHtml5Url || !this.$$html5) {
12095 throw $locationMinErr('nostate', 'History API state support is available only ' +
12096 'in HTML5 mode and only in browsers supporting HTML5 History API');
12098 // The user might modify `stateObject` after invoking `$location.state(stateObject)`
12099 // but we're changing the $$state reference to $browser.state() during the $digest
12100 // so the modification window is narrow.
12101 this.$$state = isUndefined(state) ? null : state;
12108 function locationGetter(property) {
12109 return function() {
12110 return this[property];
12115 function locationGetterSetter(property, preprocess) {
12116 return function(value) {
12117 if (isUndefined(value)) {
12118 return this[property];
12121 this[property] = preprocess(value);
12133 * @requires $rootElement
12136 * The $location service parses the URL in the browser address bar (based on the
12137 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL
12138 * available to your application. Changes to the URL in the address bar are reflected into
12139 * $location service and changes to $location are reflected into the browser address bar.
12141 * **The $location service:**
12143 * - Exposes the current URL in the browser address bar, so you can
12144 * - Watch and observe the URL.
12145 * - Change the URL.
12146 * - Synchronizes the URL with the browser when the user
12147 * - Changes the address bar.
12148 * - Clicks the back or forward button (or clicks a History link).
12149 * - Clicks on a link.
12150 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
12152 * For more information see {@link guide/$location Developer Guide: Using $location}
12157 * @name $locationProvider
12159 * Use the `$locationProvider` to configure how the application deep linking paths are stored.
12161 function $LocationProvider() {
12162 var hashPrefix = '',
12171 * @name $locationProvider#hashPrefix
12173 * @param {string=} prefix Prefix for hash part (containing path and search)
12174 * @returns {*} current value if used as getter or itself (chaining) if used as setter
12176 this.hashPrefix = function(prefix) {
12177 if (isDefined(prefix)) {
12178 hashPrefix = prefix;
12187 * @name $locationProvider#html5Mode
12189 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value.
12190 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported
12192 * - **enabled** – `{boolean}` – (default: false) If true, will rely on `history.pushState` to
12193 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not
12194 * support `pushState`.
12195 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies
12196 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are
12197 * true, and a base tag is not present, an error will be thrown when `$location` is injected.
12198 * See the {@link guide/$location $location guide for more information}
12199 * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled,
12200 * enables/disables url rewriting for relative links.
12202 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
12204 this.html5Mode = function(mode) {
12205 if (isBoolean(mode)) {
12206 html5Mode.enabled = mode;
12208 } else if (isObject(mode)) {
12210 if (isBoolean(mode.enabled)) {
12211 html5Mode.enabled = mode.enabled;
12214 if (isBoolean(mode.requireBase)) {
12215 html5Mode.requireBase = mode.requireBase;
12218 if (isBoolean(mode.rewriteLinks)) {
12219 html5Mode.rewriteLinks = mode.rewriteLinks;
12230 * @name $location#$locationChangeStart
12231 * @eventType broadcast on root scope
12233 * Broadcasted before a URL will change.
12235 * This change can be prevented by calling
12236 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
12237 * details about event object. Upon successful change
12238 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired.
12240 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
12241 * the browser supports the HTML5 History API.
12243 * @param {Object} angularEvent Synthetic event object.
12244 * @param {string} newUrl New URL
12245 * @param {string=} oldUrl URL that was before it was changed.
12246 * @param {string=} newState New history state object
12247 * @param {string=} oldState History state object that was before it was changed.
12252 * @name $location#$locationChangeSuccess
12253 * @eventType broadcast on root scope
12255 * Broadcasted after a URL was changed.
12257 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
12258 * the browser supports the HTML5 History API.
12260 * @param {Object} angularEvent Synthetic event object.
12261 * @param {string} newUrl New URL
12262 * @param {string=} oldUrl URL that was before it was changed.
12263 * @param {string=} newState New history state object
12264 * @param {string=} oldState History state object that was before it was changed.
12267 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window',
12268 function($rootScope, $browser, $sniffer, $rootElement, $window) {
12271 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to ''
12272 initialUrl = $browser.url(),
12275 if (html5Mode.enabled) {
12276 if (!baseHref && html5Mode.requireBase) {
12277 throw $locationMinErr('nobase',
12278 "$location in HTML5 mode requires a <base> tag to be present!");
12280 appBase = serverBase(initialUrl) + (baseHref || '/');
12281 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url;
12283 appBase = stripHash(initialUrl);
12284 LocationMode = LocationHashbangUrl;
12286 var appBaseNoFile = stripFile(appBase);
12288 $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix);
12289 $location.$$parseLinkUrl(initialUrl, initialUrl);
12291 $location.$$state = $browser.state();
12293 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
12295 function setBrowserUrlWithFallback(url, replace, state) {
12296 var oldUrl = $location.url();
12297 var oldState = $location.$$state;
12299 $browser.url(url, replace, state);
12301 // Make sure $location.state() returns referentially identical (not just deeply equal)
12302 // state object; this makes possible quick checking if the state changed in the digest
12303 // loop. Checking deep equality would be too expensive.
12304 $location.$$state = $browser.state();
12306 // Restore old values if pushState fails
12307 $location.url(oldUrl);
12308 $location.$$state = oldState;
12314 $rootElement.on('click', function(event) {
12315 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
12316 // currently we open nice url link and redirect then
12318 if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return;
12320 var elm = jqLite(event.target);
12322 // traverse the DOM up to find first A tag
12323 while (nodeName_(elm[0]) !== 'a') {
12324 // ignore rewriting if no A tag (reached root element, or no parent - removed from document)
12325 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return;
12328 var absHref = elm.prop('href');
12329 // get the actual href attribute - see
12330 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
12331 var relHref = elm.attr('href') || elm.attr('xlink:href');
12333 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') {
12334 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during
12336 absHref = urlResolve(absHref.animVal).href;
12339 // Ignore when url is started with javascript: or mailto:
12340 if (IGNORE_URI_REGEXP.test(absHref)) return;
12342 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) {
12343 if ($location.$$parseLinkUrl(absHref, relHref)) {
12344 // We do a preventDefault for all urls that are part of the angular application,
12345 // in html5mode and also without, so that we are able to abort navigation without
12346 // getting double entries in the location history.
12347 event.preventDefault();
12348 // update location manually
12349 if ($location.absUrl() != $browser.url()) {
12350 $rootScope.$apply();
12351 // hack to work around FF6 bug 684208 when scenario runner clicks on links
12352 $window.angular['ff-684208-preventDefault'] = true;
12359 // rewrite hashbang url <> html5 url
12360 if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) {
12361 $browser.url($location.absUrl(), true);
12364 var initializing = true;
12366 // update $location when $browser url changes
12367 $browser.onUrlChange(function(newUrl, newState) {
12369 if (isUndefined(beginsWith(appBaseNoFile, newUrl))) {
12370 // If we are navigating outside of the app then force a reload
12371 $window.location.href = newUrl;
12375 $rootScope.$evalAsync(function() {
12376 var oldUrl = $location.absUrl();
12377 var oldState = $location.$$state;
12378 var defaultPrevented;
12379 newUrl = trimEmptyHash(newUrl);
12380 $location.$$parse(newUrl);
12381 $location.$$state = newState;
12383 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
12384 newState, oldState).defaultPrevented;
12386 // if the location was changed by a `$locationChangeStart` handler then stop
12387 // processing this location change
12388 if ($location.absUrl() !== newUrl) return;
12390 if (defaultPrevented) {
12391 $location.$$parse(oldUrl);
12392 $location.$$state = oldState;
12393 setBrowserUrlWithFallback(oldUrl, false, oldState);
12395 initializing = false;
12396 afterLocationChange(oldUrl, oldState);
12399 if (!$rootScope.$$phase) $rootScope.$digest();
12403 $rootScope.$watch(function $locationWatch() {
12404 var oldUrl = trimEmptyHash($browser.url());
12405 var newUrl = trimEmptyHash($location.absUrl());
12406 var oldState = $browser.state();
12407 var currentReplace = $location.$$replace;
12408 var urlOrStateChanged = oldUrl !== newUrl ||
12409 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
12411 if (initializing || urlOrStateChanged) {
12412 initializing = false;
12414 $rootScope.$evalAsync(function() {
12415 var newUrl = $location.absUrl();
12416 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
12417 $location.$$state, oldState).defaultPrevented;
12419 // if the location was changed by a `$locationChangeStart` handler then stop
12420 // processing this location change
12421 if ($location.absUrl() !== newUrl) return;
12423 if (defaultPrevented) {
12424 $location.$$parse(oldUrl);
12425 $location.$$state = oldState;
12427 if (urlOrStateChanged) {
12428 setBrowserUrlWithFallback(newUrl, currentReplace,
12429 oldState === $location.$$state ? null : $location.$$state);
12431 afterLocationChange(oldUrl, oldState);
12436 $location.$$replace = false;
12438 // we don't need to return anything because $evalAsync will make the digest loop dirty when
12439 // there is a change
12444 function afterLocationChange(oldUrl, oldState) {
12445 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl,
12446 $location.$$state, oldState);
12454 * @requires $window
12457 * Simple service for logging. Default implementation safely writes the message
12458 * into the browser's console (if present).
12460 * The main purpose of this service is to simplify debugging and troubleshooting.
12462 * The default is to log `debug` messages. You can use
12463 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this.
12466 <example module="logExample">
12467 <file name="script.js">
12468 angular.module('logExample', [])
12469 .controller('LogController', ['$scope', '$log', function($scope, $log) {
12470 $scope.$log = $log;
12471 $scope.message = 'Hello World!';
12474 <file name="index.html">
12475 <div ng-controller="LogController">
12476 <p>Reload this page with open console, enter text and hit the log button...</p>
12478 <input type="text" ng-model="message" /></label>
12479 <button ng-click="$log.log(message)">log</button>
12480 <button ng-click="$log.warn(message)">warn</button>
12481 <button ng-click="$log.info(message)">info</button>
12482 <button ng-click="$log.error(message)">error</button>
12483 <button ng-click="$log.debug(message)">debug</button>
12491 * @name $logProvider
12493 * Use the `$logProvider` to configure how the application logs messages
12495 function $LogProvider() {
12501 * @name $logProvider#debugEnabled
12503 * @param {boolean=} flag enable or disable debug level messages
12504 * @returns {*} current value if used as getter or itself (chaining) if used as setter
12506 this.debugEnabled = function(flag) {
12507 if (isDefined(flag)) {
12515 this.$get = ['$window', function($window) {
12522 * Write a log message
12524 log: consoleLog('log'),
12531 * Write an information message
12533 info: consoleLog('info'),
12540 * Write a warning message
12542 warn: consoleLog('warn'),
12549 * Write an error message
12551 error: consoleLog('error'),
12558 * Write a debug message
12560 debug: (function() {
12561 var fn = consoleLog('debug');
12563 return function() {
12565 fn.apply(self, arguments);
12571 function formatError(arg) {
12572 if (arg instanceof Error) {
12574 arg = (arg.message && arg.stack.indexOf(arg.message) === -1)
12575 ? 'Error: ' + arg.message + '\n' + arg.stack
12577 } else if (arg.sourceURL) {
12578 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
12584 function consoleLog(type) {
12585 var console = $window.console || {},
12586 logFn = console[type] || console.log || noop,
12589 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode.
12590 // The reason behind this is that console.log has type "object" in IE8...
12592 hasApply = !!logFn.apply;
12596 return function() {
12598 forEach(arguments, function(arg) {
12599 args.push(formatError(arg));
12601 return logFn.apply(console, args);
12605 // we are IE which either doesn't have window.console => this is noop and we do nothing,
12606 // or we are IE where console.log doesn't have apply so we log at least first 2 args
12607 return function(arg1, arg2) {
12608 logFn(arg1, arg2 == null ? '' : arg2);
12614 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
12615 * Any commits to this file should be reviewed with security in mind. *
12616 * Changes to this file can potentially create security vulnerabilities. *
12617 * An approval from 2 Core members with history of modifying *
12618 * this file is required. *
12620 * Does the change somehow allow for arbitrary javascript to be executed? *
12621 * Or allows for someone to change the prototype of built-in objects? *
12622 * Or gives undesired access to variables likes document or window? *
12623 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
12625 var $parseMinErr = minErr('$parse');
12627 // Sandboxing Angular Expressions
12628 // ------------------------------
12629 // Angular expressions are generally considered safe because these expressions only have direct
12630 // access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by
12631 // obtaining a reference to native JS functions such as the Function constructor.
12633 // As an example, consider the following Angular expression:
12635 // {}.toString.constructor('alert("evil JS code")')
12637 // This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits
12638 // against the expression language, but not to prevent exploits that were enabled by exposing
12639 // sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good
12640 // practice and therefore we are not even trying to protect against interaction with an object
12641 // explicitly exposed in this way.
12643 // In general, it is not possible to access a Window object from an angular expression unless a
12644 // window or some DOM object that has a reference to window is published onto a Scope.
12645 // Similarly we prevent invocations of function known to be dangerous, as well as assignments to
12648 // See https://docs.angularjs.org/guide/security
12651 function ensureSafeMemberName(name, fullExpression) {
12652 if (name === "__defineGetter__" || name === "__defineSetter__"
12653 || name === "__lookupGetter__" || name === "__lookupSetter__"
12654 || name === "__proto__") {
12655 throw $parseMinErr('isecfld',
12656 'Attempting to access a disallowed field in Angular expressions! '
12657 + 'Expression: {0}', fullExpression);
12662 function getStringValue(name, fullExpression) {
12663 // From the JavaScript docs:
12664 // Property names must be strings. This means that non-string objects cannot be used
12665 // as keys in an object. Any non-string object, including a number, is typecasted
12666 // into a string via the toString method.
12668 // So, to ensure that we are checking the same `name` that JavaScript would use,
12669 // we cast it to a string, if possible.
12670 // Doing `name + ''` can cause a repl error if the result to `toString` is not a string,
12671 // this is, this will handle objects that misbehave.
12673 if (!isString(name)) {
12674 throw $parseMinErr('iseccst',
12675 'Cannot convert object to primitive value! '
12676 + 'Expression: {0}', fullExpression);
12681 function ensureSafeObject(obj, fullExpression) {
12682 // nifty check if obj is Function that is fast and works across iframes and other contexts
12684 if (obj.constructor === obj) {
12685 throw $parseMinErr('isecfn',
12686 'Referencing Function in Angular expressions is disallowed! Expression: {0}',
12688 } else if (// isWindow(obj)
12689 obj.window === obj) {
12690 throw $parseMinErr('isecwindow',
12691 'Referencing the Window in Angular expressions is disallowed! Expression: {0}',
12693 } else if (// isElement(obj)
12694 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) {
12695 throw $parseMinErr('isecdom',
12696 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}',
12698 } else if (// block Object so that we can't get hold of dangerous Object.* methods
12700 throw $parseMinErr('isecobj',
12701 'Referencing Object in Angular expressions is disallowed! Expression: {0}',
12708 var CALL = Function.prototype.call;
12709 var APPLY = Function.prototype.apply;
12710 var BIND = Function.prototype.bind;
12712 function ensureSafeFunction(obj, fullExpression) {
12714 if (obj.constructor === obj) {
12715 throw $parseMinErr('isecfn',
12716 'Referencing Function in Angular expressions is disallowed! Expression: {0}',
12718 } else if (obj === CALL || obj === APPLY || obj === BIND) {
12719 throw $parseMinErr('isecff',
12720 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}',
12726 function ensureSafeAssignContext(obj, fullExpression) {
12728 if (obj === (0).constructor || obj === (false).constructor || obj === ''.constructor ||
12729 obj === {}.constructor || obj === [].constructor || obj === Function.constructor) {
12730 throw $parseMinErr('isecaf',
12731 'Assigning to a constructor is disallowed! Expression: {0}', fullExpression);
12736 var OPERATORS = createMap();
12737 forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; });
12738 var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
12741 /////////////////////////////////////////
12747 var Lexer = function(options) {
12748 this.options = options;
12751 Lexer.prototype = {
12752 constructor: Lexer,
12754 lex: function(text) {
12759 while (this.index < this.text.length) {
12760 var ch = this.text.charAt(this.index);
12761 if (ch === '"' || ch === "'") {
12762 this.readString(ch);
12763 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {
12765 } else if (this.isIdent(ch)) {
12767 } else if (this.is(ch, '(){}[].,;:?')) {
12768 this.tokens.push({index: this.index, text: ch});
12770 } else if (this.isWhitespace(ch)) {
12773 var ch2 = ch + this.peek();
12774 var ch3 = ch2 + this.peek(2);
12775 var op1 = OPERATORS[ch];
12776 var op2 = OPERATORS[ch2];
12777 var op3 = OPERATORS[ch3];
12778 if (op1 || op2 || op3) {
12779 var token = op3 ? ch3 : (op2 ? ch2 : ch);
12780 this.tokens.push({index: this.index, text: token, operator: true});
12781 this.index += token.length;
12783 this.throwError('Unexpected next character ', this.index, this.index + 1);
12787 return this.tokens;
12790 is: function(ch, chars) {
12791 return chars.indexOf(ch) !== -1;
12794 peek: function(i) {
12796 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false;
12799 isNumber: function(ch) {
12800 return ('0' <= ch && ch <= '9') && typeof ch === "string";
12803 isWhitespace: function(ch) {
12804 // IE treats non-breaking space as \u00A0
12805 return (ch === ' ' || ch === '\r' || ch === '\t' ||
12806 ch === '\n' || ch === '\v' || ch === '\u00A0');
12809 isIdent: function(ch) {
12810 return ('a' <= ch && ch <= 'z' ||
12811 'A' <= ch && ch <= 'Z' ||
12812 '_' === ch || ch === '$');
12815 isExpOperator: function(ch) {
12816 return (ch === '-' || ch === '+' || this.isNumber(ch));
12819 throwError: function(error, start, end) {
12820 end = end || this.index;
12821 var colStr = (isDefined(start)
12822 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']'
12824 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].',
12825 error, colStr, this.text);
12828 readNumber: function() {
12830 var start = this.index;
12831 while (this.index < this.text.length) {
12832 var ch = lowercase(this.text.charAt(this.index));
12833 if (ch == '.' || this.isNumber(ch)) {
12836 var peekCh = this.peek();
12837 if (ch == 'e' && this.isExpOperator(peekCh)) {
12839 } else if (this.isExpOperator(ch) &&
12840 peekCh && this.isNumber(peekCh) &&
12841 number.charAt(number.length - 1) == 'e') {
12843 } else if (this.isExpOperator(ch) &&
12844 (!peekCh || !this.isNumber(peekCh)) &&
12845 number.charAt(number.length - 1) == 'e') {
12846 this.throwError('Invalid exponent');
12857 value: Number(number)
12861 readIdent: function() {
12862 var start = this.index;
12863 while (this.index < this.text.length) {
12864 var ch = this.text.charAt(this.index);
12865 if (!(this.isIdent(ch) || this.isNumber(ch))) {
12872 text: this.text.slice(start, this.index),
12877 readString: function(quote) {
12878 var start = this.index;
12881 var rawString = quote;
12882 var escape = false;
12883 while (this.index < this.text.length) {
12884 var ch = this.text.charAt(this.index);
12888 var hex = this.text.substring(this.index + 1, this.index + 5);
12889 if (!hex.match(/[\da-f]{4}/i)) {
12890 this.throwError('Invalid unicode escape [\\u' + hex + ']');
12893 string += String.fromCharCode(parseInt(hex, 16));
12895 var rep = ESCAPE[ch];
12896 string = string + (rep || ch);
12899 } else if (ch === '\\') {
12901 } else if (ch === quote) {
12915 this.throwError('Unterminated quote', start);
12919 var AST = function(lexer, options) {
12920 this.lexer = lexer;
12921 this.options = options;
12924 AST.Program = 'Program';
12925 AST.ExpressionStatement = 'ExpressionStatement';
12926 AST.AssignmentExpression = 'AssignmentExpression';
12927 AST.ConditionalExpression = 'ConditionalExpression';
12928 AST.LogicalExpression = 'LogicalExpression';
12929 AST.BinaryExpression = 'BinaryExpression';
12930 AST.UnaryExpression = 'UnaryExpression';
12931 AST.CallExpression = 'CallExpression';
12932 AST.MemberExpression = 'MemberExpression';
12933 AST.Identifier = 'Identifier';
12934 AST.Literal = 'Literal';
12935 AST.ArrayExpression = 'ArrayExpression';
12936 AST.Property = 'Property';
12937 AST.ObjectExpression = 'ObjectExpression';
12938 AST.ThisExpression = 'ThisExpression';
12940 // Internal use only
12941 AST.NGValueParameter = 'NGValueParameter';
12944 ast: function(text) {
12946 this.tokens = this.lexer.lex(text);
12948 var value = this.program();
12950 if (this.tokens.length !== 0) {
12951 this.throwError('is an unexpected token', this.tokens[0]);
12957 program: function() {
12960 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']'))
12961 body.push(this.expressionStatement());
12962 if (!this.expect(';')) {
12963 return { type: AST.Program, body: body};
12968 expressionStatement: function() {
12969 return { type: AST.ExpressionStatement, expression: this.filterChain() };
12972 filterChain: function() {
12973 var left = this.expression();
12975 while ((token = this.expect('|'))) {
12976 left = this.filter(left);
12981 expression: function() {
12982 return this.assignment();
12985 assignment: function() {
12986 var result = this.ternary();
12987 if (this.expect('=')) {
12988 result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='};
12993 ternary: function() {
12994 var test = this.logicalOR();
12997 if (this.expect('?')) {
12998 alternate = this.expression();
12999 if (this.consume(':')) {
13000 consequent = this.expression();
13001 return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent};
13007 logicalOR: function() {
13008 var left = this.logicalAND();
13009 while (this.expect('||')) {
13010 left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() };
13015 logicalAND: function() {
13016 var left = this.equality();
13017 while (this.expect('&&')) {
13018 left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()};
13023 equality: function() {
13024 var left = this.relational();
13026 while ((token = this.expect('==','!=','===','!=='))) {
13027 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() };
13032 relational: function() {
13033 var left = this.additive();
13035 while ((token = this.expect('<', '>', '<=', '>='))) {
13036 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() };
13041 additive: function() {
13042 var left = this.multiplicative();
13044 while ((token = this.expect('+','-'))) {
13045 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() };
13050 multiplicative: function() {
13051 var left = this.unary();
13053 while ((token = this.expect('*','/','%'))) {
13054 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() };
13059 unary: function() {
13061 if ((token = this.expect('+', '-', '!'))) {
13062 return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() };
13064 return this.primary();
13068 primary: function() {
13070 if (this.expect('(')) {
13071 primary = this.filterChain();
13073 } else if (this.expect('[')) {
13074 primary = this.arrayDeclaration();
13075 } else if (this.expect('{')) {
13076 primary = this.object();
13077 } else if (this.constants.hasOwnProperty(this.peek().text)) {
13078 primary = copy(this.constants[this.consume().text]);
13079 } else if (this.peek().identifier) {
13080 primary = this.identifier();
13081 } else if (this.peek().constant) {
13082 primary = this.constant();
13084 this.throwError('not a primary expression', this.peek());
13088 while ((next = this.expect('(', '[', '.'))) {
13089 if (next.text === '(') {
13090 primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() };
13092 } else if (next.text === '[') {
13093 primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true };
13095 } else if (next.text === '.') {
13096 primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false };
13098 this.throwError('IMPOSSIBLE');
13104 filter: function(baseExpression) {
13105 var args = [baseExpression];
13106 var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true};
13108 while (this.expect(':')) {
13109 args.push(this.expression());
13115 parseArguments: function() {
13117 if (this.peekToken().text !== ')') {
13119 args.push(this.expression());
13120 } while (this.expect(','));
13125 identifier: function() {
13126 var token = this.consume();
13127 if (!token.identifier) {
13128 this.throwError('is not a valid identifier', token);
13130 return { type: AST.Identifier, name: token.text };
13133 constant: function() {
13134 // TODO check that it is a constant
13135 return { type: AST.Literal, value: this.consume().value };
13138 arrayDeclaration: function() {
13140 if (this.peekToken().text !== ']') {
13142 if (this.peek(']')) {
13143 // Support trailing commas per ES5.1.
13146 elements.push(this.expression());
13147 } while (this.expect(','));
13151 return { type: AST.ArrayExpression, elements: elements };
13154 object: function() {
13155 var properties = [], property;
13156 if (this.peekToken().text !== '}') {
13158 if (this.peek('}')) {
13159 // Support trailing commas per ES5.1.
13162 property = {type: AST.Property, kind: 'init'};
13163 if (this.peek().constant) {
13164 property.key = this.constant();
13165 } else if (this.peek().identifier) {
13166 property.key = this.identifier();
13168 this.throwError("invalid key", this.peek());
13171 property.value = this.expression();
13172 properties.push(property);
13173 } while (this.expect(','));
13177 return {type: AST.ObjectExpression, properties: properties };
13180 throwError: function(msg, token) {
13181 throw $parseMinErr('syntax',
13182 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].',
13183 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index));
13186 consume: function(e1) {
13187 if (this.tokens.length === 0) {
13188 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
13191 var token = this.expect(e1);
13193 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek());
13198 peekToken: function() {
13199 if (this.tokens.length === 0) {
13200 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
13202 return this.tokens[0];
13205 peek: function(e1, e2, e3, e4) {
13206 return this.peekAhead(0, e1, e2, e3, e4);
13209 peekAhead: function(i, e1, e2, e3, e4) {
13210 if (this.tokens.length > i) {
13211 var token = this.tokens[i];
13212 var t = token.text;
13213 if (t === e1 || t === e2 || t === e3 || t === e4 ||
13214 (!e1 && !e2 && !e3 && !e4)) {
13221 expect: function(e1, e2, e3, e4) {
13222 var token = this.peek(e1, e2, e3, e4);
13224 this.tokens.shift();
13231 /* `undefined` is not a constant, it is an identifier,
13232 * but using it as an identifier is not supported
13235 'true': { type: AST.Literal, value: true },
13236 'false': { type: AST.Literal, value: false },
13237 'null': { type: AST.Literal, value: null },
13238 'undefined': {type: AST.Literal, value: undefined },
13239 'this': {type: AST.ThisExpression }
13243 function ifDefined(v, d) {
13244 return typeof v !== 'undefined' ? v : d;
13247 function plusFn(l, r) {
13248 if (typeof l === 'undefined') return r;
13249 if (typeof r === 'undefined') return l;
13253 function isStateless($filter, filterName) {
13254 var fn = $filter(filterName);
13255 return !fn.$stateful;
13258 function findConstantAndWatchExpressions(ast, $filter) {
13261 switch (ast.type) {
13263 allConstants = true;
13264 forEach(ast.body, function(expr) {
13265 findConstantAndWatchExpressions(expr.expression, $filter);
13266 allConstants = allConstants && expr.expression.constant;
13268 ast.constant = allConstants;
13271 ast.constant = true;
13274 case AST.UnaryExpression:
13275 findConstantAndWatchExpressions(ast.argument, $filter);
13276 ast.constant = ast.argument.constant;
13277 ast.toWatch = ast.argument.toWatch;
13279 case AST.BinaryExpression:
13280 findConstantAndWatchExpressions(ast.left, $filter);
13281 findConstantAndWatchExpressions(ast.right, $filter);
13282 ast.constant = ast.left.constant && ast.right.constant;
13283 ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch);
13285 case AST.LogicalExpression:
13286 findConstantAndWatchExpressions(ast.left, $filter);
13287 findConstantAndWatchExpressions(ast.right, $filter);
13288 ast.constant = ast.left.constant && ast.right.constant;
13289 ast.toWatch = ast.constant ? [] : [ast];
13291 case AST.ConditionalExpression:
13292 findConstantAndWatchExpressions(ast.test, $filter);
13293 findConstantAndWatchExpressions(ast.alternate, $filter);
13294 findConstantAndWatchExpressions(ast.consequent, $filter);
13295 ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant;
13296 ast.toWatch = ast.constant ? [] : [ast];
13298 case AST.Identifier:
13299 ast.constant = false;
13300 ast.toWatch = [ast];
13302 case AST.MemberExpression:
13303 findConstantAndWatchExpressions(ast.object, $filter);
13304 if (ast.computed) {
13305 findConstantAndWatchExpressions(ast.property, $filter);
13307 ast.constant = ast.object.constant && (!ast.computed || ast.property.constant);
13308 ast.toWatch = [ast];
13310 case AST.CallExpression:
13311 allConstants = ast.filter ? isStateless($filter, ast.callee.name) : false;
13313 forEach(ast.arguments, function(expr) {
13314 findConstantAndWatchExpressions(expr, $filter);
13315 allConstants = allConstants && expr.constant;
13316 if (!expr.constant) {
13317 argsToWatch.push.apply(argsToWatch, expr.toWatch);
13320 ast.constant = allConstants;
13321 ast.toWatch = ast.filter && isStateless($filter, ast.callee.name) ? argsToWatch : [ast];
13323 case AST.AssignmentExpression:
13324 findConstantAndWatchExpressions(ast.left, $filter);
13325 findConstantAndWatchExpressions(ast.right, $filter);
13326 ast.constant = ast.left.constant && ast.right.constant;
13327 ast.toWatch = [ast];
13329 case AST.ArrayExpression:
13330 allConstants = true;
13332 forEach(ast.elements, function(expr) {
13333 findConstantAndWatchExpressions(expr, $filter);
13334 allConstants = allConstants && expr.constant;
13335 if (!expr.constant) {
13336 argsToWatch.push.apply(argsToWatch, expr.toWatch);
13339 ast.constant = allConstants;
13340 ast.toWatch = argsToWatch;
13342 case AST.ObjectExpression:
13343 allConstants = true;
13345 forEach(ast.properties, function(property) {
13346 findConstantAndWatchExpressions(property.value, $filter);
13347 allConstants = allConstants && property.value.constant;
13348 if (!property.value.constant) {
13349 argsToWatch.push.apply(argsToWatch, property.value.toWatch);
13352 ast.constant = allConstants;
13353 ast.toWatch = argsToWatch;
13355 case AST.ThisExpression:
13356 ast.constant = false;
13362 function getInputs(body) {
13363 if (body.length != 1) return;
13364 var lastExpression = body[0].expression;
13365 var candidate = lastExpression.toWatch;
13366 if (candidate.length !== 1) return candidate;
13367 return candidate[0] !== lastExpression ? candidate : undefined;
13370 function isAssignable(ast) {
13371 return ast.type === AST.Identifier || ast.type === AST.MemberExpression;
13374 function assignableAST(ast) {
13375 if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) {
13376 return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='};
13380 function isLiteral(ast) {
13381 return ast.body.length === 0 ||
13382 ast.body.length === 1 && (
13383 ast.body[0].expression.type === AST.Literal ||
13384 ast.body[0].expression.type === AST.ArrayExpression ||
13385 ast.body[0].expression.type === AST.ObjectExpression);
13388 function isConstant(ast) {
13389 return ast.constant;
13392 function ASTCompiler(astBuilder, $filter) {
13393 this.astBuilder = astBuilder;
13394 this.$filter = $filter;
13397 ASTCompiler.prototype = {
13398 compile: function(expression, expensiveChecks) {
13400 var ast = this.astBuilder.ast(expression);
13404 expensiveChecks: expensiveChecks,
13405 fn: {vars: [], body: [], own: {}},
13406 assign: {vars: [], body: [], own: {}},
13409 findConstantAndWatchExpressions(ast, self.$filter);
13412 this.stage = 'assign';
13413 if ((assignable = assignableAST(ast))) {
13414 this.state.computing = 'assign';
13415 var result = this.nextId();
13416 this.recurse(assignable, result);
13417 this.return_(result);
13418 extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l');
13420 var toWatch = getInputs(ast.body);
13421 self.stage = 'inputs';
13422 forEach(toWatch, function(watch, key) {
13423 var fnKey = 'fn' + key;
13424 self.state[fnKey] = {vars: [], body: [], own: {}};
13425 self.state.computing = fnKey;
13426 var intoId = self.nextId();
13427 self.recurse(watch, intoId);
13428 self.return_(intoId);
13429 self.state.inputs.push(fnKey);
13430 watch.watchId = key;
13432 this.state.computing = 'fn';
13433 this.stage = 'main';
13436 // The build and minification steps remove the string "use strict" from the code, but this is done using a regex.
13437 // This is a workaround for this until we do a better job at only removing the prefix only when we should.
13438 '"' + this.USE + ' ' + this.STRICT + '";\n' +
13439 this.filterPrefix() +
13440 'var fn=' + this.generateFunction('fn', 's,l,a,i') +
13446 var fn = (new Function('$filter',
13447 'ensureSafeMemberName',
13448 'ensureSafeObject',
13449 'ensureSafeFunction',
13451 'ensureSafeAssignContext',
13457 ensureSafeMemberName,
13459 ensureSafeFunction,
13461 ensureSafeAssignContext,
13466 this.state = this.stage = undefined;
13467 fn.literal = isLiteral(ast);
13468 fn.constant = isConstant(ast);
13476 watchFns: function() {
13478 var fns = this.state.inputs;
13480 forEach(fns, function(name) {
13481 result.push('var ' + name + '=' + self.generateFunction(name, 's'));
13484 result.push('fn.inputs=[' + fns.join(',') + '];');
13486 return result.join('');
13489 generateFunction: function(name, params) {
13490 return 'function(' + params + '){' +
13491 this.varsPrefix(name) +
13496 filterPrefix: function() {
13499 forEach(this.state.filters, function(id, filter) {
13500 parts.push(id + '=$filter(' + self.escape(filter) + ')');
13502 if (parts.length) return 'var ' + parts.join(',') + ';';
13506 varsPrefix: function(section) {
13507 return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : '';
13510 body: function(section) {
13511 return this.state[section].body.join('');
13514 recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
13515 var left, right, self = this, args, expression;
13516 recursionFn = recursionFn || noop;
13517 if (!skipWatchIdCheck && isDefined(ast.watchId)) {
13518 intoId = intoId || this.nextId();
13520 this.lazyAssign(intoId, this.computedMember('i', ast.watchId)),
13521 this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true)
13525 switch (ast.type) {
13527 forEach(ast.body, function(expression, pos) {
13528 self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; });
13529 if (pos !== ast.body.length - 1) {
13530 self.current().body.push(right, ';');
13532 self.return_(right);
13537 expression = this.escape(ast.value);
13538 this.assign(intoId, expression);
13539 recursionFn(expression);
13541 case AST.UnaryExpression:
13542 this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; });
13543 expression = ast.operator + '(' + this.ifDefined(right, 0) + ')';
13544 this.assign(intoId, expression);
13545 recursionFn(expression);
13547 case AST.BinaryExpression:
13548 this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; });
13549 this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; });
13550 if (ast.operator === '+') {
13551 expression = this.plus(left, right);
13552 } else if (ast.operator === '-') {
13553 expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0);
13555 expression = '(' + left + ')' + ast.operator + '(' + right + ')';
13557 this.assign(intoId, expression);
13558 recursionFn(expression);
13560 case AST.LogicalExpression:
13561 intoId = intoId || this.nextId();
13562 self.recurse(ast.left, intoId);
13563 self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId));
13564 recursionFn(intoId);
13566 case AST.ConditionalExpression:
13567 intoId = intoId || this.nextId();
13568 self.recurse(ast.test, intoId);
13569 self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId));
13570 recursionFn(intoId);
13572 case AST.Identifier:
13573 intoId = intoId || this.nextId();
13575 nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s');
13576 nameId.computed = false;
13577 nameId.name = ast.name;
13579 ensureSafeMemberName(ast.name);
13580 self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)),
13582 self.if_(self.stage === 'inputs' || 's', function() {
13583 if (create && create !== 1) {
13585 self.not(self.nonComputedMember('s', ast.name)),
13586 self.lazyAssign(self.nonComputedMember('s', ast.name), '{}'));
13588 self.assign(intoId, self.nonComputedMember('s', ast.name));
13590 }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name))
13592 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) {
13593 self.addEnsureSafeObject(intoId);
13595 recursionFn(intoId);
13597 case AST.MemberExpression:
13598 left = nameId && (nameId.context = this.nextId()) || this.nextId();
13599 intoId = intoId || this.nextId();
13600 self.recurse(ast.object, left, undefined, function() {
13601 self.if_(self.notNull(left), function() {
13602 if (ast.computed) {
13603 right = self.nextId();
13604 self.recurse(ast.property, right);
13605 self.getStringValue(right);
13606 self.addEnsureSafeMemberName(right);
13607 if (create && create !== 1) {
13608 self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}'));
13610 expression = self.ensureSafeObject(self.computedMember(left, right));
13611 self.assign(intoId, expression);
13613 nameId.computed = true;
13614 nameId.name = right;
13617 ensureSafeMemberName(ast.property.name);
13618 if (create && create !== 1) {
13619 self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}'));
13621 expression = self.nonComputedMember(left, ast.property.name);
13622 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) {
13623 expression = self.ensureSafeObject(expression);
13625 self.assign(intoId, expression);
13627 nameId.computed = false;
13628 nameId.name = ast.property.name;
13632 self.assign(intoId, 'undefined');
13634 recursionFn(intoId);
13637 case AST.CallExpression:
13638 intoId = intoId || this.nextId();
13640 right = self.filter(ast.callee.name);
13642 forEach(ast.arguments, function(expr) {
13643 var argument = self.nextId();
13644 self.recurse(expr, argument);
13645 args.push(argument);
13647 expression = right + '(' + args.join(',') + ')';
13648 self.assign(intoId, expression);
13649 recursionFn(intoId);
13651 right = self.nextId();
13654 self.recurse(ast.callee, right, left, function() {
13655 self.if_(self.notNull(right), function() {
13656 self.addEnsureSafeFunction(right);
13657 forEach(ast.arguments, function(expr) {
13658 self.recurse(expr, self.nextId(), undefined, function(argument) {
13659 args.push(self.ensureSafeObject(argument));
13663 if (!self.state.expensiveChecks) {
13664 self.addEnsureSafeObject(left.context);
13666 expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')';
13668 expression = right + '(' + args.join(',') + ')';
13670 expression = self.ensureSafeObject(expression);
13671 self.assign(intoId, expression);
13673 self.assign(intoId, 'undefined');
13675 recursionFn(intoId);
13679 case AST.AssignmentExpression:
13680 right = this.nextId();
13682 if (!isAssignable(ast.left)) {
13683 throw $parseMinErr('lval', 'Trying to assing a value to a non l-value');
13685 this.recurse(ast.left, undefined, left, function() {
13686 self.if_(self.notNull(left.context), function() {
13687 self.recurse(ast.right, right);
13688 self.addEnsureSafeObject(self.member(left.context, left.name, left.computed));
13689 self.addEnsureSafeAssignContext(left.context);
13690 expression = self.member(left.context, left.name, left.computed) + ast.operator + right;
13691 self.assign(intoId, expression);
13692 recursionFn(intoId || expression);
13696 case AST.ArrayExpression:
13698 forEach(ast.elements, function(expr) {
13699 self.recurse(expr, self.nextId(), undefined, function(argument) {
13700 args.push(argument);
13703 expression = '[' + args.join(',') + ']';
13704 this.assign(intoId, expression);
13705 recursionFn(expression);
13707 case AST.ObjectExpression:
13709 forEach(ast.properties, function(property) {
13710 self.recurse(property.value, self.nextId(), undefined, function(expr) {
13711 args.push(self.escape(
13712 property.key.type === AST.Identifier ? property.key.name :
13713 ('' + property.key.value)) +
13717 expression = '{' + args.join(',') + '}';
13718 this.assign(intoId, expression);
13719 recursionFn(expression);
13721 case AST.ThisExpression:
13722 this.assign(intoId, 's');
13725 case AST.NGValueParameter:
13726 this.assign(intoId, 'v');
13732 getHasOwnProperty: function(element, property) {
13733 var key = element + '.' + property;
13734 var own = this.current().own;
13735 if (!own.hasOwnProperty(key)) {
13736 own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')');
13741 assign: function(id, value) {
13743 this.current().body.push(id, '=', value, ';');
13747 filter: function(filterName) {
13748 if (!this.state.filters.hasOwnProperty(filterName)) {
13749 this.state.filters[filterName] = this.nextId(true);
13751 return this.state.filters[filterName];
13754 ifDefined: function(id, defaultValue) {
13755 return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')';
13758 plus: function(left, right) {
13759 return 'plus(' + left + ',' + right + ')';
13762 return_: function(id) {
13763 this.current().body.push('return ', id, ';');
13766 if_: function(test, alternate, consequent) {
13767 if (test === true) {
13770 var body = this.current().body;
13771 body.push('if(', test, '){');
13775 body.push('else{');
13782 not: function(expression) {
13783 return '!(' + expression + ')';
13786 notNull: function(expression) {
13787 return expression + '!=null';
13790 nonComputedMember: function(left, right) {
13791 return left + '.' + right;
13794 computedMember: function(left, right) {
13795 return left + '[' + right + ']';
13798 member: function(left, right, computed) {
13799 if (computed) return this.computedMember(left, right);
13800 return this.nonComputedMember(left, right);
13803 addEnsureSafeObject: function(item) {
13804 this.current().body.push(this.ensureSafeObject(item), ';');
13807 addEnsureSafeMemberName: function(item) {
13808 this.current().body.push(this.ensureSafeMemberName(item), ';');
13811 addEnsureSafeFunction: function(item) {
13812 this.current().body.push(this.ensureSafeFunction(item), ';');
13815 addEnsureSafeAssignContext: function(item) {
13816 this.current().body.push(this.ensureSafeAssignContext(item), ';');
13819 ensureSafeObject: function(item) {
13820 return 'ensureSafeObject(' + item + ',text)';
13823 ensureSafeMemberName: function(item) {
13824 return 'ensureSafeMemberName(' + item + ',text)';
13827 ensureSafeFunction: function(item) {
13828 return 'ensureSafeFunction(' + item + ',text)';
13831 getStringValue: function(item) {
13832 this.assign(item, 'getStringValue(' + item + ',text)');
13835 ensureSafeAssignContext: function(item) {
13836 return 'ensureSafeAssignContext(' + item + ',text)';
13839 lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
13841 return function() {
13842 self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck);
13846 lazyAssign: function(id, value) {
13848 return function() {
13849 self.assign(id, value);
13853 stringEscapeRegex: /[^ a-zA-Z0-9]/g,
13855 stringEscapeFn: function(c) {
13856 return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4);
13859 escape: function(value) {
13860 if (isString(value)) return "'" + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + "'";
13861 if (isNumber(value)) return value.toString();
13862 if (value === true) return 'true';
13863 if (value === false) return 'false';
13864 if (value === null) return 'null';
13865 if (typeof value === 'undefined') return 'undefined';
13867 throw $parseMinErr('esc', 'IMPOSSIBLE');
13870 nextId: function(skip, init) {
13871 var id = 'v' + (this.state.nextId++);
13873 this.current().vars.push(id + (init ? '=' + init : ''));
13878 current: function() {
13879 return this.state[this.state.computing];
13884 function ASTInterpreter(astBuilder, $filter) {
13885 this.astBuilder = astBuilder;
13886 this.$filter = $filter;
13889 ASTInterpreter.prototype = {
13890 compile: function(expression, expensiveChecks) {
13892 var ast = this.astBuilder.ast(expression);
13893 this.expression = expression;
13894 this.expensiveChecks = expensiveChecks;
13895 findConstantAndWatchExpressions(ast, self.$filter);
13898 if ((assignable = assignableAST(ast))) {
13899 assign = this.recurse(assignable);
13901 var toWatch = getInputs(ast.body);
13905 forEach(toWatch, function(watch, key) {
13906 var input = self.recurse(watch);
13907 watch.input = input;
13908 inputs.push(input);
13909 watch.watchId = key;
13912 var expressions = [];
13913 forEach(ast.body, function(expression) {
13914 expressions.push(self.recurse(expression.expression));
13916 var fn = ast.body.length === 0 ? function() {} :
13917 ast.body.length === 1 ? expressions[0] :
13918 function(scope, locals) {
13920 forEach(expressions, function(exp) {
13921 lastValue = exp(scope, locals);
13926 fn.assign = function(scope, value, locals) {
13927 return assign(scope, locals, value);
13931 fn.inputs = inputs;
13933 fn.literal = isLiteral(ast);
13934 fn.constant = isConstant(ast);
13938 recurse: function(ast, context, create) {
13939 var left, right, self = this, args, expression;
13941 return this.inputs(ast.input, ast.watchId);
13943 switch (ast.type) {
13945 return this.value(ast.value, context);
13946 case AST.UnaryExpression:
13947 right = this.recurse(ast.argument);
13948 return this['unary' + ast.operator](right, context);
13949 case AST.BinaryExpression:
13950 left = this.recurse(ast.left);
13951 right = this.recurse(ast.right);
13952 return this['binary' + ast.operator](left, right, context);
13953 case AST.LogicalExpression:
13954 left = this.recurse(ast.left);
13955 right = this.recurse(ast.right);
13956 return this['binary' + ast.operator](left, right, context);
13957 case AST.ConditionalExpression:
13958 return this['ternary?:'](
13959 this.recurse(ast.test),
13960 this.recurse(ast.alternate),
13961 this.recurse(ast.consequent),
13964 case AST.Identifier:
13965 ensureSafeMemberName(ast.name, self.expression);
13966 return self.identifier(ast.name,
13967 self.expensiveChecks || isPossiblyDangerousMemberName(ast.name),
13968 context, create, self.expression);
13969 case AST.MemberExpression:
13970 left = this.recurse(ast.object, false, !!create);
13971 if (!ast.computed) {
13972 ensureSafeMemberName(ast.property.name, self.expression);
13973 right = ast.property.name;
13975 if (ast.computed) right = this.recurse(ast.property);
13976 return ast.computed ?
13977 this.computedMember(left, right, context, create, self.expression) :
13978 this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression);
13979 case AST.CallExpression:
13981 forEach(ast.arguments, function(expr) {
13982 args.push(self.recurse(expr));
13984 if (ast.filter) right = this.$filter(ast.callee.name);
13985 if (!ast.filter) right = this.recurse(ast.callee, true);
13986 return ast.filter ?
13987 function(scope, locals, assign, inputs) {
13989 for (var i = 0; i < args.length; ++i) {
13990 values.push(args[i](scope, locals, assign, inputs));
13992 var value = right.apply(undefined, values, inputs);
13993 return context ? {context: undefined, name: undefined, value: value} : value;
13995 function(scope, locals, assign, inputs) {
13996 var rhs = right(scope, locals, assign, inputs);
13998 if (rhs.value != null) {
13999 ensureSafeObject(rhs.context, self.expression);
14000 ensureSafeFunction(rhs.value, self.expression);
14002 for (var i = 0; i < args.length; ++i) {
14003 values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression));
14005 value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression);
14007 return context ? {value: value} : value;
14009 case AST.AssignmentExpression:
14010 left = this.recurse(ast.left, true, 1);
14011 right = this.recurse(ast.right);
14012 return function(scope, locals, assign, inputs) {
14013 var lhs = left(scope, locals, assign, inputs);
14014 var rhs = right(scope, locals, assign, inputs);
14015 ensureSafeObject(lhs.value, self.expression);
14016 ensureSafeAssignContext(lhs.context);
14017 lhs.context[lhs.name] = rhs;
14018 return context ? {value: rhs} : rhs;
14020 case AST.ArrayExpression:
14022 forEach(ast.elements, function(expr) {
14023 args.push(self.recurse(expr));
14025 return function(scope, locals, assign, inputs) {
14027 for (var i = 0; i < args.length; ++i) {
14028 value.push(args[i](scope, locals, assign, inputs));
14030 return context ? {value: value} : value;
14032 case AST.ObjectExpression:
14034 forEach(ast.properties, function(property) {
14035 args.push({key: property.key.type === AST.Identifier ?
14036 property.key.name :
14037 ('' + property.key.value),
14038 value: self.recurse(property.value)
14041 return function(scope, locals, assign, inputs) {
14043 for (var i = 0; i < args.length; ++i) {
14044 value[args[i].key] = args[i].value(scope, locals, assign, inputs);
14046 return context ? {value: value} : value;
14048 case AST.ThisExpression:
14049 return function(scope) {
14050 return context ? {value: scope} : scope;
14052 case AST.NGValueParameter:
14053 return function(scope, locals, assign, inputs) {
14054 return context ? {value: assign} : assign;
14059 'unary+': function(argument, context) {
14060 return function(scope, locals, assign, inputs) {
14061 var arg = argument(scope, locals, assign, inputs);
14062 if (isDefined(arg)) {
14067 return context ? {value: arg} : arg;
14070 'unary-': function(argument, context) {
14071 return function(scope, locals, assign, inputs) {
14072 var arg = argument(scope, locals, assign, inputs);
14073 if (isDefined(arg)) {
14078 return context ? {value: arg} : arg;
14081 'unary!': function(argument, context) {
14082 return function(scope, locals, assign, inputs) {
14083 var arg = !argument(scope, locals, assign, inputs);
14084 return context ? {value: arg} : arg;
14087 'binary+': function(left, right, context) {
14088 return function(scope, locals, assign, inputs) {
14089 var lhs = left(scope, locals, assign, inputs);
14090 var rhs = right(scope, locals, assign, inputs);
14091 var arg = plusFn(lhs, rhs);
14092 return context ? {value: arg} : arg;
14095 'binary-': function(left, right, context) {
14096 return function(scope, locals, assign, inputs) {
14097 var lhs = left(scope, locals, assign, inputs);
14098 var rhs = right(scope, locals, assign, inputs);
14099 var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0);
14100 return context ? {value: arg} : arg;
14103 'binary*': function(left, right, context) {
14104 return function(scope, locals, assign, inputs) {
14105 var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs);
14106 return context ? {value: arg} : arg;
14109 'binary/': function(left, right, context) {
14110 return function(scope, locals, assign, inputs) {
14111 var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs);
14112 return context ? {value: arg} : arg;
14115 'binary%': function(left, right, context) {
14116 return function(scope, locals, assign, inputs) {
14117 var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs);
14118 return context ? {value: arg} : arg;
14121 'binary===': function(left, right, context) {
14122 return function(scope, locals, assign, inputs) {
14123 var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs);
14124 return context ? {value: arg} : arg;
14127 'binary!==': function(left, right, context) {
14128 return function(scope, locals, assign, inputs) {
14129 var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs);
14130 return context ? {value: arg} : arg;
14133 'binary==': function(left, right, context) {
14134 return function(scope, locals, assign, inputs) {
14135 var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs);
14136 return context ? {value: arg} : arg;
14139 'binary!=': function(left, right, context) {
14140 return function(scope, locals, assign, inputs) {
14141 var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs);
14142 return context ? {value: arg} : arg;
14145 'binary<': function(left, right, context) {
14146 return function(scope, locals, assign, inputs) {
14147 var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs);
14148 return context ? {value: arg} : arg;
14151 'binary>': function(left, right, context) {
14152 return function(scope, locals, assign, inputs) {
14153 var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs);
14154 return context ? {value: arg} : arg;
14157 'binary<=': function(left, right, context) {
14158 return function(scope, locals, assign, inputs) {
14159 var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs);
14160 return context ? {value: arg} : arg;
14163 'binary>=': function(left, right, context) {
14164 return function(scope, locals, assign, inputs) {
14165 var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs);
14166 return context ? {value: arg} : arg;
14169 'binary&&': function(left, right, context) {
14170 return function(scope, locals, assign, inputs) {
14171 var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs);
14172 return context ? {value: arg} : arg;
14175 'binary||': function(left, right, context) {
14176 return function(scope, locals, assign, inputs) {
14177 var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs);
14178 return context ? {value: arg} : arg;
14181 'ternary?:': function(test, alternate, consequent, context) {
14182 return function(scope, locals, assign, inputs) {
14183 var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs);
14184 return context ? {value: arg} : arg;
14187 value: function(value, context) {
14188 return function() { return context ? {context: undefined, name: undefined, value: value} : value; };
14190 identifier: function(name, expensiveChecks, context, create, expression) {
14191 return function(scope, locals, assign, inputs) {
14192 var base = locals && (name in locals) ? locals : scope;
14193 if (create && create !== 1 && base && !(base[name])) {
14196 var value = base ? base[name] : undefined;
14197 if (expensiveChecks) {
14198 ensureSafeObject(value, expression);
14201 return {context: base, name: name, value: value};
14207 computedMember: function(left, right, context, create, expression) {
14208 return function(scope, locals, assign, inputs) {
14209 var lhs = left(scope, locals, assign, inputs);
14213 rhs = right(scope, locals, assign, inputs);
14214 rhs = getStringValue(rhs);
14215 ensureSafeMemberName(rhs, expression);
14216 if (create && create !== 1 && lhs && !(lhs[rhs])) {
14220 ensureSafeObject(value, expression);
14223 return {context: lhs, name: rhs, value: value};
14229 nonComputedMember: function(left, right, expensiveChecks, context, create, expression) {
14230 return function(scope, locals, assign, inputs) {
14231 var lhs = left(scope, locals, assign, inputs);
14232 if (create && create !== 1 && lhs && !(lhs[right])) {
14235 var value = lhs != null ? lhs[right] : undefined;
14236 if (expensiveChecks || isPossiblyDangerousMemberName(right)) {
14237 ensureSafeObject(value, expression);
14240 return {context: lhs, name: right, value: value};
14246 inputs: function(input, watchId) {
14247 return function(scope, value, locals, inputs) {
14248 if (inputs) return inputs[watchId];
14249 return input(scope, value, locals);
14257 var Parser = function(lexer, $filter, options) {
14258 this.lexer = lexer;
14259 this.$filter = $filter;
14260 this.options = options;
14261 this.ast = new AST(this.lexer);
14262 this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) :
14263 new ASTCompiler(this.ast, $filter);
14266 Parser.prototype = {
14267 constructor: Parser,
14269 parse: function(text) {
14270 return this.astCompiler.compile(text, this.options.expensiveChecks);
14274 var getterFnCacheDefault = createMap();
14275 var getterFnCacheExpensive = createMap();
14277 function isPossiblyDangerousMemberName(name) {
14278 return name == 'constructor';
14281 var objectValueOf = Object.prototype.valueOf;
14283 function getValueOf(value) {
14284 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value);
14287 ///////////////////////////////////
14296 * Converts Angular {@link guide/expression expression} into a function.
14299 * var getter = $parse('user.name');
14300 * var setter = getter.assign;
14301 * var context = {user:{name:'angular'}};
14302 * var locals = {user:{name:'local'}};
14304 * expect(getter(context)).toEqual('angular');
14305 * setter(context, 'newValue');
14306 * expect(context.user.name).toEqual('newValue');
14307 * expect(getter(context, locals)).toEqual('local');
14311 * @param {string} expression String expression to compile.
14312 * @returns {function(context, locals)} a function which represents the compiled expression:
14314 * * `context` – `{object}` – an object against which any expressions embedded in the strings
14315 * are evaluated against (typically a scope object).
14316 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
14319 * The returned function also has the following properties:
14320 * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript
14322 * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript
14323 * constant literals.
14324 * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be
14325 * set to a function to change its value on the given context.
14332 * @name $parseProvider
14335 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse}
14338 function $ParseProvider() {
14339 var cacheDefault = createMap();
14340 var cacheExpensive = createMap();
14342 this.$get = ['$filter', function($filter) {
14343 var noUnsafeEval = csp().noUnsafeEval;
14344 var $parseOptions = {
14346 expensiveChecks: false
14348 $parseOptionsExpensive = {
14350 expensiveChecks: true
14353 return function $parse(exp, interceptorFn, expensiveChecks) {
14354 var parsedExpression, oneTime, cacheKey;
14356 switch (typeof exp) {
14361 var cache = (expensiveChecks ? cacheExpensive : cacheDefault);
14362 parsedExpression = cache[cacheKey];
14364 if (!parsedExpression) {
14365 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {
14367 exp = exp.substring(2);
14369 var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions;
14370 var lexer = new Lexer(parseOptions);
14371 var parser = new Parser(lexer, $filter, parseOptions);
14372 parsedExpression = parser.parse(exp);
14373 if (parsedExpression.constant) {
14374 parsedExpression.$$watchDelegate = constantWatchDelegate;
14375 } else if (oneTime) {
14376 parsedExpression.$$watchDelegate = parsedExpression.literal ?
14377 oneTimeLiteralWatchDelegate : oneTimeWatchDelegate;
14378 } else if (parsedExpression.inputs) {
14379 parsedExpression.$$watchDelegate = inputsWatchDelegate;
14381 cache[cacheKey] = parsedExpression;
14383 return addInterceptor(parsedExpression, interceptorFn);
14386 return addInterceptor(exp, interceptorFn);
14393 function expressionInputDirtyCheck(newValue, oldValueOfValue) {
14395 if (newValue == null || oldValueOfValue == null) { // null/undefined
14396 return newValue === oldValueOfValue;
14399 if (typeof newValue === 'object') {
14401 // attempt to convert the value to a primitive type
14402 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can
14403 // be cheaply dirty-checked
14404 newValue = getValueOf(newValue);
14406 if (typeof newValue === 'object') {
14407 // objects/arrays are not supported - deep-watching them would be too expensive
14411 // fall-through to the primitive equality check
14415 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue);
14418 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) {
14419 var inputExpressions = parsedExpression.inputs;
14422 if (inputExpressions.length === 1) {
14423 var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails
14424 inputExpressions = inputExpressions[0];
14425 return scope.$watch(function expressionInputWatch(scope) {
14426 var newInputValue = inputExpressions(scope);
14427 if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) {
14428 lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]);
14429 oldInputValueOf = newInputValue && getValueOf(newInputValue);
14432 }, listener, objectEquality, prettyPrintExpression);
14435 var oldInputValueOfValues = [];
14436 var oldInputValues = [];
14437 for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
14438 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails
14439 oldInputValues[i] = null;
14442 return scope.$watch(function expressionInputsWatch(scope) {
14443 var changed = false;
14445 for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
14446 var newInputValue = inputExpressions[i](scope);
14447 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) {
14448 oldInputValues[i] = newInputValue;
14449 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue);
14454 lastResult = parsedExpression(scope, undefined, undefined, oldInputValues);
14458 }, listener, objectEquality, prettyPrintExpression);
14461 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14462 var unwatch, lastValue;
14463 return unwatch = scope.$watch(function oneTimeWatch(scope) {
14464 return parsedExpression(scope);
14465 }, function oneTimeListener(value, old, scope) {
14467 if (isFunction(listener)) {
14468 listener.apply(this, arguments);
14470 if (isDefined(value)) {
14471 scope.$$postDigest(function() {
14472 if (isDefined(lastValue)) {
14477 }, objectEquality);
14480 function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14481 var unwatch, lastValue;
14482 return unwatch = scope.$watch(function oneTimeWatch(scope) {
14483 return parsedExpression(scope);
14484 }, function oneTimeListener(value, old, scope) {
14486 if (isFunction(listener)) {
14487 listener.call(this, value, old, scope);
14489 if (isAllDefined(value)) {
14490 scope.$$postDigest(function() {
14491 if (isAllDefined(lastValue)) unwatch();
14494 }, objectEquality);
14496 function isAllDefined(value) {
14497 var allDefined = true;
14498 forEach(value, function(val) {
14499 if (!isDefined(val)) allDefined = false;
14505 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14507 return unwatch = scope.$watch(function constantWatch(scope) {
14508 return parsedExpression(scope);
14509 }, function constantListener(value, old, scope) {
14510 if (isFunction(listener)) {
14511 listener.apply(this, arguments);
14514 }, objectEquality);
14517 function addInterceptor(parsedExpression, interceptorFn) {
14518 if (!interceptorFn) return parsedExpression;
14519 var watchDelegate = parsedExpression.$$watchDelegate;
14520 var useInputs = false;
14523 watchDelegate !== oneTimeLiteralWatchDelegate &&
14524 watchDelegate !== oneTimeWatchDelegate;
14526 var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) {
14527 var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs);
14528 return interceptorFn(value, scope, locals);
14529 } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) {
14530 var value = parsedExpression(scope, locals, assign, inputs);
14531 var result = interceptorFn(value, scope, locals);
14532 // we only return the interceptor's result if the
14533 // initial value is defined (for bind-once)
14534 return isDefined(value) ? result : value;
14537 // Propagate $$watchDelegates other then inputsWatchDelegate
14538 if (parsedExpression.$$watchDelegate &&
14539 parsedExpression.$$watchDelegate !== inputsWatchDelegate) {
14540 fn.$$watchDelegate = parsedExpression.$$watchDelegate;
14541 } else if (!interceptorFn.$stateful) {
14542 // If there is an interceptor, but no watchDelegate then treat the interceptor like
14543 // we treat filters - it is assumed to be a pure function unless flagged with $stateful
14544 fn.$$watchDelegate = inputsWatchDelegate;
14545 useInputs = !parsedExpression.inputs;
14546 fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression];
14557 * @requires $rootScope
14560 * A service that helps you run functions asynchronously, and use their return values (or exceptions)
14561 * when they are done processing.
14563 * This is an implementation of promises/deferred objects inspired by
14564 * [Kris Kowal's Q](https://github.com/kriskowal/q).
14566 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred
14567 * implementations, and the other which resembles ES6 promises to some degree.
14571 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver`
14572 * function as the first argument. This is similar to the native Promise implementation from ES6 Harmony,
14573 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
14575 * While the constructor-style use is supported, not all of the supporting methods from ES6 Harmony promises are
14578 * It can be used like so:
14581 * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
14582 * // are available in the current lexical scope (they could have been injected or passed in).
14584 * function asyncGreet(name) {
14585 * // perform some asynchronous operation, resolve or reject the promise when appropriate.
14586 * return $q(function(resolve, reject) {
14587 * setTimeout(function() {
14588 * if (okToGreet(name)) {
14589 * resolve('Hello, ' + name + '!');
14591 * reject('Greeting ' + name + ' is not allowed.');
14597 * var promise = asyncGreet('Robin Hood');
14598 * promise.then(function(greeting) {
14599 * alert('Success: ' + greeting);
14600 * }, function(reason) {
14601 * alert('Failed: ' + reason);
14605 * Note: progress/notify callbacks are not currently supported via the ES6-style interface.
14607 * Note: unlike ES6 behaviour, an exception thrown in the constructor function will NOT implicitly reject the promise.
14609 * However, the more traditional CommonJS-style usage is still available, and documented below.
14611 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
14612 * interface for interacting with an object that represents the result of an action that is
14613 * performed asynchronously, and may or may not be finished at any given point in time.
14615 * From the perspective of dealing with error handling, deferred and promise APIs are to
14616 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
14619 * // for the purpose of this example let's assume that variables `$q` and `okToGreet`
14620 * // are available in the current lexical scope (they could have been injected or passed in).
14622 * function asyncGreet(name) {
14623 * var deferred = $q.defer();
14625 * setTimeout(function() {
14626 * deferred.notify('About to greet ' + name + '.');
14628 * if (okToGreet(name)) {
14629 * deferred.resolve('Hello, ' + name + '!');
14631 * deferred.reject('Greeting ' + name + ' is not allowed.');
14635 * return deferred.promise;
14638 * var promise = asyncGreet('Robin Hood');
14639 * promise.then(function(greeting) {
14640 * alert('Success: ' + greeting);
14641 * }, function(reason) {
14642 * alert('Failed: ' + reason);
14643 * }, function(update) {
14644 * alert('Got notification: ' + update);
14648 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff
14649 * comes in the way of guarantees that promise and deferred APIs make, see
14650 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md.
14652 * Additionally the promise api allows for composition that is very hard to do with the
14653 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
14654 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the
14655 * section on serial or parallel joining of promises.
14657 * # The Deferred API
14659 * A new instance of deferred is constructed by calling `$q.defer()`.
14661 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs
14662 * that can be used for signaling the successful or unsuccessful completion, as well as the status
14667 * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection
14668 * constructed via `$q.reject`, the promise will be rejected instead.
14669 * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
14670 * resolving it with a rejection constructed via `$q.reject`.
14671 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called
14672 * multiple times before the promise is either resolved or rejected.
14676 * - promise – `{Promise}` – promise object associated with this deferred.
14679 * # The Promise API
14681 * A new promise instance is created when a deferred instance is created and can be retrieved by
14682 * calling `deferred.promise`.
14684 * The purpose of the promise object is to allow for interested parties to get access to the result
14685 * of the deferred task when it completes.
14689 * - `then(successCallback, errorCallback, notifyCallback)` – regardless of when the promise was or
14690 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously
14691 * as soon as the result is available. The callbacks are called with a single argument: the result
14692 * or rejection reason. Additionally, the notify callback may be called zero or more times to
14693 * provide a progress indication, before the promise is resolved or rejected.
14695 * This method *returns a new promise* which is resolved or rejected via the return value of the
14696 * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
14697 * with the value which is resolved in that promise using
14698 * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)).
14699 * It also notifies via the return value of the `notifyCallback` method. The promise cannot be
14700 * resolved or rejected from the notifyCallback method.
14702 * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)`
14704 * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise,
14705 * but to do so without modifying the final value. This is useful to release resources or do some
14706 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full
14707 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for
14708 * more information.
14710 * # Chaining promises
14712 * Because calling the `then` method of a promise returns a new derived promise, it is easily
14713 * possible to create a chain of promises:
14716 * promiseB = promiseA.then(function(result) {
14717 * return result + 1;
14720 * // promiseB will be resolved immediately after promiseA is resolved and its value
14721 * // will be the result of promiseA incremented by 1
14724 * It is possible to create chains of any length and since a promise can be resolved with another
14725 * promise (which will defer its resolution further), it is possible to pause/defer resolution of
14726 * the promises at any point in the chain. This makes it possible to implement powerful APIs like
14727 * $http's response interceptors.
14730 * # Differences between Kris Kowal's Q and $q
14732 * There are two main differences:
14734 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
14735 * mechanism in angular, which means faster propagation of resolution or rejection into your
14736 * models and avoiding unnecessary browser repaints, which would result in flickering UI.
14737 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
14738 * all the important functionality needed for common async tasks.
14743 * it('should simulate promise', inject(function($q, $rootScope) {
14744 * var deferred = $q.defer();
14745 * var promise = deferred.promise;
14746 * var resolvedValue;
14748 * promise.then(function(value) { resolvedValue = value; });
14749 * expect(resolvedValue).toBeUndefined();
14751 * // Simulate resolving of promise
14752 * deferred.resolve(123);
14753 * // Note that the 'then' function does not get called synchronously.
14754 * // This is because we want the promise API to always be async, whether or not
14755 * // it got called synchronously or asynchronously.
14756 * expect(resolvedValue).toBeUndefined();
14758 * // Propagate promise resolution to 'then' functions using $apply().
14759 * $rootScope.$apply();
14760 * expect(resolvedValue).toEqual(123);
14764 * @param {function(function, function)} resolver Function which is responsible for resolving or
14765 * rejecting the newly created promise. The first parameter is a function which resolves the
14766 * promise, the second parameter is a function which rejects the promise.
14768 * @returns {Promise} The newly created promise.
14770 function $QProvider() {
14772 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
14773 return qFactory(function(callback) {
14774 $rootScope.$evalAsync(callback);
14775 }, $exceptionHandler);
14779 function $$QProvider() {
14780 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) {
14781 return qFactory(function(callback) {
14782 $browser.defer(callback);
14783 }, $exceptionHandler);
14788 * Constructs a promise manager.
14790 * @param {function(function)} nextTick Function for executing functions in the next turn.
14791 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for
14792 * debugging purposes.
14793 * @returns {object} Promise manager.
14795 function qFactory(nextTick, exceptionHandler) {
14796 var $qMinErr = minErr('$q', TypeError);
14797 function callOnce(self, resolveFn, rejectFn) {
14798 var called = false;
14799 function wrap(fn) {
14800 return function(value) {
14801 if (called) return;
14803 fn.call(self, value);
14807 return [wrap(resolveFn), wrap(rejectFn)];
14812 * @name ng.$q#defer
14816 * Creates a `Deferred` object which represents a task which will finish in the future.
14818 * @returns {Deferred} Returns a new instance of deferred.
14820 var defer = function() {
14821 return new Deferred();
14824 function Promise() {
14825 this.$$state = { status: 0 };
14828 extend(Promise.prototype, {
14829 then: function(onFulfilled, onRejected, progressBack) {
14830 if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) {
14833 var result = new Deferred();
14835 this.$$state.pending = this.$$state.pending || [];
14836 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);
14837 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state);
14839 return result.promise;
14842 "catch": function(callback) {
14843 return this.then(null, callback);
14846 "finally": function(callback, progressBack) {
14847 return this.then(function(value) {
14848 return handleCallback(value, true, callback);
14849 }, function(error) {
14850 return handleCallback(error, false, callback);
14855 //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native
14856 function simpleBind(context, fn) {
14857 return function(value) {
14858 fn.call(context, value);
14862 function processQueue(state) {
14863 var fn, deferred, pending;
14865 pending = state.pending;
14866 state.processScheduled = false;
14867 state.pending = undefined;
14868 for (var i = 0, ii = pending.length; i < ii; ++i) {
14869 deferred = pending[i][0];
14870 fn = pending[i][state.status];
14872 if (isFunction(fn)) {
14873 deferred.resolve(fn(state.value));
14874 } else if (state.status === 1) {
14875 deferred.resolve(state.value);
14877 deferred.reject(state.value);
14880 deferred.reject(e);
14881 exceptionHandler(e);
14886 function scheduleProcessQueue(state) {
14887 if (state.processScheduled || !state.pending) return;
14888 state.processScheduled = true;
14889 nextTick(function() { processQueue(state); });
14892 function Deferred() {
14893 this.promise = new Promise();
14894 //Necessary to support unbound execution :/
14895 this.resolve = simpleBind(this, this.resolve);
14896 this.reject = simpleBind(this, this.reject);
14897 this.notify = simpleBind(this, this.notify);
14900 extend(Deferred.prototype, {
14901 resolve: function(val) {
14902 if (this.promise.$$state.status) return;
14903 if (val === this.promise) {
14904 this.$$reject($qMinErr(
14906 "Expected promise to be resolved with value other than itself '{0}'",
14909 this.$$resolve(val);
14914 $$resolve: function(val) {
14917 fns = callOnce(this, this.$$resolve, this.$$reject);
14919 if ((isObject(val) || isFunction(val))) then = val && val.then;
14920 if (isFunction(then)) {
14921 this.promise.$$state.status = -1;
14922 then.call(val, fns[0], fns[1], this.notify);
14924 this.promise.$$state.value = val;
14925 this.promise.$$state.status = 1;
14926 scheduleProcessQueue(this.promise.$$state);
14930 exceptionHandler(e);
14934 reject: function(reason) {
14935 if (this.promise.$$state.status) return;
14936 this.$$reject(reason);
14939 $$reject: function(reason) {
14940 this.promise.$$state.value = reason;
14941 this.promise.$$state.status = 2;
14942 scheduleProcessQueue(this.promise.$$state);
14945 notify: function(progress) {
14946 var callbacks = this.promise.$$state.pending;
14948 if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) {
14949 nextTick(function() {
14950 var callback, result;
14951 for (var i = 0, ii = callbacks.length; i < ii; i++) {
14952 result = callbacks[i][0];
14953 callback = callbacks[i][3];
14955 result.notify(isFunction(callback) ? callback(progress) : progress);
14957 exceptionHandler(e);
14971 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be
14972 * used to forward rejection in a chain of promises. If you are dealing with the last promise in
14973 * a promise chain, you don't need to worry about it.
14975 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of
14976 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via
14977 * a promise error callback and you want to forward the error to the promise derived from the
14978 * current promise, you have to "rethrow" the error by returning a rejection constructed via
14982 * promiseB = promiseA.then(function(result) {
14983 * // success: do something and resolve promiseB
14984 * // with the old or a new result
14986 * }, function(reason) {
14987 * // error: handle the error if possible and
14988 * // resolve promiseB with newPromiseOrValue,
14989 * // otherwise forward the rejection to promiseB
14990 * if (canHandle(reason)) {
14991 * // handle the error and recover
14992 * return newPromiseOrValue;
14994 * return $q.reject(reason);
14998 * @param {*} reason Constant, message, exception or an object representing the rejection reason.
14999 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
15001 var reject = function(reason) {
15002 var result = new Deferred();
15003 result.reject(reason);
15004 return result.promise;
15007 var makePromise = function makePromise(value, resolved) {
15008 var result = new Deferred();
15010 result.resolve(value);
15012 result.reject(value);
15014 return result.promise;
15017 var handleCallback = function handleCallback(value, isResolved, callback) {
15018 var callbackOutput = null;
15020 if (isFunction(callback)) callbackOutput = callback();
15022 return makePromise(e, false);
15024 if (isPromiseLike(callbackOutput)) {
15025 return callbackOutput.then(function() {
15026 return makePromise(value, isResolved);
15027 }, function(error) {
15028 return makePromise(error, false);
15031 return makePromise(value, isResolved);
15041 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
15042 * This is useful when you are dealing with an object that might or might not be a promise, or if
15043 * the promise comes from a source that can't be trusted.
15045 * @param {*} value Value or a promise
15046 * @param {Function=} successCallback
15047 * @param {Function=} errorCallback
15048 * @param {Function=} progressCallback
15049 * @returns {Promise} Returns a promise of the passed value or promise
15053 var when = function(value, callback, errback, progressBack) {
15054 var result = new Deferred();
15055 result.resolve(value);
15056 return result.promise.then(callback, errback, progressBack);
15065 * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6.
15067 * @param {*} value Value or a promise
15068 * @param {Function=} successCallback
15069 * @param {Function=} errorCallback
15070 * @param {Function=} progressCallback
15071 * @returns {Promise} Returns a promise of the passed value or promise
15073 var resolve = when;
15081 * Combines multiple promises into a single promise that is resolved when all of the input
15082 * promises are resolved.
15084 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
15085 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values,
15086 * each value corresponding to the promise at the same index/key in the `promises` array/hash.
15087 * If any of the promises is resolved with a rejection, this resulting promise will be rejected
15088 * with the same rejection value.
15091 function all(promises) {
15092 var deferred = new Deferred(),
15094 results = isArray(promises) ? [] : {};
15096 forEach(promises, function(promise, key) {
15098 when(promise).then(function(value) {
15099 if (results.hasOwnProperty(key)) return;
15100 results[key] = value;
15101 if (!(--counter)) deferred.resolve(results);
15102 }, function(reason) {
15103 if (results.hasOwnProperty(key)) return;
15104 deferred.reject(reason);
15108 if (counter === 0) {
15109 deferred.resolve(results);
15112 return deferred.promise;
15115 var $Q = function Q(resolver) {
15116 if (!isFunction(resolver)) {
15117 throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver);
15120 if (!(this instanceof Q)) {
15121 // More useful when $Q is the Promise itself.
15122 return new Q(resolver);
15125 var deferred = new Deferred();
15127 function resolveFn(value) {
15128 deferred.resolve(value);
15131 function rejectFn(reason) {
15132 deferred.reject(reason);
15135 resolver(resolveFn, rejectFn);
15137 return deferred.promise;
15141 $Q.reject = reject;
15143 $Q.resolve = resolve;
15149 function $$RAFProvider() { //rAF
15150 this.$get = ['$window', '$timeout', function($window, $timeout) {
15151 var requestAnimationFrame = $window.requestAnimationFrame ||
15152 $window.webkitRequestAnimationFrame;
15154 var cancelAnimationFrame = $window.cancelAnimationFrame ||
15155 $window.webkitCancelAnimationFrame ||
15156 $window.webkitCancelRequestAnimationFrame;
15158 var rafSupported = !!requestAnimationFrame;
15159 var raf = rafSupported
15161 var id = requestAnimationFrame(fn);
15162 return function() {
15163 cancelAnimationFrame(id);
15167 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666
15168 return function() {
15169 $timeout.cancel(timer);
15173 raf.supported = rafSupported;
15182 * The design decisions behind the scope are heavily favored for speed and memory consumption.
15184 * The typical use of scope is to watch the expressions, which most of the time return the same
15185 * value as last time so we optimize the operation.
15187 * Closures construction is expensive in terms of speed as well as memory:
15188 * - No closures, instead use prototypical inheritance for API
15189 * - Internal state needs to be stored on scope directly, which means that private state is
15190 * exposed as $$____ properties
15192 * Loop operations are optimized by using while(count--) { ... }
15193 * - This means that in order to keep the same order of execution as addition we have to add
15194 * items to the array at the beginning (unshift) instead of at the end (push)
15196 * Child scopes are created and removed often
15197 * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists
15199 * There are fewer watches than observers. This is why you don't want the observer to be implemented
15200 * in the same way as watch. Watch requires return of the initialization function which is expensive
15207 * @name $rootScopeProvider
15210 * Provider for the $rootScope service.
15215 * @name $rootScopeProvider#digestTtl
15218 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and
15219 * assuming that the model is unstable.
15221 * The current default is 10 iterations.
15223 * In complex applications it's possible that the dependencies between `$watch`s will result in
15224 * several digest iterations. However if an application needs more than the default 10 digest
15225 * iterations for its model to stabilize then you should investigate what is causing the model to
15226 * continuously change during the digest.
15228 * Increasing the TTL could have performance implications, so you should not change it without
15229 * proper justification.
15231 * @param {number} limit The number of digest iterations.
15240 * Every application has a single root {@link ng.$rootScope.Scope scope}.
15241 * All other scopes are descendant scopes of the root scope. Scopes provide separation
15242 * between the model and the view, via a mechanism for watching the model for changes.
15243 * They also provide event emission/broadcast and subscription facility. See the
15244 * {@link guide/scope developer guide on scopes}.
15246 function $RootScopeProvider() {
15248 var $rootScopeMinErr = minErr('$rootScope');
15249 var lastDirtyWatch = null;
15250 var applyAsyncId = null;
15252 this.digestTtl = function(value) {
15253 if (arguments.length) {
15259 function createChildScopeClass(parent) {
15260 function ChildScope() {
15261 this.$$watchers = this.$$nextSibling =
15262 this.$$childHead = this.$$childTail = null;
15263 this.$$listeners = {};
15264 this.$$listenerCount = {};
15265 this.$$watchersCount = 0;
15266 this.$id = nextUid();
15267 this.$$ChildScope = null;
15269 ChildScope.prototype = parent;
15273 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser',
15274 function($injector, $exceptionHandler, $parse, $browser) {
15276 function destroyChildScope($event) {
15277 $event.currentScope.$$destroyed = true;
15280 function cleanUpScope($scope) {
15283 // There is a memory leak in IE9 if all child scopes are not disconnected
15284 // completely when a scope is destroyed. So this code will recurse up through
15285 // all this scopes children
15287 // See issue https://github.com/angular/angular.js/issues/10706
15288 $scope.$$childHead && cleanUpScope($scope.$$childHead);
15289 $scope.$$nextSibling && cleanUpScope($scope.$$nextSibling);
15292 // The code below works around IE9 and V8's memory leaks
15295 // - https://code.google.com/p/v8/issues/detail?id=2073#c26
15296 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909
15297 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
15299 $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead =
15300 $scope.$$childTail = $scope.$root = $scope.$$watchers = null;
15305 * @name $rootScope.Scope
15308 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
15309 * {@link auto.$injector $injector}. Child scopes are created using the
15310 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
15311 * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for
15312 * an in-depth introduction and usage examples.
15316 * A scope can inherit from a parent scope, as in this example:
15318 var parent = $rootScope;
15319 var child = parent.$new();
15321 parent.salutation = "Hello";
15322 expect(child.salutation).toEqual('Hello');
15324 child.salutation = "Welcome";
15325 expect(child.salutation).toEqual('Welcome');
15326 expect(parent.salutation).toEqual('Hello');
15329 * When interacting with `Scope` in tests, additional helper methods are available on the
15330 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional
15334 * @param {Object.<string, function()>=} providers Map of service factory which need to be
15335 * provided for the current scope. Defaults to {@link ng}.
15336 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should
15337 * append/override services provided by `providers`. This is handy
15338 * when unit-testing and having the need to override a default
15340 * @returns {Object} Newly created scope.
15344 this.$id = nextUid();
15345 this.$$phase = this.$parent = this.$$watchers =
15346 this.$$nextSibling = this.$$prevSibling =
15347 this.$$childHead = this.$$childTail = null;
15349 this.$$destroyed = false;
15350 this.$$listeners = {};
15351 this.$$listenerCount = {};
15352 this.$$watchersCount = 0;
15353 this.$$isolateBindings = null;
15358 * @name $rootScope.Scope#$id
15361 * Unique scope ID (monotonically increasing) useful for debugging.
15366 * @name $rootScope.Scope#$parent
15369 * Reference to the parent scope.
15374 * @name $rootScope.Scope#$root
15377 * Reference to the root scope.
15380 Scope.prototype = {
15381 constructor: Scope,
15384 * @name $rootScope.Scope#$new
15388 * Creates a new child {@link ng.$rootScope.Scope scope}.
15390 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event.
15391 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
15393 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is
15394 * desired for the scope and its child scopes to be permanently detached from the parent and
15395 * thus stop participating in model change detection and listener notification by invoking.
15397 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the
15398 * parent scope. The scope is isolated, as it can not see parent scope properties.
15399 * When creating widgets, it is useful for the widget to not accidentally read parent
15402 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent`
15403 * of the newly created scope. Defaults to `this` scope if not provided.
15404 * This is used when creating a transclude scope to correctly place it
15405 * in the scope hierarchy while maintaining the correct prototypical
15408 * @returns {Object} The newly created child scope.
15411 $new: function(isolate, parent) {
15414 parent = parent || this;
15417 child = new Scope();
15418 child.$root = this.$root;
15420 // Only create a child scope class if somebody asks for one,
15421 // but cache it to allow the VM to optimize lookups.
15422 if (!this.$$ChildScope) {
15423 this.$$ChildScope = createChildScopeClass(this);
15425 child = new this.$$ChildScope();
15427 child.$parent = parent;
15428 child.$$prevSibling = parent.$$childTail;
15429 if (parent.$$childHead) {
15430 parent.$$childTail.$$nextSibling = child;
15431 parent.$$childTail = child;
15433 parent.$$childHead = parent.$$childTail = child;
15436 // When the new scope is not isolated or we inherit from `this`, and
15437 // the parent scope is destroyed, the property `$$destroyed` is inherited
15438 // prototypically. In all other cases, this property needs to be set
15439 // when the parent scope is destroyed.
15440 // The listener needs to be added after the parent is set
15441 if (isolate || parent != this) child.$on('$destroy', destroyChildScope);
15448 * @name $rootScope.Scope#$watch
15452 * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
15454 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest
15455 * $digest()} and should return the value that will be watched. (`watchExpression` should not change
15456 * its value when executed multiple times with the same input because it may be executed multiple
15457 * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be
15458 * [idempotent](http://en.wikipedia.org/wiki/Idempotence).
15459 * - The `listener` is called only when the value from the current `watchExpression` and the
15460 * previous call to `watchExpression` are not equal (with the exception of the initial run,
15461 * see below). Inequality is determined according to reference inequality,
15462 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators)
15463 * via the `!==` Javascript operator, unless `objectEquality == true`
15465 * - When `objectEquality == true`, inequality of the `watchExpression` is determined
15466 * according to the {@link angular.equals} function. To save the value of the object for
15467 * later comparison, the {@link angular.copy} function is used. This therefore means that
15468 * watching complex objects will have adverse memory and performance implications.
15469 * - The watch `listener` may change the model, which may trigger other `listener`s to fire.
15470 * This is achieved by rerunning the watchers until no changes are detected. The rerun
15471 * iteration limit is 10 to prevent an infinite loop deadlock.
15474 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
15475 * you can register a `watchExpression` function with no `listener`. (Be prepared for
15476 * multiple calls to your `watchExpression` because it will execute multiple times in a
15477 * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.)
15479 * After a watcher is registered with the scope, the `listener` fn is called asynchronously
15480 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
15481 * watcher. In rare cases, this is undesirable because the listener is called when the result
15482 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
15483 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the
15484 * listener was called due to initialization.
15490 // let's assume that scope was dependency injected as the $rootScope
15491 var scope = $rootScope;
15492 scope.name = 'misko';
15495 expect(scope.counter).toEqual(0);
15496 scope.$watch('name', function(newValue, oldValue) {
15497 scope.counter = scope.counter + 1;
15499 expect(scope.counter).toEqual(0);
15502 // the listener is always called during the first $digest loop after it was registered
15503 expect(scope.counter).toEqual(1);
15506 // but now it will not be called unless the value changes
15507 expect(scope.counter).toEqual(1);
15509 scope.name = 'adam';
15511 expect(scope.counter).toEqual(2);
15515 // Using a function as a watchExpression
15517 scope.foodCounter = 0;
15518 expect(scope.foodCounter).toEqual(0);
15520 // This function returns the value being watched. It is called for each turn of the $digest loop
15521 function() { return food; },
15522 // This is the change listener, called when the value returned from the above function changes
15523 function(newValue, oldValue) {
15524 if ( newValue !== oldValue ) {
15525 // Only increment the counter if the value changed
15526 scope.foodCounter = scope.foodCounter + 1;
15530 // No digest has been run so the counter will be zero
15531 expect(scope.foodCounter).toEqual(0);
15533 // Run the digest but since food has not changed count will still be zero
15535 expect(scope.foodCounter).toEqual(0);
15537 // Update food and run digest. Now the counter will increment
15538 food = 'cheeseburger';
15540 expect(scope.foodCounter).toEqual(1);
15546 * @param {(function()|string)} watchExpression Expression that is evaluated on each
15547 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers
15548 * a call to the `listener`.
15550 * - `string`: Evaluated as {@link guide/expression expression}
15551 * - `function(scope)`: called with current `scope` as a parameter.
15552 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value
15553 * of `watchExpression` changes.
15555 * - `newVal` contains the current value of the `watchExpression`
15556 * - `oldVal` contains the previous value of the `watchExpression`
15557 * - `scope` refers to the current scope
15558 * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of
15559 * comparing for reference equality.
15560 * @returns {function()} Returns a deregistration function for this listener.
15562 $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) {
15563 var get = $parse(watchExp);
15565 if (get.$$watchDelegate) {
15566 return get.$$watchDelegate(this, listener, objectEquality, get, watchExp);
15569 array = scope.$$watchers,
15572 last: initWatchVal,
15574 exp: prettyPrintExpression || watchExp,
15575 eq: !!objectEquality
15578 lastDirtyWatch = null;
15580 if (!isFunction(listener)) {
15585 array = scope.$$watchers = [];
15587 // we use unshift since we use a while loop in $digest for speed.
15588 // the while loop reads in reverse order.
15589 array.unshift(watcher);
15590 incrementWatchersCount(this, 1);
15592 return function deregisterWatch() {
15593 if (arrayRemove(array, watcher) >= 0) {
15594 incrementWatchersCount(scope, -1);
15596 lastDirtyWatch = null;
15602 * @name $rootScope.Scope#$watchGroup
15606 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`.
15607 * If any one expression in the collection changes the `listener` is executed.
15609 * - The items in the `watchExpressions` array are observed via standard $watch operation and are examined on every
15610 * call to $digest() to see if any items changes.
15611 * - The `listener` is called whenever any expression in the `watchExpressions` array changes.
15613 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually
15614 * watched using {@link ng.$rootScope.Scope#$watch $watch()}
15616 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any
15617 * expression in `watchExpressions` changes
15618 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching
15619 * those of `watchExpression`
15620 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching
15621 * those of `watchExpression`
15622 * The `scope` refers to the current scope.
15623 * @returns {function()} Returns a de-registration function for all listeners.
15625 $watchGroup: function(watchExpressions, listener) {
15626 var oldValues = new Array(watchExpressions.length);
15627 var newValues = new Array(watchExpressions.length);
15628 var deregisterFns = [];
15630 var changeReactionScheduled = false;
15631 var firstRun = true;
15633 if (!watchExpressions.length) {
15634 // No expressions means we call the listener ASAP
15635 var shouldCall = true;
15636 self.$evalAsync(function() {
15637 if (shouldCall) listener(newValues, newValues, self);
15639 return function deregisterWatchGroup() {
15640 shouldCall = false;
15644 if (watchExpressions.length === 1) {
15645 // Special case size of one
15646 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) {
15647 newValues[0] = value;
15648 oldValues[0] = oldValue;
15649 listener(newValues, (value === oldValue) ? newValues : oldValues, scope);
15653 forEach(watchExpressions, function(expr, i) {
15654 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) {
15655 newValues[i] = value;
15656 oldValues[i] = oldValue;
15657 if (!changeReactionScheduled) {
15658 changeReactionScheduled = true;
15659 self.$evalAsync(watchGroupAction);
15662 deregisterFns.push(unwatchFn);
15665 function watchGroupAction() {
15666 changeReactionScheduled = false;
15670 listener(newValues, newValues, self);
15672 listener(newValues, oldValues, self);
15676 return function deregisterWatchGroup() {
15677 while (deregisterFns.length) {
15678 deregisterFns.shift()();
15686 * @name $rootScope.Scope#$watchCollection
15690 * Shallow watches the properties of an object and fires whenever any of the properties change
15691 * (for arrays, this implies watching the array items; for object maps, this implies watching
15692 * the properties). If a change is detected, the `listener` callback is fired.
15694 * - The `obj` collection is observed via standard $watch operation and is examined on every
15695 * call to $digest() to see if any items have been added, removed, or moved.
15696 * - The `listener` is called whenever anything within the `obj` has changed. Examples include
15697 * adding, removing, and moving items belonging to an object or array.
15702 $scope.names = ['igor', 'matias', 'misko', 'james'];
15703 $scope.dataCount = 4;
15705 $scope.$watchCollection('names', function(newNames, oldNames) {
15706 $scope.dataCount = newNames.length;
15709 expect($scope.dataCount).toEqual(4);
15712 //still at 4 ... no changes
15713 expect($scope.dataCount).toEqual(4);
15715 $scope.names.pop();
15718 //now there's been a change
15719 expect($scope.dataCount).toEqual(3);
15723 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The
15724 * expression value should evaluate to an object or an array which is observed on each
15725 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the
15726 * collection will trigger a call to the `listener`.
15728 * @param {function(newCollection, oldCollection, scope)} listener a callback function called
15729 * when a change is detected.
15730 * - The `newCollection` object is the newly modified data obtained from the `obj` expression
15731 * - The `oldCollection` object is a copy of the former collection data.
15732 * Due to performance considerations, the`oldCollection` value is computed only if the
15733 * `listener` function declares two or more arguments.
15734 * - The `scope` argument refers to the current scope.
15736 * @returns {function()} Returns a de-registration function for this listener. When the
15737 * de-registration function is executed, the internal watch operation is terminated.
15739 $watchCollection: function(obj, listener) {
15740 $watchCollectionInterceptor.$stateful = true;
15743 // the current value, updated on each dirty-check run
15745 // a shallow copy of the newValue from the last dirty-check run,
15746 // updated to match newValue during dirty-check run
15748 // a shallow copy of the newValue from when the last change happened
15750 // only track veryOldValue if the listener is asking for it
15751 var trackVeryOldValue = (listener.length > 1);
15752 var changeDetected = 0;
15753 var changeDetector = $parse(obj, $watchCollectionInterceptor);
15754 var internalArray = [];
15755 var internalObject = {};
15756 var initRun = true;
15759 function $watchCollectionInterceptor(_value) {
15761 var newLength, key, bothNaN, newItem, oldItem;
15763 // If the new value is undefined, then return undefined as the watch may be a one-time watch
15764 if (isUndefined(newValue)) return;
15766 if (!isObject(newValue)) { // if primitive
15767 if (oldValue !== newValue) {
15768 oldValue = newValue;
15771 } else if (isArrayLike(newValue)) {
15772 if (oldValue !== internalArray) {
15773 // we are transitioning from something which was not an array into array.
15774 oldValue = internalArray;
15775 oldLength = oldValue.length = 0;
15779 newLength = newValue.length;
15781 if (oldLength !== newLength) {
15782 // if lengths do not match we need to trigger change notification
15784 oldValue.length = oldLength = newLength;
15786 // copy the items to oldValue and look for changes.
15787 for (var i = 0; i < newLength; i++) {
15788 oldItem = oldValue[i];
15789 newItem = newValue[i];
15791 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
15792 if (!bothNaN && (oldItem !== newItem)) {
15794 oldValue[i] = newItem;
15798 if (oldValue !== internalObject) {
15799 // we are transitioning from something which was not an object into object.
15800 oldValue = internalObject = {};
15804 // copy the items to oldValue and look for changes.
15806 for (key in newValue) {
15807 if (hasOwnProperty.call(newValue, key)) {
15809 newItem = newValue[key];
15810 oldItem = oldValue[key];
15812 if (key in oldValue) {
15813 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
15814 if (!bothNaN && (oldItem !== newItem)) {
15816 oldValue[key] = newItem;
15820 oldValue[key] = newItem;
15825 if (oldLength > newLength) {
15826 // we used to have more keys, need to find them and destroy them.
15828 for (key in oldValue) {
15829 if (!hasOwnProperty.call(newValue, key)) {
15831 delete oldValue[key];
15836 return changeDetected;
15839 function $watchCollectionAction() {
15842 listener(newValue, newValue, self);
15844 listener(newValue, veryOldValue, self);
15847 // make a copy for the next time a collection is changed
15848 if (trackVeryOldValue) {
15849 if (!isObject(newValue)) {
15851 veryOldValue = newValue;
15852 } else if (isArrayLike(newValue)) {
15853 veryOldValue = new Array(newValue.length);
15854 for (var i = 0; i < newValue.length; i++) {
15855 veryOldValue[i] = newValue[i];
15857 } else { // if object
15859 for (var key in newValue) {
15860 if (hasOwnProperty.call(newValue, key)) {
15861 veryOldValue[key] = newValue[key];
15868 return this.$watch(changeDetector, $watchCollectionAction);
15873 * @name $rootScope.Scope#$digest
15877 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and
15878 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change
15879 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers}
15880 * until no more listeners are firing. This means that it is possible to get into an infinite
15881 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of
15882 * iterations exceeds 10.
15884 * Usually, you don't call `$digest()` directly in
15885 * {@link ng.directive:ngController controllers} or in
15886 * {@link ng.$compileProvider#directive directives}.
15887 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within
15888 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`.
15890 * If you want to be notified whenever `$digest()` is called,
15891 * you can register a `watchExpression` function with
15892 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`.
15894 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle.
15899 scope.name = 'misko';
15902 expect(scope.counter).toEqual(0);
15903 scope.$watch('name', function(newValue, oldValue) {
15904 scope.counter = scope.counter + 1;
15906 expect(scope.counter).toEqual(0);
15909 // the listener is always called during the first $digest loop after it was registered
15910 expect(scope.counter).toEqual(1);
15913 // but now it will not be called unless the value changes
15914 expect(scope.counter).toEqual(1);
15916 scope.name = 'adam';
15918 expect(scope.counter).toEqual(2);
15922 $digest: function() {
15923 var watch, value, last,
15927 next, current, target = this,
15929 logIdx, logMsg, asyncTask;
15931 beginPhase('$digest');
15932 // Check for changes to browser url that happened in sync before the call to $digest
15933 $browser.$$checkUrlChange();
15935 if (this === $rootScope && applyAsyncId !== null) {
15936 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then
15937 // cancel the scheduled $apply and flush the queue of expressions to be evaluated.
15938 $browser.defer.cancel(applyAsyncId);
15942 lastDirtyWatch = null;
15944 do { // "while dirty" loop
15948 while (asyncQueue.length) {
15950 asyncTask = asyncQueue.shift();
15951 asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals);
15953 $exceptionHandler(e);
15955 lastDirtyWatch = null;
15958 traverseScopesLoop:
15959 do { // "traverse the scopes" loop
15960 if ((watchers = current.$$watchers)) {
15961 // process our watches
15962 length = watchers.length;
15965 watch = watchers[length];
15966 // Most common watches are on primitives, in which case we can short
15967 // circuit it with === operator, only when === fails do we use .equals
15969 if ((value = watch.get(current)) !== (last = watch.last) &&
15971 ? equals(value, last)
15972 : (typeof value === 'number' && typeof last === 'number'
15973 && isNaN(value) && isNaN(last)))) {
15975 lastDirtyWatch = watch;
15976 watch.last = watch.eq ? copy(value, null) : value;
15977 watch.fn(value, ((last === initWatchVal) ? value : last), current);
15980 if (!watchLog[logIdx]) watchLog[logIdx] = [];
15981 watchLog[logIdx].push({
15982 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
15987 } else if (watch === lastDirtyWatch) {
15988 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers
15989 // have already been tested.
15991 break traverseScopesLoop;
15995 $exceptionHandler(e);
16000 // Insanity Warning: scope depth-first traversal
16001 // yes, this code is a bit crazy, but it works and we have tests to prove it!
16002 // this piece should be kept in sync with the traversal in $broadcast
16003 if (!(next = ((current.$$watchersCount && current.$$childHead) ||
16004 (current !== target && current.$$nextSibling)))) {
16005 while (current !== target && !(next = current.$$nextSibling)) {
16006 current = current.$parent;
16009 } while ((current = next));
16011 // `break traverseScopesLoop;` takes us to here
16013 if ((dirty || asyncQueue.length) && !(ttl--)) {
16015 throw $rootScopeMinErr('infdig',
16016 '{0} $digest() iterations reached. Aborting!\n' +
16017 'Watchers fired in the last 5 iterations: {1}',
16021 } while (dirty || asyncQueue.length);
16025 while (postDigestQueue.length) {
16027 postDigestQueue.shift()();
16029 $exceptionHandler(e);
16037 * @name $rootScope.Scope#$destroy
16038 * @eventType broadcast on scope being destroyed
16041 * Broadcasted when a scope and its children are being destroyed.
16043 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
16044 * clean up DOM bindings before an element is removed from the DOM.
16049 * @name $rootScope.Scope#$destroy
16053 * Removes the current scope (and all of its children) from the parent scope. Removal implies
16054 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
16055 * propagate to the current scope and its children. Removal also implies that the current
16056 * scope is eligible for garbage collection.
16058 * The `$destroy()` is usually used by directives such as
16059 * {@link ng.directive:ngRepeat ngRepeat} for managing the
16060 * unrolling of the loop.
16062 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope.
16063 * Application code can register a `$destroy` event handler that will give it a chance to
16064 * perform any necessary cleanup.
16066 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
16067 * clean up DOM bindings before an element is removed from the DOM.
16069 $destroy: function() {
16070 // We can't destroy a scope that has been already destroyed.
16071 if (this.$$destroyed) return;
16072 var parent = this.$parent;
16074 this.$broadcast('$destroy');
16075 this.$$destroyed = true;
16077 if (this === $rootScope) {
16078 //Remove handlers attached to window when $rootScope is removed
16079 $browser.$$applicationDestroyed();
16082 incrementWatchersCount(this, -this.$$watchersCount);
16083 for (var eventName in this.$$listenerCount) {
16084 decrementListenerCount(this, this.$$listenerCount[eventName], eventName);
16087 // sever all the references to parent scopes (after this cleanup, the current scope should
16088 // not be retained by any of our references and should be eligible for garbage collection)
16089 if (parent && parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
16090 if (parent && parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
16091 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
16092 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
16094 // Disable listeners, watchers and apply/digest methods
16095 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop;
16096 this.$on = this.$watch = this.$watchGroup = function() { return noop; };
16097 this.$$listeners = {};
16099 // Disconnect the next sibling to prevent `cleanUpScope` destroying those too
16100 this.$$nextSibling = null;
16101 cleanUpScope(this);
16106 * @name $rootScope.Scope#$eval
16110 * Executes the `expression` on the current scope and returns the result. Any exceptions in
16111 * the expression are propagated (uncaught). This is useful when evaluating Angular
16116 var scope = ng.$rootScope.Scope();
16120 expect(scope.$eval('a+b')).toEqual(3);
16121 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
16124 * @param {(string|function())=} expression An angular expression to be executed.
16126 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16127 * - `function(scope)`: execute the function with the current `scope` parameter.
16129 * @param {(object)=} locals Local variables object, useful for overriding values in scope.
16130 * @returns {*} The result of evaluating the expression.
16132 $eval: function(expr, locals) {
16133 return $parse(expr)(this, locals);
16138 * @name $rootScope.Scope#$evalAsync
16142 * Executes the expression on the current scope at a later point in time.
16144 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only
16147 * - it will execute after the function that scheduled the evaluation (preferably before DOM
16149 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after
16150 * `expression` execution.
16152 * Any exceptions from the execution of the expression are forwarded to the
16153 * {@link ng.$exceptionHandler $exceptionHandler} service.
16155 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle
16156 * will be scheduled. However, it is encouraged to always call code that changes the model
16157 * from within an `$apply` call. That includes code evaluated via `$evalAsync`.
16159 * @param {(string|function())=} expression An angular expression to be executed.
16161 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16162 * - `function(scope)`: execute the function with the current `scope` parameter.
16164 * @param {(object)=} locals Local variables object, useful for overriding values in scope.
16166 $evalAsync: function(expr, locals) {
16167 // if we are outside of an $digest loop and this is the first time we are scheduling async
16168 // task also schedule async auto-flush
16169 if (!$rootScope.$$phase && !asyncQueue.length) {
16170 $browser.defer(function() {
16171 if (asyncQueue.length) {
16172 $rootScope.$digest();
16177 asyncQueue.push({scope: this, expression: expr, locals: locals});
16180 $$postDigest: function(fn) {
16181 postDigestQueue.push(fn);
16186 * @name $rootScope.Scope#$apply
16190 * `$apply()` is used to execute an expression in angular from outside of the angular
16191 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries).
16192 * Because we are calling into the angular framework we need to perform proper scope life
16193 * cycle of {@link ng.$exceptionHandler exception handling},
16194 * {@link ng.$rootScope.Scope#$digest executing watches}.
16198 * # Pseudo-Code of `$apply()`
16200 function $apply(expr) {
16202 return $eval(expr);
16204 $exceptionHandler(e);
16212 * Scope's `$apply()` method transitions through the following stages:
16214 * 1. The {@link guide/expression expression} is executed using the
16215 * {@link ng.$rootScope.Scope#$eval $eval()} method.
16216 * 2. Any exceptions from the execution of the expression are forwarded to the
16217 * {@link ng.$exceptionHandler $exceptionHandler} service.
16218 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the
16219 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
16222 * @param {(string|function())=} exp An angular expression to be executed.
16224 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16225 * - `function(scope)`: execute the function with current `scope` parameter.
16227 * @returns {*} The result of evaluating the expression.
16229 $apply: function(expr) {
16231 beginPhase('$apply');
16233 return this.$eval(expr);
16238 $exceptionHandler(e);
16241 $rootScope.$digest();
16243 $exceptionHandler(e);
16251 * @name $rootScope.Scope#$applyAsync
16255 * Schedule the invocation of $apply to occur at a later time. The actual time difference
16256 * varies across browsers, but is typically around ~10 milliseconds.
16258 * This can be used to queue up multiple expressions which need to be evaluated in the same
16261 * @param {(string|function())=} exp An angular expression to be executed.
16263 * - `string`: execute using the rules as defined in {@link guide/expression expression}.
16264 * - `function(scope)`: execute the function with current `scope` parameter.
16266 $applyAsync: function(expr) {
16268 expr && applyAsyncQueue.push($applyAsyncExpression);
16269 scheduleApplyAsync();
16271 function $applyAsyncExpression() {
16278 * @name $rootScope.Scope#$on
16282 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for
16283 * discussion of event life cycle.
16285 * The event listener function format is: `function(event, args...)`. The `event` object
16286 * passed into the listener has the following attributes:
16288 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or
16290 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the
16291 * event propagates through the scope hierarchy, this property is set to null.
16292 * - `name` - `{string}`: name of the event.
16293 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel
16294 * further event propagation (available only for events that were `$emit`-ed).
16295 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag
16297 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
16299 * @param {string} name Event name to listen on.
16300 * @param {function(event, ...args)} listener Function to call when the event is emitted.
16301 * @returns {function()} Returns a deregistration function for this listener.
16303 $on: function(name, listener) {
16304 var namedListeners = this.$$listeners[name];
16305 if (!namedListeners) {
16306 this.$$listeners[name] = namedListeners = [];
16308 namedListeners.push(listener);
16310 var current = this;
16312 if (!current.$$listenerCount[name]) {
16313 current.$$listenerCount[name] = 0;
16315 current.$$listenerCount[name]++;
16316 } while ((current = current.$parent));
16319 return function() {
16320 var indexOfListener = namedListeners.indexOf(listener);
16321 if (indexOfListener !== -1) {
16322 namedListeners[indexOfListener] = null;
16323 decrementListenerCount(self, 1, name);
16331 * @name $rootScope.Scope#$emit
16335 * Dispatches an event `name` upwards through the scope hierarchy notifying the
16336 * registered {@link ng.$rootScope.Scope#$on} listeners.
16338 * The event life cycle starts at the scope on which `$emit` was called. All
16339 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
16340 * notified. Afterwards, the event traverses upwards toward the root scope and calls all
16341 * registered listeners along the way. The event will stop propagating if one of the listeners
16344 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
16345 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
16347 * @param {string} name Event name to emit.
16348 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
16349 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}).
16351 $emit: function(name, args) {
16355 stopPropagation = false,
16358 targetScope: scope,
16359 stopPropagation: function() {stopPropagation = true;},
16360 preventDefault: function() {
16361 event.defaultPrevented = true;
16363 defaultPrevented: false
16365 listenerArgs = concat([event], arguments, 1),
16369 namedListeners = scope.$$listeners[name] || empty;
16370 event.currentScope = scope;
16371 for (i = 0, length = namedListeners.length; i < length; i++) {
16373 // if listeners were deregistered, defragment the array
16374 if (!namedListeners[i]) {
16375 namedListeners.splice(i, 1);
16381 //allow all listeners attached to the current scope to run
16382 namedListeners[i].apply(null, listenerArgs);
16384 $exceptionHandler(e);
16387 //if any listener on the current scope stops propagation, prevent bubbling
16388 if (stopPropagation) {
16389 event.currentScope = null;
16393 scope = scope.$parent;
16396 event.currentScope = null;
16404 * @name $rootScope.Scope#$broadcast
16408 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the
16409 * registered {@link ng.$rootScope.Scope#$on} listeners.
16411 * The event life cycle starts at the scope on which `$broadcast` was called. All
16412 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
16413 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current
16414 * scope and calls all registered listeners along the way. The event cannot be canceled.
16416 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
16417 * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
16419 * @param {string} name Event name to broadcast.
16420 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
16421 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
16423 $broadcast: function(name, args) {
16429 targetScope: target,
16430 preventDefault: function() {
16431 event.defaultPrevented = true;
16433 defaultPrevented: false
16436 if (!target.$$listenerCount[name]) return event;
16438 var listenerArgs = concat([event], arguments, 1),
16439 listeners, i, length;
16441 //down while you can, then up and next sibling or up and next sibling until back at root
16442 while ((current = next)) {
16443 event.currentScope = current;
16444 listeners = current.$$listeners[name] || [];
16445 for (i = 0, length = listeners.length; i < length; i++) {
16446 // if listeners were deregistered, defragment the array
16447 if (!listeners[i]) {
16448 listeners.splice(i, 1);
16455 listeners[i].apply(null, listenerArgs);
16457 $exceptionHandler(e);
16461 // Insanity Warning: scope depth-first traversal
16462 // yes, this code is a bit crazy, but it works and we have tests to prove it!
16463 // this piece should be kept in sync with the traversal in $digest
16464 // (though it differs due to having the extra check for $$listenerCount)
16465 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
16466 (current !== target && current.$$nextSibling)))) {
16467 while (current !== target && !(next = current.$$nextSibling)) {
16468 current = current.$parent;
16473 event.currentScope = null;
16478 var $rootScope = new Scope();
16480 //The internal queues. Expose them on the $rootScope for debugging/testing purposes.
16481 var asyncQueue = $rootScope.$$asyncQueue = [];
16482 var postDigestQueue = $rootScope.$$postDigestQueue = [];
16483 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = [];
16488 function beginPhase(phase) {
16489 if ($rootScope.$$phase) {
16490 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase);
16493 $rootScope.$$phase = phase;
16496 function clearPhase() {
16497 $rootScope.$$phase = null;
16500 function incrementWatchersCount(current, count) {
16502 current.$$watchersCount += count;
16503 } while ((current = current.$parent));
16506 function decrementListenerCount(current, count, name) {
16508 current.$$listenerCount[name] -= count;
16510 if (current.$$listenerCount[name] === 0) {
16511 delete current.$$listenerCount[name];
16513 } while ((current = current.$parent));
16517 * function used as an initial value for watchers.
16518 * because it's unique we can easily tell it apart from other values
16520 function initWatchVal() {}
16522 function flushApplyAsync() {
16523 while (applyAsyncQueue.length) {
16525 applyAsyncQueue.shift()();
16527 $exceptionHandler(e);
16530 applyAsyncId = null;
16533 function scheduleApplyAsync() {
16534 if (applyAsyncId === null) {
16535 applyAsyncId = $browser.defer(function() {
16536 $rootScope.$apply(flushApplyAsync);
16545 * Private service to sanitize uris for links and images. Used by $compile and $sanitize.
16547 function $$SanitizeUriProvider() {
16548 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/,
16549 imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/;
16553 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
16554 * urls during a[href] sanitization.
16556 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
16558 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
16559 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
16560 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
16561 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
16563 * @param {RegExp=} regexp New regexp to whitelist urls with.
16564 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
16565 * chaining otherwise.
16567 this.aHrefSanitizationWhitelist = function(regexp) {
16568 if (isDefined(regexp)) {
16569 aHrefSanitizationWhitelist = regexp;
16572 return aHrefSanitizationWhitelist;
16578 * Retrieves or overrides the default regular expression that is used for whitelisting of safe
16579 * urls during img[src] sanitization.
16581 * The sanitization is a security measure aimed at prevent XSS attacks via html links.
16583 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
16584 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
16585 * regular expression. If a match is found, the original url is written into the dom. Otherwise,
16586 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
16588 * @param {RegExp=} regexp New regexp to whitelist urls with.
16589 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
16590 * chaining otherwise.
16592 this.imgSrcSanitizationWhitelist = function(regexp) {
16593 if (isDefined(regexp)) {
16594 imgSrcSanitizationWhitelist = regexp;
16597 return imgSrcSanitizationWhitelist;
16600 this.$get = function() {
16601 return function sanitizeUri(uri, isImage) {
16602 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
16604 normalizedVal = urlResolve(uri).href;
16605 if (normalizedVal !== '' && !normalizedVal.match(regex)) {
16606 return 'unsafe:' + normalizedVal;
16613 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
16614 * Any commits to this file should be reviewed with security in mind. *
16615 * Changes to this file can potentially create security vulnerabilities. *
16616 * An approval from 2 Core members with history of modifying *
16617 * this file is required. *
16619 * Does the change somehow allow for arbitrary javascript to be executed? *
16620 * Or allows for someone to change the prototype of built-in objects? *
16621 * Or gives undesired access to variables likes document or window? *
16622 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
16624 var $sceMinErr = minErr('$sce');
16626 var SCE_CONTEXTS = {
16630 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a
16631 // url. (e.g. ng-include, script src, templateUrl)
16632 RESOURCE_URL: 'resourceUrl',
16636 // Helper functions follow.
16638 function adjustMatcher(matcher) {
16639 if (matcher === 'self') {
16641 } else if (isString(matcher)) {
16642 // Strings match exactly except for 2 wildcards - '*' and '**'.
16643 // '*' matches any character except those from the set ':/.?&'.
16644 // '**' matches any character (like .* in a RegExp).
16645 // More than 2 *'s raises an error as it's ill defined.
16646 if (matcher.indexOf('***') > -1) {
16647 throw $sceMinErr('iwcard',
16648 'Illegal sequence *** in string matcher. String: {0}', matcher);
16650 matcher = escapeForRegexp(matcher).
16651 replace('\\*\\*', '.*').
16652 replace('\\*', '[^:/.?&;]*');
16653 return new RegExp('^' + matcher + '$');
16654 } else if (isRegExp(matcher)) {
16655 // The only other type of matcher allowed is a Regexp.
16656 // Match entire URL / disallow partial matches.
16657 // Flags are reset (i.e. no global, ignoreCase or multiline)
16658 return new RegExp('^' + matcher.source + '$');
16660 throw $sceMinErr('imatcher',
16661 'Matchers may only be "self", string patterns or RegExp objects');
16666 function adjustMatchers(matchers) {
16667 var adjustedMatchers = [];
16668 if (isDefined(matchers)) {
16669 forEach(matchers, function(matcher) {
16670 adjustedMatchers.push(adjustMatcher(matcher));
16673 return adjustedMatchers;
16679 * @name $sceDelegate
16684 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict
16685 * Contextual Escaping (SCE)} services to AngularJS.
16687 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of
16688 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is
16689 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to
16690 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things
16691 * work because `$sce` delegates to `$sceDelegate` for these operations.
16693 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service.
16695 * The default instance of `$sceDelegate` should work out of the box with little pain. While you
16696 * can override it completely to change the behavior of `$sce`, the common case would
16697 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting
16698 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as
16699 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist
16700 * $sceDelegateProvider.resourceUrlWhitelist} and {@link
16701 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
16706 * @name $sceDelegateProvider
16709 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate
16710 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure
16711 * that the URLs used for sourcing Angular templates are safe. Refer {@link
16712 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and
16713 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist}
16715 * For the general details about this service in Angular, read the main page for {@link ng.$sce
16716 * Strict Contextual Escaping (SCE)}.
16718 * **Example**: Consider the following case. <a name="example"></a>
16720 * - your app is hosted at url `http://myapp.example.com/`
16721 * - but some of your templates are hosted on other domains you control such as
16722 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc.
16723 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`.
16725 * Here is what a secure configuration for this scenario might look like:
16728 * angular.module('myApp', []).config(function($sceDelegateProvider) {
16729 * $sceDelegateProvider.resourceUrlWhitelist([
16730 * // Allow same origin resource loads.
16732 * // Allow loading from our assets domain. Notice the difference between * and **.
16733 * 'http://srv*.assets.example.com/**'
16736 * // The blacklist overrides the whitelist so the open redirect here is blocked.
16737 * $sceDelegateProvider.resourceUrlBlacklist([
16738 * 'http://myapp.example.com/clickThru**'
16744 function $SceDelegateProvider() {
16745 this.SCE_CONTEXTS = SCE_CONTEXTS;
16747 // Resource URLs can also be trusted by policy.
16748 var resourceUrlWhitelist = ['self'],
16749 resourceUrlBlacklist = [];
16753 * @name $sceDelegateProvider#resourceUrlWhitelist
16756 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value
16757 * provided. This must be an array or null. A snapshot of this array is used so further
16758 * changes to the array are ignored.
16760 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
16761 * allowed in this array.
16763 * Note: **an empty whitelist array will block all URLs**!
16765 * @return {Array} the currently set whitelist array.
16767 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only
16768 * same origin resource requests.
16771 * Sets/Gets the whitelist of trusted resource URLs.
16773 this.resourceUrlWhitelist = function(value) {
16774 if (arguments.length) {
16775 resourceUrlWhitelist = adjustMatchers(value);
16777 return resourceUrlWhitelist;
16782 * @name $sceDelegateProvider#resourceUrlBlacklist
16785 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value
16786 * provided. This must be an array or null. A snapshot of this array is used so further
16787 * changes to the array are ignored.
16789 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
16790 * allowed in this array.
16792 * The typical usage for the blacklist is to **block
16793 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as
16794 * these would otherwise be trusted but actually return content from the redirected domain.
16796 * Finally, **the blacklist overrides the whitelist** and has the final say.
16798 * @return {Array} the currently set blacklist array.
16800 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there
16801 * is no blacklist.)
16804 * Sets/Gets the blacklist of trusted resource URLs.
16807 this.resourceUrlBlacklist = function(value) {
16808 if (arguments.length) {
16809 resourceUrlBlacklist = adjustMatchers(value);
16811 return resourceUrlBlacklist;
16814 this.$get = ['$injector', function($injector) {
16816 var htmlSanitizer = function htmlSanitizer(html) {
16817 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
16820 if ($injector.has('$sanitize')) {
16821 htmlSanitizer = $injector.get('$sanitize');
16825 function matchUrl(matcher, parsedUrl) {
16826 if (matcher === 'self') {
16827 return urlIsSameOrigin(parsedUrl);
16829 // definitely a regex. See adjustMatchers()
16830 return !!matcher.exec(parsedUrl.href);
16834 function isResourceUrlAllowedByPolicy(url) {
16835 var parsedUrl = urlResolve(url.toString());
16836 var i, n, allowed = false;
16837 // Ensure that at least one item from the whitelist allows this url.
16838 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) {
16839 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) {
16845 // Ensure that no item from the blacklist blocked this url.
16846 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) {
16847 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) {
16856 function generateHolderType(Base) {
16857 var holderType = function TrustedValueHolderType(trustedValue) {
16858 this.$$unwrapTrustedValue = function() {
16859 return trustedValue;
16863 holderType.prototype = new Base();
16865 holderType.prototype.valueOf = function sceValueOf() {
16866 return this.$$unwrapTrustedValue();
16868 holderType.prototype.toString = function sceToString() {
16869 return this.$$unwrapTrustedValue().toString();
16874 var trustedValueHolderBase = generateHolderType(),
16877 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase);
16878 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase);
16879 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase);
16880 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase);
16881 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]);
16885 * @name $sceDelegate#trustAs
16888 * Returns an object that is trusted by angular for use in specified strict
16889 * contextual escaping contexts (such as ng-bind-html, ng-include, any src
16890 * attribute interpolation, any dom event binding attribute interpolation
16891 * such as for onclick, etc.) that uses the provided value.
16892 * See {@link ng.$sce $sce} for enabling strict contextual escaping.
16894 * @param {string} type The kind of context in which this value is safe for use. e.g. url,
16895 * resourceUrl, html, js and css.
16896 * @param {*} value The value that that should be considered trusted/safe.
16897 * @returns {*} A value that can be used to stand in for the provided `value` in places
16898 * where Angular expects a $sce.trustAs() return value.
16900 function trustAs(type, trustedValue) {
16901 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
16902 if (!Constructor) {
16903 throw $sceMinErr('icontext',
16904 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}',
16905 type, trustedValue);
16907 if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') {
16908 return trustedValue;
16910 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting
16911 // mutable objects, we ensure here that the value passed in is actually a string.
16912 if (typeof trustedValue !== 'string') {
16913 throw $sceMinErr('itype',
16914 'Attempted to trust a non-string value in a content requiring a string: Context: {0}',
16917 return new Constructor(trustedValue);
16922 * @name $sceDelegate#valueOf
16925 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs
16926 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link
16927 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}.
16929 * If the passed parameter is not a value that had been returned by {@link
16930 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is.
16932 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}
16933 * call or anything else.
16934 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs
16935 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns
16936 * `value` unchanged.
16938 function valueOf(maybeTrusted) {
16939 if (maybeTrusted instanceof trustedValueHolderBase) {
16940 return maybeTrusted.$$unwrapTrustedValue();
16942 return maybeTrusted;
16948 * @name $sceDelegate#getTrusted
16951 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and
16952 * returns the originally supplied value if the queried context type is a supertype of the
16953 * created type. If this condition isn't satisfied, throws an exception.
16955 * @param {string} type The kind of context in which this value is to be used.
16956 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs
16957 * `$sceDelegate.trustAs`} call.
16958 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs
16959 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception.
16961 function getTrusted(type, maybeTrusted) {
16962 if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') {
16963 return maybeTrusted;
16965 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
16966 if (constructor && maybeTrusted instanceof constructor) {
16967 return maybeTrusted.$$unwrapTrustedValue();
16969 // If we get here, then we may only take one of two actions.
16970 // 1. sanitize the value for the requested type, or
16971 // 2. throw an exception.
16972 if (type === SCE_CONTEXTS.RESOURCE_URL) {
16973 if (isResourceUrlAllowedByPolicy(maybeTrusted)) {
16974 return maybeTrusted;
16976 throw $sceMinErr('insecurl',
16977 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}',
16978 maybeTrusted.toString());
16980 } else if (type === SCE_CONTEXTS.HTML) {
16981 return htmlSanitizer(maybeTrusted);
16983 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
16986 return { trustAs: trustAs,
16987 getTrusted: getTrusted,
16988 valueOf: valueOf };
16995 * @name $sceProvider
16998 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service.
16999 * - enable/disable Strict Contextual Escaping (SCE) in a module
17000 * - override the default implementation with a custom delegate
17002 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}.
17005 /* jshint maxlen: false*/
17014 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS.
17016 * # Strict Contextual Escaping
17018 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain
17019 * contexts to result in a value that is marked as safe to use for that context. One example of
17020 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer
17021 * to these contexts as privileged or SCE contexts.
17023 * As of version 1.2, Angular ships with SCE enabled by default.
17025 * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow
17026 * one to execute arbitrary javascript by the use of the expression() syntax. Refer
17027 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
17028 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>`
17029 * to the top of your HTML document.
17031 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for
17032 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier.
17034 * Here's an example of a binding in a privileged context:
17037 * <input ng-model="userHtml" aria-label="User input">
17038 * <div ng-bind-html="userHtml"></div>
17041 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE
17042 * disabled, this application allows the user to render arbitrary HTML into the DIV.
17043 * In a more realistic example, one may be rendering user comments, blog articles, etc. via
17044 * bindings. (HTML is just one example of a context where rendering user controlled input creates
17045 * security vulnerabilities.)
17047 * For the case of HTML, you might use a library, either on the client side, or on the server side,
17048 * to sanitize unsafe HTML before binding to the value and rendering it in the document.
17050 * How would you ensure that every place that used these types of bindings was bound to a value that
17051 * was sanitized by your library (or returned as safe for rendering by your server?) How can you
17052 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
17053 * properties/fields and forgot to update the binding to the sanitized value?
17055 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can
17056 * determine that something explicitly says it's safe to use a value for binding in that
17057 * context. You can then audit your code (a simple grep would do) to ensure that this is only done
17058 * for those values that you can easily tell are safe - because they were received from your server,
17059 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps
17060 * allowing only the files in a specific directory to do this. Ensuring that the internal API
17061 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task.
17063 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs}
17064 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to
17065 * obtain values that will be accepted by SCE / privileged contexts.
17068 * ## How does it work?
17070 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted
17071 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link
17072 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the
17073 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals.
17075 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link
17076 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly
17080 * var ngBindHtmlDirective = ['$sce', function($sce) {
17081 * return function(scope, element, attr) {
17082 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
17083 * element.html(value || '');
17089 * ## Impact on loading templates
17091 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as
17092 * `templateUrl`'s specified by {@link guide/directive directives}.
17094 * By default, Angular only loads templates from the same domain and protocol as the application
17095 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl
17096 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or
17097 * protocols, you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist
17098 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value.
17102 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
17103 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
17104 * policy apply in addition to this and may further restrict whether the template is successfully
17105 * loaded. This means that without the right CORS policy, loading templates from a different domain
17106 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some
17109 * ## This feels like too much overhead
17111 * It's important to remember that SCE only applies to interpolation expressions.
17113 * If your expressions are constant literals, they're automatically trusted and you don't need to
17114 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g.
17115 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works.
17117 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them
17118 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here.
17120 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load
17121 * templates in `ng-include` from your application's domain without having to even know about SCE.
17122 * It blocks loading templates from other domains or loading templates over http from an https
17123 * served document. You can change these by setting your own custom {@link
17124 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link
17125 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs.
17127 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an
17128 * application that's secure and can be audited to verify that with much more ease than bolting
17129 * security onto an application later.
17131 * <a name="contexts"></a>
17132 * ## What trusted context types are supported?
17134 * | Context | Notes |
17135 * |---------------------|----------------|
17136 * | `$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. |
17137 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. |
17138 * | `$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. |
17139 * | `$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. |
17140 * | `$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. |
17142 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a>
17144 * Each element in these arrays must be one of the following:
17147 * - The special **string**, `'self'`, can be used to match against all URLs of the **same
17148 * domain** as the application document using the **same protocol**.
17149 * - **String** (except the special value `'self'`)
17150 * - The string is matched against the full *normalized / absolute URL* of the resource
17151 * being tested (substring matches are not good enough.)
17152 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters
17153 * match themselves.
17154 * - `*`: matches zero or more occurrences of any character other than one of the following 6
17155 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use
17157 * - `**`: matches zero or more occurrences of *any* character. As such, it's not
17158 * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g.
17159 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might
17160 * not have been the intention.) Its usage at the very end of the path is ok. (e.g.
17161 * http://foo.example.com/templates/**).
17162 * - **RegExp** (*see caveat below*)
17163 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax
17164 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to
17165 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should
17166 * have good test coverage). For instance, the use of `.` in the regex is correct only in a
17167 * small number of cases. A `.` character in the regex used when matching the scheme or a
17168 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It
17169 * is highly recommended to use the string patterns and only fall back to regular expressions
17170 * as a last resort.
17171 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is
17172 * matched against the **entire** *normalized / absolute URL* of the resource being tested
17173 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags
17174 * present on the RegExp (such as multiline, global, ignoreCase) are ignored.
17175 * - If you are generating your JavaScript from some other templating engine (not
17176 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)),
17177 * remember to escape your regular expression (and be aware that you might need more than
17178 * one level of escaping depending on your templating engine and the way you interpolated
17179 * the value.) Do make use of your platform's escaping mechanism as it might be good
17180 * enough before coding your own. E.g. Ruby has
17181 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape)
17182 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape).
17183 * Javascript lacks a similar built in function for escaping. Take a look at Google
17184 * Closure library's [goog.string.regExpEscape(s)](
17185 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962).
17187 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example.
17189 * ## Show me an example using SCE.
17191 * <example module="mySceApp" deps="angular-sanitize.js">
17192 * <file name="index.html">
17193 * <div ng-controller="AppController as myCtrl">
17194 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br>
17195 * <b>User comments</b><br>
17196 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when
17197 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an
17199 * <div class="well">
17200 * <div ng-repeat="userComment in myCtrl.userComments">
17201 * <b>{{userComment.name}}</b>:
17202 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span>
17209 * <file name="script.js">
17210 * angular.module('mySceApp', ['ngSanitize'])
17211 * .controller('AppController', ['$http', '$templateCache', '$sce',
17212 * function($http, $templateCache, $sce) {
17214 * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) {
17215 * self.userComments = userComments;
17217 * self.explicitlyTrustedHtml = $sce.trustAsHtml(
17218 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' +
17219 * 'sanitization."">Hover over this text.</span>');
17223 * <file name="test_data.json">
17225 * { "name": "Alice",
17227 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>"
17230 * "htmlComment": "<i>Yes!</i> Am I the only other one?"
17235 * <file name="protractor.js" type="protractor">
17236 * describe('SCE doc demo', function() {
17237 * it('should sanitize untrusted values', function() {
17238 * expect(element.all(by.css('.htmlComment')).first().getInnerHtml())
17239 * .toBe('<span>Is <i>anyone</i> reading this?</span>');
17242 * it('should NOT sanitize explicitly trusted values', function() {
17243 * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe(
17244 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' +
17245 * 'sanitization."">Hover over this text.</span>');
17253 * ## Can I disable SCE completely?
17255 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits
17256 * for little coding overhead. It will be much harder to take an SCE disabled application and
17257 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE
17258 * for cases where you have a lot of existing code that was written before SCE was introduced and
17259 * you're migrating them a module at a time.
17261 * That said, here's how you can completely disable SCE:
17264 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
17265 * // Completely disable SCE. For demonstration purposes only!
17266 * // Do not use in new projects.
17267 * $sceProvider.enabled(false);
17272 /* jshint maxlen: 100 */
17274 function $SceProvider() {
17275 var enabled = true;
17279 * @name $sceProvider#enabled
17282 * @param {boolean=} value If provided, then enables/disables SCE.
17283 * @return {boolean} true if SCE is enabled, false otherwise.
17286 * Enables/disables SCE and returns the current value.
17288 this.enabled = function(value) {
17289 if (arguments.length) {
17296 /* Design notes on the default implementation for SCE.
17298 * The API contract for the SCE delegate
17299 * -------------------------------------
17300 * The SCE delegate object must provide the following 3 methods:
17302 * - trustAs(contextEnum, value)
17303 * This method is used to tell the SCE service that the provided value is OK to use in the
17304 * contexts specified by contextEnum. It must return an object that will be accepted by
17305 * getTrusted() for a compatible contextEnum and return this value.
17308 * For values that were not produced by trustAs(), return them as is. For values that were
17309 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if
17310 * trustAs is wrapping the given values into some type, this operation unwraps it when given
17313 * - getTrusted(contextEnum, value)
17314 * This function should return the a value that is safe to use in the context specified by
17315 * contextEnum or throw and exception otherwise.
17317 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be
17318 * opaque or wrapped in some holder object. That happens to be an implementation detail. For
17319 * instance, an implementation could maintain a registry of all trusted objects by context. In
17320 * such a case, trustAs() would return the same object that was passed in. getTrusted() would
17321 * return the same object passed in if it was found in the registry under a compatible context or
17322 * throw an exception otherwise. An implementation might only wrap values some of the time based
17323 * on some criteria. getTrusted() might return a value and not throw an exception for special
17324 * constants or objects even if not wrapped. All such implementations fulfill this contract.
17327 * A note on the inheritance model for SCE contexts
17328 * ------------------------------------------------
17329 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This
17330 * is purely an implementation details.
17332 * The contract is simply this:
17334 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value)
17335 * will also succeed.
17337 * Inheritance happens to capture this in a natural way. In some future, we
17338 * may not use inheritance anymore. That is OK because no code outside of
17339 * sce.js and sceSpecs.js would need to be aware of this detail.
17342 this.$get = ['$parse', '$sceDelegate', function(
17343 $parse, $sceDelegate) {
17344 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow
17345 // the "expression(javascript expression)" syntax which is insecure.
17346 if (enabled && msie < 8) {
17347 throw $sceMinErr('iequirks',
17348 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' +
17349 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
17350 'document. See http://docs.angularjs.org/api/ng.$sce for more information.');
17353 var sce = shallowCopy(SCE_CONTEXTS);
17357 * @name $sce#isEnabled
17360 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you
17361 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}.
17364 * Returns a boolean indicating if SCE is enabled.
17366 sce.isEnabled = function() {
17369 sce.trustAs = $sceDelegate.trustAs;
17370 sce.getTrusted = $sceDelegate.getTrusted;
17371 sce.valueOf = $sceDelegate.valueOf;
17374 sce.trustAs = sce.getTrusted = function(type, value) { return value; };
17375 sce.valueOf = identity;
17380 * @name $sce#parseAs
17383 * Converts Angular {@link guide/expression expression} into a function. This is like {@link
17384 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it
17385 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*,
17388 * @param {string} type The kind of SCE context in which this result will be used.
17389 * @param {string} expression String expression to compile.
17390 * @returns {function(context, locals)} a function which represents the compiled expression:
17392 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17393 * are evaluated against (typically a scope object).
17394 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17397 sce.parseAs = function sceParseAs(type, expr) {
17398 var parsed = $parse(expr);
17399 if (parsed.literal && parsed.constant) {
17402 return $parse(expr, function(value) {
17403 return sce.getTrusted(type, value);
17410 * @name $sce#trustAs
17413 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such,
17414 * returns an object that is trusted by angular for use in specified strict contextual
17415 * escaping contexts (such as ng-bind-html, ng-include, any src attribute
17416 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.)
17417 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual
17420 * @param {string} type The kind of context in which this value is safe for use. e.g. url,
17421 * resourceUrl, html, js and css.
17422 * @param {*} value The value that that should be considered trusted/safe.
17423 * @returns {*} A value that can be used to stand in for the provided `value` in places
17424 * where Angular expects a $sce.trustAs() return value.
17429 * @name $sce#trustAsHtml
17432 * Shorthand method. `$sce.trustAsHtml(value)` →
17433 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`}
17435 * @param {*} value The value to trustAs.
17436 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml
17437 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives
17438 * only accept expressions that are either literal constants or are the
17439 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17444 * @name $sce#trustAsUrl
17447 * Shorthand method. `$sce.trustAsUrl(value)` →
17448 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`}
17450 * @param {*} value The value to trustAs.
17451 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl
17452 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives
17453 * only accept expressions that are either literal constants or are the
17454 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17459 * @name $sce#trustAsResourceUrl
17462 * Shorthand method. `$sce.trustAsResourceUrl(value)` →
17463 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`}
17465 * @param {*} value The value to trustAs.
17466 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl
17467 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives
17468 * only accept expressions that are either literal constants or are the return
17469 * value of {@link ng.$sce#trustAs $sce.trustAs}.)
17474 * @name $sce#trustAsJs
17477 * Shorthand method. `$sce.trustAsJs(value)` →
17478 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`}
17480 * @param {*} value The value to trustAs.
17481 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs
17482 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives
17483 * only accept expressions that are either literal constants or are the
17484 * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
17489 * @name $sce#getTrusted
17492 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such,
17493 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the
17494 * originally supplied value if the queried context type is a supertype of the created type.
17495 * If this condition isn't satisfied, throws an exception.
17497 * @param {string} type The kind of context in which this value is to be used.
17498 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`}
17500 * @returns {*} The value the was originally provided to
17501 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context.
17502 * Otherwise, throws an exception.
17507 * @name $sce#getTrustedHtml
17510 * Shorthand method. `$sce.getTrustedHtml(value)` →
17511 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`}
17513 * @param {*} value The value to pass to `$sce.getTrusted`.
17514 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)`
17519 * @name $sce#getTrustedCss
17522 * Shorthand method. `$sce.getTrustedCss(value)` →
17523 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`}
17525 * @param {*} value The value to pass to `$sce.getTrusted`.
17526 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)`
17531 * @name $sce#getTrustedUrl
17534 * Shorthand method. `$sce.getTrustedUrl(value)` →
17535 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`}
17537 * @param {*} value The value to pass to `$sce.getTrusted`.
17538 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)`
17543 * @name $sce#getTrustedResourceUrl
17546 * Shorthand method. `$sce.getTrustedResourceUrl(value)` →
17547 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`}
17549 * @param {*} value The value to pass to `$sceDelegate.getTrusted`.
17550 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)`
17555 * @name $sce#getTrustedJs
17558 * Shorthand method. `$sce.getTrustedJs(value)` →
17559 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`}
17561 * @param {*} value The value to pass to `$sce.getTrusted`.
17562 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)`
17567 * @name $sce#parseAsHtml
17570 * Shorthand method. `$sce.parseAsHtml(expression string)` →
17571 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`}
17573 * @param {string} expression String expression to compile.
17574 * @returns {function(context, locals)} a function which represents the compiled expression:
17576 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17577 * are evaluated against (typically a scope object).
17578 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17584 * @name $sce#parseAsCss
17587 * Shorthand method. `$sce.parseAsCss(value)` →
17588 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`}
17590 * @param {string} expression String expression to compile.
17591 * @returns {function(context, locals)} a function which represents the compiled expression:
17593 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17594 * are evaluated against (typically a scope object).
17595 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17601 * @name $sce#parseAsUrl
17604 * Shorthand method. `$sce.parseAsUrl(value)` →
17605 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`}
17607 * @param {string} expression String expression to compile.
17608 * @returns {function(context, locals)} a function which represents the compiled expression:
17610 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17611 * are evaluated against (typically a scope object).
17612 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17618 * @name $sce#parseAsResourceUrl
17621 * Shorthand method. `$sce.parseAsResourceUrl(value)` →
17622 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`}
17624 * @param {string} expression String expression to compile.
17625 * @returns {function(context, locals)} a function which represents the compiled expression:
17627 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17628 * are evaluated against (typically a scope object).
17629 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17635 * @name $sce#parseAsJs
17638 * Shorthand method. `$sce.parseAsJs(value)` →
17639 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`}
17641 * @param {string} expression String expression to compile.
17642 * @returns {function(context, locals)} a function which represents the compiled expression:
17644 * * `context` – `{object}` – an object against which any expressions embedded in the strings
17645 * are evaluated against (typically a scope object).
17646 * * `locals` – `{object=}` – local variables context object, useful for overriding values in
17650 // Shorthand delegations.
17651 var parse = sce.parseAs,
17652 getTrusted = sce.getTrusted,
17653 trustAs = sce.trustAs;
17655 forEach(SCE_CONTEXTS, function(enumValue, name) {
17656 var lName = lowercase(name);
17657 sce[camelCase("parse_as_" + lName)] = function(expr) {
17658 return parse(enumValue, expr);
17660 sce[camelCase("get_trusted_" + lName)] = function(value) {
17661 return getTrusted(enumValue, value);
17663 sce[camelCase("trust_as_" + lName)] = function(value) {
17664 return trustAs(enumValue, value);
17673 * !!! This is an undocumented "private" service !!!
17676 * @requires $window
17677 * @requires $document
17679 * @property {boolean} history Does the browser support html5 history api ?
17680 * @property {boolean} transitions Does the browser support CSS transition events ?
17681 * @property {boolean} animations Does the browser support CSS animation events ?
17684 * This is very simple implementation of testing browser's features.
17686 function $SnifferProvider() {
17687 this.$get = ['$window', '$document', function($window, $document) {
17688 var eventSupport = {},
17690 toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
17691 boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
17692 document = $document[0] || {},
17694 vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/,
17695 bodyStyle = document.body && document.body.style,
17696 transitions = false,
17697 animations = false,
17701 for (var prop in bodyStyle) {
17702 if (match = vendorRegex.exec(prop)) {
17703 vendorPrefix = match[0];
17704 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1);
17709 if (!vendorPrefix) {
17710 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit';
17713 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle));
17714 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle));
17716 if (android && (!transitions || !animations)) {
17717 transitions = isString(bodyStyle.webkitTransition);
17718 animations = isString(bodyStyle.webkitAnimation);
17724 // Android has history.pushState, but it does not update location correctly
17725 // so let's not use the history API at all.
17726 // http://code.google.com/p/android/issues/detail?id=17471
17727 // https://github.com/angular/angular.js/issues/904
17729 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has
17730 // so let's not use the history API also
17731 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined
17733 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee),
17735 hasEvent: function(event) {
17736 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
17737 // it. In particular the event is not fired when backspace or delete key are pressed or
17738 // when cut operation is performed.
17739 // IE10+ implements 'input' event but it erroneously fires under various situations,
17740 // e.g. when placeholder changes, or a form is focused.
17741 if (event === 'input' && msie <= 11) return false;
17743 if (isUndefined(eventSupport[event])) {
17744 var divElm = document.createElement('div');
17745 eventSupport[event] = 'on' + event in divElm;
17748 return eventSupport[event];
17751 vendorPrefix: vendorPrefix,
17752 transitions: transitions,
17753 animations: animations,
17759 var $compileMinErr = minErr('$compile');
17763 * @name $templateRequest
17766 * The `$templateRequest` service runs security checks then downloads the provided template using
17767 * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request
17768 * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the
17769 * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the
17770 * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted
17771 * when `tpl` is of type string and `$templateCache` has the matching entry.
17773 * @param {string|TrustedResourceUrl} tpl The HTTP request template URL
17774 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty
17776 * @return {Promise} a promise for the HTTP response data of the given URL.
17778 * @property {number} totalPendingRequests total amount of pending template requests being downloaded.
17780 function $TemplateRequestProvider() {
17781 this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) {
17782 function handleRequestFn(tpl, ignoreRequestError) {
17783 handleRequestFn.totalPendingRequests++;
17785 // We consider the template cache holds only trusted templates, so
17786 // there's no need to go through whitelisting again for keys that already
17787 // are included in there. This also makes Angular accept any script
17788 // directive, no matter its name. However, we still need to unwrap trusted
17790 if (!isString(tpl) || !$templateCache.get(tpl)) {
17791 tpl = $sce.getTrustedResourceUrl(tpl);
17794 var transformResponse = $http.defaults && $http.defaults.transformResponse;
17796 if (isArray(transformResponse)) {
17797 transformResponse = transformResponse.filter(function(transformer) {
17798 return transformer !== defaultHttpResponseTransform;
17800 } else if (transformResponse === defaultHttpResponseTransform) {
17801 transformResponse = null;
17804 var httpOptions = {
17805 cache: $templateCache,
17806 transformResponse: transformResponse
17809 return $http.get(tpl, httpOptions)
17810 ['finally'](function() {
17811 handleRequestFn.totalPendingRequests--;
17813 .then(function(response) {
17814 $templateCache.put(tpl, response.data);
17815 return response.data;
17818 function handleError(resp) {
17819 if (!ignoreRequestError) {
17820 throw $compileMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})',
17821 tpl, resp.status, resp.statusText);
17823 return $q.reject(resp);
17827 handleRequestFn.totalPendingRequests = 0;
17829 return handleRequestFn;
17833 function $$TestabilityProvider() {
17834 this.$get = ['$rootScope', '$browser', '$location',
17835 function($rootScope, $browser, $location) {
17838 * @name $testability
17841 * The private $$testability service provides a collection of methods for use when debugging
17842 * or by automated test and debugging tools.
17844 var testability = {};
17847 * @name $$testability#findBindings
17850 * Returns an array of elements that are bound (via ng-bind or {{}})
17851 * to expressions matching the input.
17853 * @param {Element} element The element root to search from.
17854 * @param {string} expression The binding expression to match.
17855 * @param {boolean} opt_exactMatch If true, only returns exact matches
17856 * for the expression. Filters and whitespace are ignored.
17858 testability.findBindings = function(element, expression, opt_exactMatch) {
17859 var bindings = element.getElementsByClassName('ng-binding');
17861 forEach(bindings, function(binding) {
17862 var dataBinding = angular.element(binding).data('$binding');
17864 forEach(dataBinding, function(bindingName) {
17865 if (opt_exactMatch) {
17866 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)');
17867 if (matcher.test(bindingName)) {
17868 matches.push(binding);
17871 if (bindingName.indexOf(expression) != -1) {
17872 matches.push(binding);
17882 * @name $$testability#findModels
17885 * Returns an array of elements that are two-way found via ng-model to
17886 * expressions matching the input.
17888 * @param {Element} element The element root to search from.
17889 * @param {string} expression The model expression to match.
17890 * @param {boolean} opt_exactMatch If true, only returns exact matches
17891 * for the expression.
17893 testability.findModels = function(element, expression, opt_exactMatch) {
17894 var prefixes = ['ng-', 'data-ng-', 'ng\\:'];
17895 for (var p = 0; p < prefixes.length; ++p) {
17896 var attributeEquals = opt_exactMatch ? '=' : '*=';
17897 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]';
17898 var elements = element.querySelectorAll(selector);
17899 if (elements.length) {
17906 * @name $$testability#getLocation
17909 * Shortcut for getting the location in a browser agnostic way. Returns
17910 * the path, search, and hash. (e.g. /path?a=b#hash)
17912 testability.getLocation = function() {
17913 return $location.url();
17917 * @name $$testability#setLocation
17920 * Shortcut for navigating to a location without doing a full page reload.
17922 * @param {string} url The location url (path, search and hash,
17923 * e.g. /path?a=b#hash) to go to.
17925 testability.setLocation = function(url) {
17926 if (url !== $location.url()) {
17927 $location.url(url);
17928 $rootScope.$digest();
17933 * @name $$testability#whenStable
17936 * Calls the callback when $timeout and $http requests are completed.
17938 * @param {function} callback
17940 testability.whenStable = function(callback) {
17941 $browser.notifyWhenNoOutstandingRequests(callback);
17944 return testability;
17948 function $TimeoutProvider() {
17949 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler',
17950 function($rootScope, $browser, $q, $$q, $exceptionHandler) {
17952 var deferreds = {};
17960 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch
17961 * block and delegates any exceptions to
17962 * {@link ng.$exceptionHandler $exceptionHandler} service.
17964 * The return value of calling `$timeout` is a promise, which will be resolved when
17965 * the delay has passed and the timeout function, if provided, is executed.
17967 * To cancel a timeout request, call `$timeout.cancel(promise)`.
17969 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
17970 * synchronously flush the queue of deferred functions.
17972 * If you only want a promise that will be resolved after some specified delay
17973 * then you can call `$timeout` without the `fn` function.
17975 * @param {function()=} fn A function, whose execution should be delayed.
17976 * @param {number=} [delay=0] Delay in milliseconds.
17977 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
17978 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
17979 * @param {...*=} Pass additional parameters to the executed function.
17980 * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this
17981 * promise will be resolved with is the return value of the `fn` function.
17984 function timeout(fn, delay, invokeApply) {
17985 if (!isFunction(fn)) {
17986 invokeApply = delay;
17991 var args = sliceArgs(arguments, 3),
17992 skipApply = (isDefined(invokeApply) && !invokeApply),
17993 deferred = (skipApply ? $$q : $q).defer(),
17994 promise = deferred.promise,
17997 timeoutId = $browser.defer(function() {
17999 deferred.resolve(fn.apply(null, args));
18001 deferred.reject(e);
18002 $exceptionHandler(e);
18005 delete deferreds[promise.$$timeoutId];
18008 if (!skipApply) $rootScope.$apply();
18011 promise.$$timeoutId = timeoutId;
18012 deferreds[timeoutId] = deferred;
18020 * @name $timeout#cancel
18023 * Cancels a task associated with the `promise`. As a result of this, the promise will be
18024 * resolved with a rejection.
18026 * @param {Promise=} promise Promise returned by the `$timeout` function.
18027 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
18030 timeout.cancel = function(promise) {
18031 if (promise && promise.$$timeoutId in deferreds) {
18032 deferreds[promise.$$timeoutId].reject('canceled');
18033 delete deferreds[promise.$$timeoutId];
18034 return $browser.defer.cancel(promise.$$timeoutId);
18043 // NOTE: The usage of window and document instead of $window and $document here is
18044 // deliberate. This service depends on the specific behavior of anchor nodes created by the
18045 // browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and
18046 // cause us to break tests. In addition, when the browser resolves a URL for XHR, it
18047 // doesn't know about mocked locations and resolves URLs to the real document - which is
18048 // exactly the behavior needed here. There is little value is mocking these out for this
18050 var urlParsingNode = document.createElement("a");
18051 var originUrl = urlResolve(window.location.href);
18056 * Implementation Notes for non-IE browsers
18057 * ----------------------------------------
18058 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM,
18059 * results both in the normalizing and parsing of the URL. Normalizing means that a relative
18060 * URL will be resolved into an absolute URL in the context of the application document.
18061 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related
18062 * properties are all populated to reflect the normalized URL. This approach has wide
18063 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See
18064 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
18066 * Implementation Notes for IE
18067 * ---------------------------
18068 * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other
18069 * browsers. However, the parsed components will not be set if the URL assigned did not specify
18070 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We
18071 * work around that by performing the parsing in a 2nd step by taking a previously normalized
18072 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the
18073 * properties such as protocol, hostname, port, etc.
18076 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement
18077 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
18078 * http://url.spec.whatwg.org/#urlutils
18079 * https://github.com/angular/angular.js/pull/2902
18080 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/
18083 * @param {string} url The URL to be parsed.
18084 * @description Normalizes and parses a URL.
18085 * @returns {object} Returns the normalized URL as a dictionary.
18087 * | member name | Description |
18088 * |---------------|----------------|
18089 * | href | A normalized version of the provided URL if it was not an absolute URL |
18090 * | protocol | The protocol including the trailing colon |
18091 * | host | The host and port (if the port is non-default) of the normalizedUrl |
18092 * | search | The search params, minus the question mark |
18093 * | hash | The hash string, minus the hash symbol
18094 * | hostname | The hostname
18095 * | port | The port, without ":"
18096 * | pathname | The pathname, beginning with "/"
18099 function urlResolve(url) {
18103 // Normalize before parse. Refer Implementation Notes on why this is
18104 // done in two steps on IE.
18105 urlParsingNode.setAttribute("href", href);
18106 href = urlParsingNode.href;
18109 urlParsingNode.setAttribute('href', href);
18111 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
18113 href: urlParsingNode.href,
18114 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
18115 host: urlParsingNode.host,
18116 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '',
18117 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
18118 hostname: urlParsingNode.hostname,
18119 port: urlParsingNode.port,
18120 pathname: (urlParsingNode.pathname.charAt(0) === '/')
18121 ? urlParsingNode.pathname
18122 : '/' + urlParsingNode.pathname
18127 * Parse a request URL and determine whether this is a same-origin request as the application document.
18129 * @param {string|object} requestUrl The url of the request as a string that will be resolved
18130 * or a parsed URL object.
18131 * @returns {boolean} Whether the request is for the same origin as the application document.
18133 function urlIsSameOrigin(requestUrl) {
18134 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl;
18135 return (parsed.protocol === originUrl.protocol &&
18136 parsed.host === originUrl.host);
18144 * A reference to the browser's `window` object. While `window`
18145 * is globally available in JavaScript, it causes testability problems, because
18146 * it is a global variable. In angular we always refer to it through the
18147 * `$window` service, so it may be overridden, removed or mocked for testing.
18149 * Expressions, like the one defined for the `ngClick` directive in the example
18150 * below, are evaluated with respect to the current scope. Therefore, there is
18151 * no risk of inadvertently coding in a dependency on a global value in such an
18155 <example module="windowExample">
18156 <file name="index.html">
18158 angular.module('windowExample', [])
18159 .controller('ExampleController', ['$scope', '$window', function($scope, $window) {
18160 $scope.greeting = 'Hello, World!';
18161 $scope.doGreeting = function(greeting) {
18162 $window.alert(greeting);
18166 <div ng-controller="ExampleController">
18167 <input type="text" ng-model="greeting" aria-label="greeting" />
18168 <button ng-click="doGreeting(greeting)">ALERT</button>
18171 <file name="protractor.js" type="protractor">
18172 it('should display the greeting in the input box', function() {
18173 element(by.model('greeting')).sendKeys('Hello, E2E Tests');
18174 // If we click the button it will block the test runner
18175 // element(':button').click();
18180 function $WindowProvider() {
18181 this.$get = valueFn(window);
18185 * @name $$cookieReader
18186 * @requires $document
18189 * This is a private service for reading cookies used by $http and ngCookies
18191 * @return {Object} a key/value map of the current cookies
18193 function $$CookieReader($document) {
18194 var rawDocument = $document[0] || {};
18195 var lastCookies = {};
18196 var lastCookieString = '';
18198 function safeDecodeURIComponent(str) {
18200 return decodeURIComponent(str);
18206 return function() {
18207 var cookieArray, cookie, i, index, name;
18208 var currentCookieString = rawDocument.cookie || '';
18210 if (currentCookieString !== lastCookieString) {
18211 lastCookieString = currentCookieString;
18212 cookieArray = lastCookieString.split('; ');
18215 for (i = 0; i < cookieArray.length; i++) {
18216 cookie = cookieArray[i];
18217 index = cookie.indexOf('=');
18218 if (index > 0) { //ignore nameless cookies
18219 name = safeDecodeURIComponent(cookie.substring(0, index));
18220 // the first value that is seen for a cookie is the most
18221 // specific one. values for the same cookie name that
18222 // follow are for less specific paths.
18223 if (isUndefined(lastCookies[name])) {
18224 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
18229 return lastCookies;
18233 $$CookieReader.$inject = ['$document'];
18235 function $$CookieReaderProvider() {
18236 this.$get = $$CookieReader;
18239 /* global currencyFilter: true,
18241 filterFilter: true,
18243 limitToFilter: true,
18244 lowercaseFilter: true,
18245 numberFilter: true,
18246 orderByFilter: true,
18247 uppercaseFilter: true,
18252 * @name $filterProvider
18255 * Filters are just functions which transform input to an output. However filters need to be
18256 * Dependency Injected. To achieve this a filter definition consists of a factory function which is
18257 * annotated with dependencies and is responsible for creating a filter function.
18259 * <div class="alert alert-warning">
18260 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
18261 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
18262 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
18263 * (`myapp_subsection_filterx`).
18267 * // Filter registration
18268 * function MyModule($provide, $filterProvider) {
18269 * // create a service to demonstrate injection (not always needed)
18270 * $provide.value('greet', function(name){
18271 * return 'Hello ' + name + '!';
18274 * // register a filter factory which uses the
18275 * // greet service to demonstrate DI.
18276 * $filterProvider.register('greet', function(greet){
18277 * // return the filter function which uses the greet service
18278 * // to generate salutation
18279 * return function(text) {
18280 * // filters need to be forgiving so check input validity
18281 * return text && greet(text) || text;
18287 * The filter function is registered with the `$injector` under the filter name suffix with
18291 * it('should be the same instance', inject(
18292 * function($filterProvider) {
18293 * $filterProvider.register('reverse', function(){
18297 * function($filter, reverseFilter) {
18298 * expect($filter('reverse')).toBe(reverseFilter);
18303 * For more information about how angular filters work, and how to create your own filters, see
18304 * {@link guide/filter Filters} in the Angular Developer Guide.
18312 * Filters are used for formatting data displayed to the user.
18314 * The general syntax in templates is as follows:
18316 * {{ expression [| filter_name[:parameter_value] ... ] }}
18318 * @param {String} name Name of the filter function to retrieve
18319 * @return {Function} the filter function
18321 <example name="$filter" module="filterExample">
18322 <file name="index.html">
18323 <div ng-controller="MainCtrl">
18324 <h3>{{ originalText }}</h3>
18325 <h3>{{ filteredText }}</h3>
18329 <file name="script.js">
18330 angular.module('filterExample', [])
18331 .controller('MainCtrl', function($scope, $filter) {
18332 $scope.originalText = 'hello';
18333 $scope.filteredText = $filter('uppercase')($scope.originalText);
18338 $FilterProvider.$inject = ['$provide'];
18339 function $FilterProvider($provide) {
18340 var suffix = 'Filter';
18344 * @name $filterProvider#register
18345 * @param {string|Object} name Name of the filter function, or an object map of filters where
18346 * the keys are the filter names and the values are the filter factories.
18348 * <div class="alert alert-warning">
18349 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
18350 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
18351 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
18352 * (`myapp_subsection_filterx`).
18354 * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered.
18355 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map
18356 * of the registered filter instances.
18358 function register(name, factory) {
18359 if (isObject(name)) {
18361 forEach(name, function(filter, key) {
18362 filters[key] = register(key, filter);
18366 return $provide.factory(name + suffix, factory);
18369 this.register = register;
18371 this.$get = ['$injector', function($injector) {
18372 return function(name) {
18373 return $injector.get(name + suffix);
18377 ////////////////////////////////////////
18380 currencyFilter: false,
18382 filterFilter: false,
18384 limitToFilter: false,
18385 lowercaseFilter: false,
18386 numberFilter: false,
18387 orderByFilter: false,
18388 uppercaseFilter: false,
18391 register('currency', currencyFilter);
18392 register('date', dateFilter);
18393 register('filter', filterFilter);
18394 register('json', jsonFilter);
18395 register('limitTo', limitToFilter);
18396 register('lowercase', lowercaseFilter);
18397 register('number', numberFilter);
18398 register('orderBy', orderByFilter);
18399 register('uppercase', uppercaseFilter);
18408 * Selects a subset of items from `array` and returns it as a new array.
18410 * @param {Array} array The source array.
18411 * @param {string|Object|function()} expression The predicate to be used for selecting items from
18416 * - `string`: The string is used for matching against the contents of the `array`. All strings or
18417 * objects with string properties in `array` that match this string will be returned. This also
18418 * applies to nested object properties.
18419 * The predicate can be negated by prefixing the string with `!`.
18421 * - `Object`: A pattern object can be used to filter specific properties on objects contained
18422 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
18423 * which have property `name` containing "M" and property `phone` containing "1". A special
18424 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any
18425 * property of the object or its nested object properties. That's equivalent to the simple
18426 * substring match with a `string` as described above. The predicate can be negated by prefixing
18427 * the string with `!`.
18428 * For example `{name: "!M"}` predicate will return an array of items which have property `name`
18429 * not containing "M".
18431 * Note that a named property will match properties on the same level only, while the special
18432 * `$` property will match properties on the same level or deeper. E.g. an array item like
18433 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but
18434 * **will** be matched by `{$: 'John'}`.
18436 * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters.
18437 * The function is called for each element of the array, with the element, its index, and
18438 * the entire array itself as arguments.
18440 * The final result is an array of those elements that the predicate returned true for.
18442 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in
18443 * determining if the expected value (from the filter expression) and actual value (from
18444 * the object in the array) should be considered a match.
18448 * - `function(actual, expected)`:
18449 * The function will be given the object value and the predicate value to compare and
18450 * should return true if both values should be considered equal.
18452 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`.
18453 * This is essentially strict comparison of expected and actual.
18455 * - `false|undefined`: A short hand for a function which will look for a substring match in case
18458 * Primitive values are converted to strings. Objects are not compared against primitives,
18459 * unless they have a custom `toString` method (e.g. `Date` objects).
18463 <file name="index.html">
18464 <div ng-init="friends = [{name:'John', phone:'555-1276'},
18465 {name:'Mary', phone:'800-BIG-MARY'},
18466 {name:'Mike', phone:'555-4321'},
18467 {name:'Adam', phone:'555-5678'},
18468 {name:'Julie', phone:'555-8765'},
18469 {name:'Juliette', phone:'555-5678'}]"></div>
18471 <label>Search: <input ng-model="searchText"></label>
18472 <table id="searchTextResults">
18473 <tr><th>Name</th><th>Phone</th></tr>
18474 <tr ng-repeat="friend in friends | filter:searchText">
18475 <td>{{friend.name}}</td>
18476 <td>{{friend.phone}}</td>
18480 <label>Any: <input ng-model="search.$"></label> <br>
18481 <label>Name only <input ng-model="search.name"></label><br>
18482 <label>Phone only <input ng-model="search.phone"></label><br>
18483 <label>Equality <input type="checkbox" ng-model="strict"></label><br>
18484 <table id="searchObjResults">
18485 <tr><th>Name</th><th>Phone</th></tr>
18486 <tr ng-repeat="friendObj in friends | filter:search:strict">
18487 <td>{{friendObj.name}}</td>
18488 <td>{{friendObj.phone}}</td>
18492 <file name="protractor.js" type="protractor">
18493 var expectFriendNames = function(expectedNames, key) {
18494 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) {
18495 arr.forEach(function(wd, i) {
18496 expect(wd.getText()).toMatch(expectedNames[i]);
18501 it('should search across all fields when filtering with a string', function() {
18502 var searchText = element(by.model('searchText'));
18503 searchText.clear();
18504 searchText.sendKeys('m');
18505 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend');
18507 searchText.clear();
18508 searchText.sendKeys('76');
18509 expectFriendNames(['John', 'Julie'], 'friend');
18512 it('should search in specific fields when filtering with a predicate object', function() {
18513 var searchAny = element(by.model('search.$'));
18515 searchAny.sendKeys('i');
18516 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj');
18518 it('should use a equal comparison when comparator is true', function() {
18519 var searchName = element(by.model('search.name'));
18520 var strict = element(by.model('strict'));
18521 searchName.clear();
18522 searchName.sendKeys('Julie');
18524 expectFriendNames(['Julie'], 'friendObj');
18529 function filterFilter() {
18530 return function(array, expression, comparator) {
18531 if (!isArrayLike(array)) {
18532 if (array == null) {
18535 throw minErr('filter')('notarray', 'Expected array but received: {0}', array);
18539 var expressionType = getTypeForFilter(expression);
18541 var matchAgainstAnyProp;
18543 switch (expressionType) {
18545 predicateFn = expression;
18551 matchAgainstAnyProp = true;
18555 predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp);
18561 return Array.prototype.filter.call(array, predicateFn);
18565 // Helper functions for `filterFilter`
18566 function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
18567 var shouldMatchPrimitives = isObject(expression) && ('$' in expression);
18570 if (comparator === true) {
18571 comparator = equals;
18572 } else if (!isFunction(comparator)) {
18573 comparator = function(actual, expected) {
18574 if (isUndefined(actual)) {
18575 // No substring matching against `undefined`
18578 if ((actual === null) || (expected === null)) {
18579 // No substring matching against `null`; only match against `null`
18580 return actual === expected;
18582 if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) {
18583 // Should not compare primitives against objects, unless they have custom `toString` method
18587 actual = lowercase('' + actual);
18588 expected = lowercase('' + expected);
18589 return actual.indexOf(expected) !== -1;
18593 predicateFn = function(item) {
18594 if (shouldMatchPrimitives && !isObject(item)) {
18595 return deepCompare(item, expression.$, comparator, false);
18597 return deepCompare(item, expression, comparator, matchAgainstAnyProp);
18600 return predicateFn;
18603 function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) {
18604 var actualType = getTypeForFilter(actual);
18605 var expectedType = getTypeForFilter(expected);
18607 if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
18608 return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
18609 } else if (isArray(actual)) {
18610 // In case `actual` is an array, consider it a match
18611 // if ANY of it's items matches `expected`
18612 return actual.some(function(item) {
18613 return deepCompare(item, expected, comparator, matchAgainstAnyProp);
18617 switch (actualType) {
18620 if (matchAgainstAnyProp) {
18621 for (key in actual) {
18622 if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) {
18626 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false);
18627 } else if (expectedType === 'object') {
18628 for (key in expected) {
18629 var expectedVal = expected[key];
18630 if (isFunction(expectedVal) || isUndefined(expectedVal)) {
18634 var matchAnyProperty = key === '$';
18635 var actualVal = matchAnyProperty ? actual : actual[key];
18636 if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) {
18642 return comparator(actual, expected);
18648 return comparator(actual, expected);
18652 // Used for easily differentiating between `null` and actual `object`
18653 function getTypeForFilter(val) {
18654 return (val === null) ? 'null' : typeof val;
18663 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
18664 * symbol for current locale is used.
18666 * @param {number} amount Input to filter.
18667 * @param {string=} symbol Currency symbol or identifier to be displayed.
18668 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale
18669 * @returns {string} Formatted number.
18673 <example module="currencyExample">
18674 <file name="index.html">
18676 angular.module('currencyExample', [])
18677 .controller('ExampleController', ['$scope', function($scope) {
18678 $scope.amount = 1234.56;
18681 <div ng-controller="ExampleController">
18682 <input type="number" ng-model="amount" aria-label="amount"> <br>
18683 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br>
18684 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span>
18685 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span>
18688 <file name="protractor.js" type="protractor">
18689 it('should init with 1234.56', function() {
18690 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56');
18691 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56');
18692 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235');
18694 it('should update', function() {
18695 if (browser.params.browser == 'safari') {
18696 // Safari does not understand the minus key. See
18697 // https://github.com/angular/protractor/issues/481
18700 element(by.model('amount')).clear();
18701 element(by.model('amount')).sendKeys('-1234');
18702 expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00');
18703 expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00');
18704 expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234');
18709 currencyFilter.$inject = ['$locale'];
18710 function currencyFilter($locale) {
18711 var formats = $locale.NUMBER_FORMATS;
18712 return function(amount, currencySymbol, fractionSize) {
18713 if (isUndefined(currencySymbol)) {
18714 currencySymbol = formats.CURRENCY_SYM;
18717 if (isUndefined(fractionSize)) {
18718 fractionSize = formats.PATTERNS[1].maxFrac;
18721 // if null or undefined pass it through
18722 return (amount == null)
18724 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize).
18725 replace(/\u00A4/g, currencySymbol);
18735 * Formats a number as text.
18737 * If the input is null or undefined, it will just be returned.
18738 * If the input is infinite (Infinity/-Infinity) the Infinity symbol '∞' is returned.
18739 * If the input is not a number an empty string is returned.
18742 * @param {number|string} number Number to format.
18743 * @param {(number|string)=} fractionSize Number of decimal places to round the number to.
18744 * If this is not provided then the fraction size is computed from the current locale's number
18745 * formatting pattern. In the case of the default locale, it will be 3.
18746 * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
18749 <example module="numberFilterExample">
18750 <file name="index.html">
18752 angular.module('numberFilterExample', [])
18753 .controller('ExampleController', ['$scope', function($scope) {
18754 $scope.val = 1234.56789;
18757 <div ng-controller="ExampleController">
18758 <label>Enter number: <input ng-model='val'></label><br>
18759 Default formatting: <span id='number-default'>{{val | number}}</span><br>
18760 No fractions: <span>{{val | number:0}}</span><br>
18761 Negative number: <span>{{-val | number:4}}</span>
18764 <file name="protractor.js" type="protractor">
18765 it('should format numbers', function() {
18766 expect(element(by.id('number-default')).getText()).toBe('1,234.568');
18767 expect(element(by.binding('val | number:0')).getText()).toBe('1,235');
18768 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679');
18771 it('should update', function() {
18772 element(by.model('val')).clear();
18773 element(by.model('val')).sendKeys('3374.333');
18774 expect(element(by.id('number-default')).getText()).toBe('3,374.333');
18775 expect(element(by.binding('val | number:0')).getText()).toBe('3,374');
18776 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330');
18783 numberFilter.$inject = ['$locale'];
18784 function numberFilter($locale) {
18785 var formats = $locale.NUMBER_FORMATS;
18786 return function(number, fractionSize) {
18788 // if null or undefined pass it through
18789 return (number == null)
18791 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP,
18796 var DECIMAL_SEP = '.';
18797 function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
18798 if (isObject(number)) return '';
18800 var isNegative = number < 0;
18801 number = Math.abs(number);
18803 var isInfinity = number === Infinity;
18804 if (!isInfinity && !isFinite(number)) return '';
18806 var numStr = number + '',
18808 hasExponent = false,
18811 if (isInfinity) formatedText = '\u221e';
18813 if (!isInfinity && numStr.indexOf('e') !== -1) {
18814 var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
18815 if (match && match[2] == '-' && match[3] > fractionSize + 1) {
18818 formatedText = numStr;
18819 hasExponent = true;
18823 if (!isInfinity && !hasExponent) {
18824 var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
18826 // determine fractionSize if it is not specified
18827 if (isUndefined(fractionSize)) {
18828 fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac);
18831 // safely round numbers in JS without hitting imprecisions of floating-point arithmetics
18833 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
18834 number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
18836 var fraction = ('' + number).split(DECIMAL_SEP);
18837 var whole = fraction[0];
18838 fraction = fraction[1] || '';
18841 lgroup = pattern.lgSize,
18842 group = pattern.gSize;
18844 if (whole.length >= (lgroup + group)) {
18845 pos = whole.length - lgroup;
18846 for (i = 0; i < pos; i++) {
18847 if ((pos - i) % group === 0 && i !== 0) {
18848 formatedText += groupSep;
18850 formatedText += whole.charAt(i);
18854 for (i = pos; i < whole.length; i++) {
18855 if ((whole.length - i) % lgroup === 0 && i !== 0) {
18856 formatedText += groupSep;
18858 formatedText += whole.charAt(i);
18861 // format fraction part.
18862 while (fraction.length < fractionSize) {
18866 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize);
18868 if (fractionSize > 0 && number < 1) {
18869 formatedText = number.toFixed(fractionSize);
18870 number = parseFloat(formatedText);
18871 formatedText = formatedText.replace(DECIMAL_SEP, decimalSep);
18875 if (number === 0) {
18876 isNegative = false;
18879 parts.push(isNegative ? pattern.negPre : pattern.posPre,
18881 isNegative ? pattern.negSuf : pattern.posSuf);
18882 return parts.join('');
18885 function padNumber(num, digits, trim) {
18892 while (num.length < digits) num = '0' + num;
18894 num = num.substr(num.length - digits);
18900 function dateGetter(name, size, offset, trim) {
18901 offset = offset || 0;
18902 return function(date) {
18903 var value = date['get' + name]();
18904 if (offset > 0 || value > -offset) {
18907 if (value === 0 && offset == -12) value = 12;
18908 return padNumber(value, size, trim);
18912 function dateStrGetter(name, shortForm) {
18913 return function(date, formats) {
18914 var value = date['get' + name]();
18915 var get = uppercase(shortForm ? ('SHORT' + name) : name);
18917 return formats[get][value];
18921 function timeZoneGetter(date, formats, offset) {
18922 var zone = -1 * offset;
18923 var paddedZone = (zone >= 0) ? "+" : "";
18925 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) +
18926 padNumber(Math.abs(zone % 60), 2);
18931 function getFirstThursdayOfYear(year) {
18932 // 0 = index of January
18933 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay();
18934 // 4 = index of Thursday (+1 to account for 1st = 5)
18935 // 11 = index of *next* Thursday (+1 account for 1st = 12)
18936 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst);
18939 function getThursdayThisWeek(datetime) {
18940 return new Date(datetime.getFullYear(), datetime.getMonth(),
18941 // 4 = index of Thursday
18942 datetime.getDate() + (4 - datetime.getDay()));
18945 function weekGetter(size) {
18946 return function(date) {
18947 var firstThurs = getFirstThursdayOfYear(date.getFullYear()),
18948 thisThurs = getThursdayThisWeek(date);
18950 var diff = +thisThurs - +firstThurs,
18951 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week
18953 return padNumber(result, size);
18957 function ampmGetter(date, formats) {
18958 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
18961 function eraGetter(date, formats) {
18962 return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1];
18965 function longEraGetter(date, formats) {
18966 return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1];
18969 var DATE_FORMATS = {
18970 yyyy: dateGetter('FullYear', 4),
18971 yy: dateGetter('FullYear', 2, 0, true),
18972 y: dateGetter('FullYear', 1),
18973 MMMM: dateStrGetter('Month'),
18974 MMM: dateStrGetter('Month', true),
18975 MM: dateGetter('Month', 2, 1),
18976 M: dateGetter('Month', 1, 1),
18977 dd: dateGetter('Date', 2),
18978 d: dateGetter('Date', 1),
18979 HH: dateGetter('Hours', 2),
18980 H: dateGetter('Hours', 1),
18981 hh: dateGetter('Hours', 2, -12),
18982 h: dateGetter('Hours', 1, -12),
18983 mm: dateGetter('Minutes', 2),
18984 m: dateGetter('Minutes', 1),
18985 ss: dateGetter('Seconds', 2),
18986 s: dateGetter('Seconds', 1),
18987 // while ISO 8601 requires fractions to be prefixed with `.` or `,`
18988 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions
18989 sss: dateGetter('Milliseconds', 3),
18990 EEEE: dateStrGetter('Day'),
18991 EEE: dateStrGetter('Day', true),
18999 GGGG: longEraGetter
19002 var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,
19003 NUMBER_STRING = /^\-?\d+$/;
19011 * Formats `date` to a string based on the requested `format`.
19013 * `format` string can be composed of the following elements:
19015 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
19016 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
19017 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
19018 * * `'MMMM'`: Month in year (January-December)
19019 * * `'MMM'`: Month in year (Jan-Dec)
19020 * * `'MM'`: Month in year, padded (01-12)
19021 * * `'M'`: Month in year (1-12)
19022 * * `'dd'`: Day in month, padded (01-31)
19023 * * `'d'`: Day in month (1-31)
19024 * * `'EEEE'`: Day in Week,(Sunday-Saturday)
19025 * * `'EEE'`: Day in Week, (Sun-Sat)
19026 * * `'HH'`: Hour in day, padded (00-23)
19027 * * `'H'`: Hour in day (0-23)
19028 * * `'hh'`: Hour in AM/PM, padded (01-12)
19029 * * `'h'`: Hour in AM/PM, (1-12)
19030 * * `'mm'`: Minute in hour, padded (00-59)
19031 * * `'m'`: Minute in hour (0-59)
19032 * * `'ss'`: Second in minute, padded (00-59)
19033 * * `'s'`: Second in minute (0-59)
19034 * * `'sss'`: Millisecond in second, padded (000-999)
19035 * * `'a'`: AM/PM marker
19036 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
19037 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year
19038 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year
19039 * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD')
19040 * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini')
19042 * `format` string can also be one of the following predefined
19043 * {@link guide/i18n localizable formats}:
19045 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
19046 * (e.g. Sep 3, 2010 12:05:08 PM)
19047 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM)
19048 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale
19049 * (e.g. Friday, September 3, 2010)
19050 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010)
19051 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010)
19052 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
19053 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM)
19054 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM)
19056 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g.
19057 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence
19058 * (e.g. `"h 'o''clock'"`).
19060 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
19061 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its
19062 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is
19063 * specified in the string input, the time is considered to be in the local timezone.
19064 * @param {string=} format Formatting rules (see Description). If not specified,
19065 * `mediumDate` is used.
19066 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the
19067 * continental US time zone abbreviations, but for general use, use a time zone offset, for
19068 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
19069 * If not specified, the timezone of the browser will be used.
19070 * @returns {string} Formatted string or the input if input is not recognized as date/millis.
19074 <file name="index.html">
19075 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>:
19076 <span>{{1288323623006 | date:'medium'}}</span><br>
19077 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
19078 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br>
19079 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
19080 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br>
19081 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>:
19082 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br>
19084 <file name="protractor.js" type="protractor">
19085 it('should format date', function() {
19086 expect(element(by.binding("1288323623006 | date:'medium'")).getText()).
19087 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
19088 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()).
19089 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/);
19090 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()).
19091 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
19092 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()).
19093 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/);
19098 dateFilter.$inject = ['$locale'];
19099 function dateFilter($locale) {
19102 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
19103 // 1 2 3 4 5 6 7 8 9 10 11
19104 function jsonStringToDate(string) {
19106 if (match = string.match(R_ISO8601_STR)) {
19107 var date = new Date(0),
19110 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear,
19111 timeSetter = match[8] ? date.setUTCHours : date.setHours;
19114 tzHour = toInt(match[9] + match[10]);
19115 tzMin = toInt(match[9] + match[11]);
19117 dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
19118 var h = toInt(match[4] || 0) - tzHour;
19119 var m = toInt(match[5] || 0) - tzMin;
19120 var s = toInt(match[6] || 0);
19121 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
19122 timeSetter.call(date, h, m, s, ms);
19129 return function(date, format, timezone) {
19134 format = format || 'mediumDate';
19135 format = $locale.DATETIME_FORMATS[format] || format;
19136 if (isString(date)) {
19137 date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date);
19140 if (isNumber(date)) {
19141 date = new Date(date);
19144 if (!isDate(date) || !isFinite(date.getTime())) {
19149 match = DATE_FORMATS_SPLIT.exec(format);
19151 parts = concat(parts, match, 1);
19152 format = parts.pop();
19154 parts.push(format);
19159 var dateTimezoneOffset = date.getTimezoneOffset();
19161 dateTimezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
19162 date = convertTimezoneToLocal(date, timezone, true);
19164 forEach(parts, function(value) {
19165 fn = DATE_FORMATS[value];
19166 text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset)
19167 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
19181 * Allows you to convert a JavaScript object into JSON string.
19183 * This filter is mostly useful for debugging. When using the double curly {{value}} notation
19184 * the binding is automatically converted to JSON.
19186 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
19187 * @param {number=} spacing The number of spaces to use per indentation, defaults to 2.
19188 * @returns {string} JSON string.
19193 <file name="index.html">
19194 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre>
19195 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre>
19197 <file name="protractor.js" type="protractor">
19198 it('should jsonify filtered objects', function() {
19199 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
19200 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
19206 function jsonFilter() {
19207 return function(object, spacing) {
19208 if (isUndefined(spacing)) {
19211 return toJson(object, spacing);
19221 * Converts string to lowercase.
19222 * @see angular.lowercase
19224 var lowercaseFilter = valueFn(lowercase);
19232 * Converts string to uppercase.
19233 * @see angular.uppercase
19235 var uppercaseFilter = valueFn(uppercase);
19243 * Creates a new array or string containing only a specified number of elements. The elements
19244 * are taken from either the beginning or the end of the source array, string or number, as specified by
19245 * the value and sign (positive or negative) of `limit`. If a number is used as input, it is
19246 * converted to a string.
19248 * @param {Array|string|number} input Source array, string or number to be limited.
19249 * @param {string|number} limit The length of the returned array or string. If the `limit` number
19250 * is positive, `limit` number of items from the beginning of the source array/string are copied.
19251 * If the number is negative, `limit` number of items from the end of the source array/string
19252 * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined,
19253 * the input will be returned unchanged.
19254 * @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin`
19255 * indicates an offset from the end of `input`. Defaults to `0`.
19256 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
19257 * had less than `limit` elements.
19260 <example module="limitToExample">
19261 <file name="index.html">
19263 angular.module('limitToExample', [])
19264 .controller('ExampleController', ['$scope', function($scope) {
19265 $scope.numbers = [1,2,3,4,5,6,7,8,9];
19266 $scope.letters = "abcdefghi";
19267 $scope.longNumber = 2345432342;
19268 $scope.numLimit = 3;
19269 $scope.letterLimit = 3;
19270 $scope.longNumberLimit = 3;
19273 <div ng-controller="ExampleController">
19275 Limit {{numbers}} to:
19276 <input type="number" step="1" ng-model="numLimit">
19278 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p>
19280 Limit {{letters}} to:
19281 <input type="number" step="1" ng-model="letterLimit">
19283 <p>Output letters: {{ letters | limitTo:letterLimit }}</p>
19285 Limit {{longNumber}} to:
19286 <input type="number" step="1" ng-model="longNumberLimit">
19288 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p>
19291 <file name="protractor.js" type="protractor">
19292 var numLimitInput = element(by.model('numLimit'));
19293 var letterLimitInput = element(by.model('letterLimit'));
19294 var longNumberLimitInput = element(by.model('longNumberLimit'));
19295 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit'));
19296 var limitedLetters = element(by.binding('letters | limitTo:letterLimit'));
19297 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit'));
19299 it('should limit the number array to first three items', function() {
19300 expect(numLimitInput.getAttribute('value')).toBe('3');
19301 expect(letterLimitInput.getAttribute('value')).toBe('3');
19302 expect(longNumberLimitInput.getAttribute('value')).toBe('3');
19303 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]');
19304 expect(limitedLetters.getText()).toEqual('Output letters: abc');
19305 expect(limitedLongNumber.getText()).toEqual('Output long number: 234');
19308 // There is a bug in safari and protractor that doesn't like the minus key
19309 // it('should update the output when -3 is entered', function() {
19310 // numLimitInput.clear();
19311 // numLimitInput.sendKeys('-3');
19312 // letterLimitInput.clear();
19313 // letterLimitInput.sendKeys('-3');
19314 // longNumberLimitInput.clear();
19315 // longNumberLimitInput.sendKeys('-3');
19316 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
19317 // expect(limitedLetters.getText()).toEqual('Output letters: ghi');
19318 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342');
19321 it('should not exceed the maximum size of input array', function() {
19322 numLimitInput.clear();
19323 numLimitInput.sendKeys('100');
19324 letterLimitInput.clear();
19325 letterLimitInput.sendKeys('100');
19326 longNumberLimitInput.clear();
19327 longNumberLimitInput.sendKeys('100');
19328 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]');
19329 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi');
19330 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342');
19335 function limitToFilter() {
19336 return function(input, limit, begin) {
19337 if (Math.abs(Number(limit)) === Infinity) {
19338 limit = Number(limit);
19340 limit = toInt(limit);
19342 if (isNaN(limit)) return input;
19344 if (isNumber(input)) input = input.toString();
19345 if (!isArray(input) && !isString(input)) return input;
19347 begin = (!begin || isNaN(begin)) ? 0 : toInt(begin);
19348 begin = (begin < 0) ? Math.max(0, input.length + begin) : begin;
19351 return input.slice(begin, begin + limit);
19354 return input.slice(limit, input.length);
19356 return input.slice(Math.max(0, begin + limit), begin);
19368 * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically
19369 * for strings and numerically for numbers. Note: if you notice numbers are not being sorted
19370 * as expected, make sure they are actually being saved as numbers and not strings.
19372 * @param {Array} array The array to sort.
19373 * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be
19374 * used by the comparator to determine the order of elements.
19378 * - `function`: Getter function. The result of this function will be sorted using the
19379 * `<`, `===`, `>` operator.
19380 * - `string`: An Angular expression. The result of this expression is used to compare elements
19381 * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by
19382 * 3 first characters of a property called `name`). The result of a constant expression
19383 * is interpreted as a property name to be used in comparisons (for example `"special name"`
19384 * to sort object by the value of their `special name` property). An expression can be
19385 * optionally prefixed with `+` or `-` to control ascending or descending sort order
19386 * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array
19387 * element itself is used to compare where sorting.
19388 * - `Array`: An array of function or string predicates. The first predicate in the array
19389 * is used for sorting, but when two items are equivalent, the next predicate is used.
19391 * If the predicate is missing or empty then it defaults to `'+'`.
19393 * @param {boolean=} reverse Reverse the order of the array.
19394 * @returns {Array} Sorted copy of the source array.
19398 * The example below demonstrates a simple ngRepeat, where the data is sorted
19399 * by age in descending order (predicate is set to `'-age'`).
19400 * `reverse` is not set, which means it defaults to `false`.
19401 <example module="orderByExample">
19402 <file name="index.html">
19404 angular.module('orderByExample', [])
19405 .controller('ExampleController', ['$scope', function($scope) {
19407 [{name:'John', phone:'555-1212', age:10},
19408 {name:'Mary', phone:'555-9876', age:19},
19409 {name:'Mike', phone:'555-4321', age:21},
19410 {name:'Adam', phone:'555-5678', age:35},
19411 {name:'Julie', phone:'555-8765', age:29}];
19414 <div ng-controller="ExampleController">
19415 <table class="friend">
19418 <th>Phone Number</th>
19421 <tr ng-repeat="friend in friends | orderBy:'-age'">
19422 <td>{{friend.name}}</td>
19423 <td>{{friend.phone}}</td>
19424 <td>{{friend.age}}</td>
19431 * The predicate and reverse parameters can be controlled dynamically through scope properties,
19432 * as shown in the next example.
19434 <example module="orderByExample">
19435 <file name="index.html">
19437 angular.module('orderByExample', [])
19438 .controller('ExampleController', ['$scope', function($scope) {
19440 [{name:'John', phone:'555-1212', age:10},
19441 {name:'Mary', phone:'555-9876', age:19},
19442 {name:'Mike', phone:'555-4321', age:21},
19443 {name:'Adam', phone:'555-5678', age:35},
19444 {name:'Julie', phone:'555-8765', age:29}];
19445 $scope.predicate = 'age';
19446 $scope.reverse = true;
19447 $scope.order = function(predicate) {
19448 $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
19449 $scope.predicate = predicate;
19453 <style type="text/css">
19457 .sortorder.reverse:after {
19461 <div ng-controller="ExampleController">
19462 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
19464 [ <a href="" ng-click="predicate=''">unsorted</a> ]
19465 <table class="friend">
19468 <a href="" ng-click="order('name')">Name</a>
19469 <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
19472 <a href="" ng-click="order('phone')">Phone Number</a>
19473 <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span>
19476 <a href="" ng-click="order('age')">Age</a>
19477 <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span>
19480 <tr ng-repeat="friend in friends | orderBy:predicate:reverse">
19481 <td>{{friend.name}}</td>
19482 <td>{{friend.phone}}</td>
19483 <td>{{friend.age}}</td>
19490 * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the
19491 * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the
19492 * desired parameters.
19497 <example module="orderByExample">
19498 <file name="index.html">
19499 <div ng-controller="ExampleController">
19500 <table class="friend">
19502 <th><a href="" ng-click="reverse=false;order('name', false)">Name</a>
19503 (<a href="" ng-click="order('-name',false)">^</a>)</th>
19504 <th><a href="" ng-click="reverse=!reverse;order('phone', reverse)">Phone Number</a></th>
19505 <th><a href="" ng-click="reverse=!reverse;order('age',reverse)">Age</a></th>
19507 <tr ng-repeat="friend in friends">
19508 <td>{{friend.name}}</td>
19509 <td>{{friend.phone}}</td>
19510 <td>{{friend.age}}</td>
19516 <file name="script.js">
19517 angular.module('orderByExample', [])
19518 .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) {
19519 var orderBy = $filter('orderBy');
19521 { name: 'John', phone: '555-1212', age: 10 },
19522 { name: 'Mary', phone: '555-9876', age: 19 },
19523 { name: 'Mike', phone: '555-4321', age: 21 },
19524 { name: 'Adam', phone: '555-5678', age: 35 },
19525 { name: 'Julie', phone: '555-8765', age: 29 }
19527 $scope.order = function(predicate, reverse) {
19528 $scope.friends = orderBy($scope.friends, predicate, reverse);
19530 $scope.order('-age',false);
19535 orderByFilter.$inject = ['$parse'];
19536 function orderByFilter($parse) {
19537 return function(array, sortPredicate, reverseOrder) {
19539 if (!(isArrayLike(array))) return array;
19541 if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; }
19542 if (sortPredicate.length === 0) { sortPredicate = ['+']; }
19544 var predicates = processPredicates(sortPredicate, reverseOrder);
19545 // Add a predicate at the end that evaluates to the element index. This makes the
19546 // sort stable as it works as a tie-breaker when all the input predicates cannot
19547 // distinguish between two elements.
19548 predicates.push({ get: function() { return {}; }, descending: reverseOrder ? -1 : 1});
19550 // The next three lines are a version of a Swartzian Transform idiom from Perl
19551 // (sometimes called the Decorate-Sort-Undecorate idiom)
19552 // See https://en.wikipedia.org/wiki/Schwartzian_transform
19553 var compareValues = Array.prototype.map.call(array, getComparisonObject);
19554 compareValues.sort(doComparison);
19555 array = compareValues.map(function(item) { return item.value; });
19559 function getComparisonObject(value, index) {
19562 predicateValues: predicates.map(function(predicate) {
19563 return getPredicateValue(predicate.get(value), index);
19568 function doComparison(v1, v2) {
19570 for (var index=0, length = predicates.length; index < length; ++index) {
19571 result = compare(v1.predicateValues[index], v2.predicateValues[index]) * predicates[index].descending;
19578 function processPredicates(sortPredicate, reverseOrder) {
19579 reverseOrder = reverseOrder ? -1 : 1;
19580 return sortPredicate.map(function(predicate) {
19581 var descending = 1, get = identity;
19583 if (isFunction(predicate)) {
19585 } else if (isString(predicate)) {
19586 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) {
19587 descending = predicate.charAt(0) == '-' ? -1 : 1;
19588 predicate = predicate.substring(1);
19590 if (predicate !== '') {
19591 get = $parse(predicate);
19592 if (get.constant) {
19594 get = function(value) { return value[key]; };
19598 return { get: get, descending: descending * reverseOrder };
19602 function isPrimitive(value) {
19603 switch (typeof value) {
19604 case 'number': /* falls through */
19605 case 'boolean': /* falls through */
19613 function objectValue(value, index) {
19614 // If `valueOf` is a valid function use that
19615 if (typeof value.valueOf === 'function') {
19616 value = value.valueOf();
19617 if (isPrimitive(value)) return value;
19619 // If `toString` is a valid function and not the one from `Object.prototype` use that
19620 if (hasCustomToString(value)) {
19621 value = value.toString();
19622 if (isPrimitive(value)) return value;
19624 // We have a basic object so we use the position of the object in the collection
19628 function getPredicateValue(value, index) {
19629 var type = typeof value;
19630 if (value === null) {
19633 } else if (type === 'string') {
19634 value = value.toLowerCase();
19635 } else if (type === 'object') {
19636 value = objectValue(value, index);
19638 return { value: value, type: type };
19641 function compare(v1, v2) {
19643 if (v1.type === v2.type) {
19644 if (v1.value !== v2.value) {
19645 result = v1.value < v2.value ? -1 : 1;
19648 result = v1.type < v2.type ? -1 : 1;
19654 function ngDirective(directive) {
19655 if (isFunction(directive)) {
19660 directive.restrict = directive.restrict || 'AC';
19661 return valueFn(directive);
19670 * Modifies the default behavior of the html A tag so that the default action is prevented when
19671 * the href attribute is empty.
19673 * This change permits the easy creation of action links with the `ngClick` directive
19674 * without changing the location or causing page reloads, e.g.:
19675 * `<a href="" ng-click="list.addItem()">Add Item</a>`
19677 var htmlAnchorDirective = valueFn({
19679 compile: function(element, attr) {
19680 if (!attr.href && !attr.xlinkHref) {
19681 return function(scope, element) {
19682 // If the linked element is not an anchor tag anymore, do nothing
19683 if (element[0].nodeName.toLowerCase() !== 'a') return;
19685 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
19686 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
19687 'xlink:href' : 'href';
19688 element.on('click', function(event) {
19689 // if we have no href url, then don't navigate anywhere.
19690 if (!element.attr(href)) {
19691 event.preventDefault();
19706 * Using Angular markup like `{{hash}}` in an href attribute will
19707 * make the link go to the wrong URL if the user clicks it before
19708 * Angular has a chance to replace the `{{hash}}` markup with its
19709 * value. Until Angular replaces the markup the link will be broken
19710 * and will most likely return a 404 error. The `ngHref` directive
19711 * solves this problem.
19713 * The wrong way to write it:
19715 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
19718 * The correct way to write it:
19720 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
19724 * @param {template} ngHref any string which can contain `{{}}` markup.
19727 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes
19728 * in links and their different behaviors:
19730 <file name="index.html">
19731 <input ng-model="value" /><br />
19732 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br />
19733 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br />
19734 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br />
19735 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br />
19736 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br />
19737 <a id="link-6" ng-href="{{value}}">link</a> (link, change location)
19739 <file name="protractor.js" type="protractor">
19740 it('should execute ng-click but not reload when href without value', function() {
19741 element(by.id('link-1')).click();
19742 expect(element(by.model('value')).getAttribute('value')).toEqual('1');
19743 expect(element(by.id('link-1')).getAttribute('href')).toBe('');
19746 it('should execute ng-click but not reload when href empty string', function() {
19747 element(by.id('link-2')).click();
19748 expect(element(by.model('value')).getAttribute('value')).toEqual('2');
19749 expect(element(by.id('link-2')).getAttribute('href')).toBe('');
19752 it('should execute ng-click and change url when ng-href specified', function() {
19753 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/);
19755 element(by.id('link-3')).click();
19757 // At this point, we navigate away from an Angular page, so we need
19758 // to use browser.driver to get the base webdriver.
19760 browser.wait(function() {
19761 return browser.driver.getCurrentUrl().then(function(url) {
19762 return url.match(/\/123$/);
19764 }, 5000, 'page should navigate to /123');
19767 it('should execute ng-click but not reload when href empty string and name specified', function() {
19768 element(by.id('link-4')).click();
19769 expect(element(by.model('value')).getAttribute('value')).toEqual('4');
19770 expect(element(by.id('link-4')).getAttribute('href')).toBe('');
19773 it('should execute ng-click but not reload when no href but name specified', function() {
19774 element(by.id('link-5')).click();
19775 expect(element(by.model('value')).getAttribute('value')).toEqual('5');
19776 expect(element(by.id('link-5')).getAttribute('href')).toBe(null);
19779 it('should only change url when only ng-href', function() {
19780 element(by.model('value')).clear();
19781 element(by.model('value')).sendKeys('6');
19782 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/);
19784 element(by.id('link-6')).click();
19786 // At this point, we navigate away from an Angular page, so we need
19787 // to use browser.driver to get the base webdriver.
19788 browser.wait(function() {
19789 return browser.driver.getCurrentUrl().then(function(url) {
19790 return url.match(/\/6$/);
19792 }, 5000, 'page should navigate to /6');
19805 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't
19806 * work right: The browser will fetch from the URL with the literal
19807 * text `{{hash}}` until Angular replaces the expression inside
19808 * `{{hash}}`. The `ngSrc` directive solves this problem.
19810 * The buggy way to write it:
19812 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/>
19815 * The correct way to write it:
19817 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" />
19821 * @param {template} ngSrc any string which can contain `{{}}` markup.
19831 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't
19832 * work right: The browser will fetch from the URL with the literal
19833 * text `{{hash}}` until Angular replaces the expression inside
19834 * `{{hash}}`. The `ngSrcset` directive solves this problem.
19836 * The buggy way to write it:
19838 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/>
19841 * The correct way to write it:
19843 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" />
19847 * @param {template} ngSrcset any string which can contain `{{}}` markup.
19858 * This directive sets the `disabled` attribute on the element if the
19859 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy.
19861 * A special directive is necessary because we cannot use interpolation inside the `disabled`
19862 * attribute. The following example would make the button enabled on Chrome/Firefox
19863 * but not on older IEs:
19866 * <!-- See below for an example of ng-disabled being used correctly -->
19867 * <div ng-init="isDisabled = false">
19868 * <button disabled="{{isDisabled}}">Disabled</button>
19872 * This is because the HTML specification does not require browsers to preserve the values of
19873 * boolean attributes such as `disabled` (Their presence means true and their absence means false.)
19874 * If we put an Angular interpolation expression into such an attribute then the
19875 * binding information would be lost when the browser removes the attribute.
19879 <file name="index.html">
19880 <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/>
19881 <button ng-model="button" ng-disabled="checked">Button</button>
19883 <file name="protractor.js" type="protractor">
19884 it('should toggle button', function() {
19885 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
19886 element(by.model('checked')).click();
19887 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy();
19893 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
19894 * then the `disabled` attribute will be set on the element
19905 * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy.
19907 * Note that this directive should not be used together with {@link ngModel `ngModel`},
19908 * as this can lead to unexpected behavior.
19910 * ### Why do we need `ngChecked`?
19912 * The HTML specification does not require browsers to preserve the values of boolean attributes
19913 * such as checked. (Their presence means true and their absence means false.)
19914 * If we put an Angular interpolation expression into such an attribute then the
19915 * binding information would be lost when the browser removes the attribute.
19916 * The `ngChecked` directive solves this problem for the `checked` attribute.
19917 * This complementary directive is not removed by the browser and so provides
19918 * a permanent reliable place to store the binding information.
19921 <file name="index.html">
19922 <label>Check me to check both: <input type="checkbox" ng-model="master"></label><br/>
19923 <input id="checkSlave" type="checkbox" ng-checked="master" aria-label="Slave input">
19925 <file name="protractor.js" type="protractor">
19926 it('should check both checkBoxes', function() {
19927 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy();
19928 element(by.model('master')).click();
19929 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy();
19935 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy,
19936 * then the `checked` attribute will be set on the element
19947 * The HTML specification does not require browsers to preserve the values of boolean attributes
19948 * such as readonly. (Their presence means true and their absence means false.)
19949 * If we put an Angular interpolation expression into such an attribute then the
19950 * binding information would be lost when the browser removes the attribute.
19951 * The `ngReadonly` directive solves this problem for the `readonly` attribute.
19952 * This complementary directive is not removed by the browser and so provides
19953 * a permanent reliable place to store the binding information.
19956 <file name="index.html">
19957 <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/>
19958 <input type="text" ng-readonly="checked" value="I'm Angular" aria-label="Readonly field" />
19960 <file name="protractor.js" type="protractor">
19961 it('should toggle readonly attr', function() {
19962 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy();
19963 element(by.model('checked')).click();
19964 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy();
19970 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy,
19971 * then special attribute "readonly" will be set on the element
19982 * The HTML specification does not require browsers to preserve the values of boolean attributes
19983 * such as selected. (Their presence means true and their absence means false.)
19984 * If we put an Angular interpolation expression into such an attribute then the
19985 * binding information would be lost when the browser removes the attribute.
19986 * The `ngSelected` directive solves this problem for the `selected` attribute.
19987 * This complementary directive is not removed by the browser and so provides
19988 * a permanent reliable place to store the binding information.
19992 <file name="index.html">
19993 <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/>
19994 <select aria-label="ngSelected demo">
19995 <option>Hello!</option>
19996 <option id="greet" ng-selected="selected">Greetings!</option>
19999 <file name="protractor.js" type="protractor">
20000 it('should select Greetings!', function() {
20001 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
20002 element(by.model('selected')).click();
20003 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy();
20009 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy,
20010 * then special attribute "selected" will be set on the element
20020 * The HTML specification does not require browsers to preserve the values of boolean attributes
20021 * such as open. (Their presence means true and their absence means false.)
20022 * If we put an Angular interpolation expression into such an attribute then the
20023 * binding information would be lost when the browser removes the attribute.
20024 * The `ngOpen` directive solves this problem for the `open` attribute.
20025 * This complementary directive is not removed by the browser and so provides
20026 * a permanent reliable place to store the binding information.
20029 <file name="index.html">
20030 <label>Check me check multiple: <input type="checkbox" ng-model="open"></label><br/>
20031 <details id="details" ng-open="open">
20032 <summary>Show/Hide me</summary>
20035 <file name="protractor.js" type="protractor">
20036 it('should toggle open', function() {
20037 expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
20038 element(by.model('open')).click();
20039 expect(element(by.id('details')).getAttribute('open')).toBeTruthy();
20045 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy,
20046 * then special attribute "open" will be set on the element
20049 var ngAttributeAliasDirectives = {};
20051 // boolean attrs are evaluated
20052 forEach(BOOLEAN_ATTR, function(propName, attrName) {
20053 // binding to multiple is not supported
20054 if (propName == "multiple") return;
20056 function defaultLinkFn(scope, element, attr) {
20057 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
20058 attr.$set(attrName, !!value);
20062 var normalized = directiveNormalize('ng-' + attrName);
20063 var linkFn = defaultLinkFn;
20065 if (propName === 'checked') {
20066 linkFn = function(scope, element, attr) {
20067 // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input
20068 if (attr.ngModel !== attr[normalized]) {
20069 defaultLinkFn(scope, element, attr);
20074 ngAttributeAliasDirectives[normalized] = function() {
20083 // aliased input attrs are evaluated
20084 forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) {
20085 ngAttributeAliasDirectives[ngAttr] = function() {
20088 link: function(scope, element, attr) {
20089 //special case ngPattern when a literal regular expression value
20090 //is used as the expression (this way we don't have to watch anything).
20091 if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") {
20092 var match = attr.ngPattern.match(REGEX_STRING_REGEXP);
20094 attr.$set("ngPattern", new RegExp(match[1], match[2]));
20099 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) {
20100 attr.$set(ngAttr, value);
20107 // ng-src, ng-srcset, ng-href are interpolated
20108 forEach(['src', 'srcset', 'href'], function(attrName) {
20109 var normalized = directiveNormalize('ng-' + attrName);
20110 ngAttributeAliasDirectives[normalized] = function() {
20112 priority: 99, // it needs to run after the attributes are interpolated
20113 link: function(scope, element, attr) {
20114 var propName = attrName,
20117 if (attrName === 'href' &&
20118 toString.call(element.prop('href')) === '[object SVGAnimatedString]') {
20119 name = 'xlinkHref';
20120 attr.$attr[name] = 'xlink:href';
20124 attr.$observe(normalized, function(value) {
20126 if (attrName === 'href') {
20127 attr.$set(name, null);
20132 attr.$set(name, value);
20134 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
20135 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
20136 // to set the property as well to achieve the desired effect.
20137 // we use attr[attrName] value since $set can sanitize the url.
20138 if (msie && propName) element.prop(propName, attr[name]);
20145 /* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true
20147 var nullFormCtrl = {
20149 $$renameControl: nullFormRenameControl,
20150 $removeControl: noop,
20151 $setValidity: noop,
20153 $setPristine: noop,
20154 $setSubmitted: noop
20156 SUBMITTED_CLASS = 'ng-submitted';
20158 function nullFormRenameControl(control, name) {
20159 control.$name = name;
20164 * @name form.FormController
20166 * @property {boolean} $pristine True if user has not interacted with the form yet.
20167 * @property {boolean} $dirty True if user has already interacted with the form.
20168 * @property {boolean} $valid True if all of the containing forms and controls are valid.
20169 * @property {boolean} $invalid True if at least one containing control or form is invalid.
20170 * @property {boolean} $pending True if at least one containing control or form is pending.
20171 * @property {boolean} $submitted True if user has submitted the form even if its invalid.
20173 * @property {Object} $error Is an object hash, containing references to controls or
20174 * forms with failing validators, where:
20176 * - keys are validation tokens (error names),
20177 * - values are arrays of controls or forms that have a failing validator for given error name.
20179 * Built-in validation tokens:
20191 * - `datetimelocal`
20197 * `FormController` keeps track of all its controls and nested forms as well as the state of them,
20198 * such as being valid/invalid or dirty/pristine.
20200 * Each {@link ng.directive:form form} directive creates an instance
20201 * of `FormController`.
20204 //asks for $scope to fool the BC controller module
20205 FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate'];
20206 function FormController(element, attrs, $scope, $animate, $interpolate) {
20212 form.$$success = {};
20213 form.$pending = undefined;
20214 form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope);
20215 form.$dirty = false;
20216 form.$pristine = true;
20217 form.$valid = true;
20218 form.$invalid = false;
20219 form.$submitted = false;
20220 form.$$parentForm = nullFormCtrl;
20224 * @name form.FormController#$rollbackViewValue
20227 * Rollback all form controls pending updates to the `$modelValue`.
20229 * Updates may be pending by a debounced event or because the input is waiting for a some future
20230 * event defined in `ng-model-options`. This method is typically needed by the reset button of
20231 * a form that uses `ng-model-options` to pend updates.
20233 form.$rollbackViewValue = function() {
20234 forEach(controls, function(control) {
20235 control.$rollbackViewValue();
20241 * @name form.FormController#$commitViewValue
20244 * Commit all form controls pending updates to the `$modelValue`.
20246 * Updates may be pending by a debounced event or because the input is waiting for a some future
20247 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController`
20248 * usually handles calling this in response to input events.
20250 form.$commitViewValue = function() {
20251 forEach(controls, function(control) {
20252 control.$commitViewValue();
20258 * @name form.FormController#$addControl
20259 * @param {object} control control object, either a {@link form.FormController} or an
20260 * {@link ngModel.NgModelController}
20263 * Register a control with the form. Input elements using ngModelController do this automatically
20264 * when they are linked.
20266 * Note that the current state of the control will not be reflected on the new parent form. This
20267 * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine`
20270 * However, if the method is used programmatically, for example by adding dynamically created controls,
20271 * or controls that have been previously removed without destroying their corresponding DOM element,
20272 * it's the developers responsiblity to make sure the current state propagates to the parent form.
20274 * For example, if an input control is added that is already `$dirty` and has `$error` properties,
20275 * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form.
20277 form.$addControl = function(control) {
20278 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored
20279 // and not added to the scope. Now we throw an error.
20280 assertNotHasOwnProperty(control.$name, 'input');
20281 controls.push(control);
20283 if (control.$name) {
20284 form[control.$name] = control;
20287 control.$$parentForm = form;
20290 // Private API: rename a form control
20291 form.$$renameControl = function(control, newName) {
20292 var oldName = control.$name;
20294 if (form[oldName] === control) {
20295 delete form[oldName];
20297 form[newName] = control;
20298 control.$name = newName;
20303 * @name form.FormController#$removeControl
20304 * @param {object} control control object, either a {@link form.FormController} or an
20305 * {@link ngModel.NgModelController}
20308 * Deregister a control from the form.
20310 * Input elements using ngModelController do this automatically when they are destroyed.
20312 * Note that only the removed control's validation state (`$errors`etc.) will be removed from the
20313 * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be
20314 * different from case to case. For example, removing the only `$dirty` control from a form may or
20315 * may not mean that the form is still `$dirty`.
20317 form.$removeControl = function(control) {
20318 if (control.$name && form[control.$name] === control) {
20319 delete form[control.$name];
20321 forEach(form.$pending, function(value, name) {
20322 form.$setValidity(name, null, control);
20324 forEach(form.$error, function(value, name) {
20325 form.$setValidity(name, null, control);
20327 forEach(form.$$success, function(value, name) {
20328 form.$setValidity(name, null, control);
20331 arrayRemove(controls, control);
20332 control.$$parentForm = nullFormCtrl;
20338 * @name form.FormController#$setValidity
20341 * Sets the validity of a form control.
20343 * This method will also propagate to parent forms.
20345 addSetValidityMethod({
20348 set: function(object, property, controller) {
20349 var list = object[property];
20351 object[property] = [controller];
20353 var index = list.indexOf(controller);
20354 if (index === -1) {
20355 list.push(controller);
20359 unset: function(object, property, controller) {
20360 var list = object[property];
20364 arrayRemove(list, controller);
20365 if (list.length === 0) {
20366 delete object[property];
20374 * @name form.FormController#$setDirty
20377 * Sets the form to a dirty state.
20379 * This method can be called to add the 'ng-dirty' class and set the form to a dirty
20380 * state (ng-dirty class). This method will also propagate to parent forms.
20382 form.$setDirty = function() {
20383 $animate.removeClass(element, PRISTINE_CLASS);
20384 $animate.addClass(element, DIRTY_CLASS);
20385 form.$dirty = true;
20386 form.$pristine = false;
20387 form.$$parentForm.$setDirty();
20392 * @name form.FormController#$setPristine
20395 * Sets the form to its pristine state.
20397 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine
20398 * state (ng-pristine class). This method will also propagate to all the controls contained
20401 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
20402 * saving or resetting it.
20404 form.$setPristine = function() {
20405 $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS);
20406 form.$dirty = false;
20407 form.$pristine = true;
20408 form.$submitted = false;
20409 forEach(controls, function(control) {
20410 control.$setPristine();
20416 * @name form.FormController#$setUntouched
20419 * Sets the form to its untouched state.
20421 * This method can be called to remove the 'ng-touched' class and set the form controls to their
20422 * untouched state (ng-untouched class).
20424 * Setting a form controls back to their untouched state is often useful when setting the form
20425 * back to its pristine state.
20427 form.$setUntouched = function() {
20428 forEach(controls, function(control) {
20429 control.$setUntouched();
20435 * @name form.FormController#$setSubmitted
20438 * Sets the form to its submitted state.
20440 form.$setSubmitted = function() {
20441 $animate.addClass(element, SUBMITTED_CLASS);
20442 form.$submitted = true;
20443 form.$$parentForm.$setSubmitted();
20453 * Nestable alias of {@link ng.directive:form `form`} directive. HTML
20454 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
20455 * sub-group of controls needs to be determined.
20457 * Note: the purpose of `ngForm` is to group controls,
20458 * but not to be a replacement for the `<form>` tag with all of its capabilities
20459 * (e.g. posting to the server, ...).
20461 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into
20462 * related scope, under this name.
20472 * Directive that instantiates
20473 * {@link form.FormController FormController}.
20475 * If the `name` attribute is specified, the form controller is published onto the current scope under
20478 * # Alias: {@link ng.directive:ngForm `ngForm`}
20480 * In Angular, forms can be nested. This means that the outer form is valid when all of the child
20481 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so
20482 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to
20483 * `<form>` but can be nested. This allows you to have nested forms, which is very useful when
20484 * using Angular validation directives in forms that are dynamically generated using the
20485 * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name`
20486 * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an
20487 * `ngForm` directive and nest these in an outer `form` element.
20491 * - `ng-valid` is set if the form is valid.
20492 * - `ng-invalid` is set if the form is invalid.
20493 * - `ng-pending` is set if the form is pending.
20494 * - `ng-pristine` is set if the form is pristine.
20495 * - `ng-dirty` is set if the form is dirty.
20496 * - `ng-submitted` is set if the form was submitted.
20498 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
20501 * # Submitting a form and preventing the default action
20503 * Since the role of forms in client-side Angular applications is different than in classical
20504 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full
20505 * page reload that sends the data to the server. Instead some javascript logic should be triggered
20506 * to handle the form submission in an application-specific way.
20508 * For this reason, Angular prevents the default action (form submission to the server) unless the
20509 * `<form>` element has an `action` attribute specified.
20511 * You can use one of the following two ways to specify what javascript method should be called when
20512 * a form is submitted:
20514 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element
20515 * - {@link ng.directive:ngClick ngClick} directive on the first
20516 * button or input field of type submit (input[type=submit])
20518 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit}
20519 * or {@link ng.directive:ngClick ngClick} directives.
20520 * This is because of the following form submission rules in the HTML specification:
20522 * - If a form has only one input field then hitting enter in this field triggers form submit
20524 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter
20525 * doesn't trigger submit
20526 * - if a form has one or more input fields and one or more buttons or input[type=submit] then
20527 * hitting enter in any of the input fields will trigger the click handler on the *first* button or
20528 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
20530 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is
20531 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
20532 * to have access to the updated model.
20534 * ## Animation Hooks
20536 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed.
20537 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any
20538 * other validations that are performed within the form. Animations in ngForm are similar to how
20539 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well
20540 * as JS animations.
20542 * The following example shows a simple way to utilize CSS transitions to style a form element
20543 * that has been rendered as invalid after it has been validated:
20546 * //be sure to include ngAnimate as a module to hook into more
20547 * //advanced animations
20549 * transition:0.5s linear all;
20550 * background: white;
20552 * .my-form.ng-invalid {
20559 <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample">
20560 <file name="index.html">
20562 angular.module('formExample', [])
20563 .controller('FormController', ['$scope', function($scope) {
20564 $scope.userType = 'guest';
20569 transition:all linear 0.5s;
20570 background: transparent;
20572 .my-form.ng-invalid {
20576 <form name="myForm" ng-controller="FormController" class="my-form">
20577 userType: <input name="input" ng-model="userType" required>
20578 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br>
20579 <code>userType = {{userType}}</code><br>
20580 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br>
20581 <code>myForm.input.$error = {{myForm.input.$error}}</code><br>
20582 <code>myForm.$valid = {{myForm.$valid}}</code><br>
20583 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br>
20586 <file name="protractor.js" type="protractor">
20587 it('should initialize to model', function() {
20588 var userType = element(by.binding('userType'));
20589 var valid = element(by.binding('myForm.input.$valid'));
20591 expect(userType.getText()).toContain('guest');
20592 expect(valid.getText()).toContain('true');
20595 it('should be invalid if empty', function() {
20596 var userType = element(by.binding('userType'));
20597 var valid = element(by.binding('myForm.input.$valid'));
20598 var userInput = element(by.model('userType'));
20601 userInput.sendKeys('');
20603 expect(userType.getText()).toEqual('userType =');
20604 expect(valid.getText()).toContain('false');
20609 * @param {string=} name Name of the form. If specified, the form controller will be published into
20610 * related scope, under this name.
20612 var formDirectiveFactory = function(isNgForm) {
20613 return ['$timeout', '$parse', function($timeout, $parse) {
20614 var formDirective = {
20616 restrict: isNgForm ? 'EAC' : 'E',
20617 require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form
20618 controller: FormController,
20619 compile: function ngFormCompile(formElement, attr) {
20620 // Setup initial state of the control
20621 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);
20623 var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false);
20626 pre: function ngFormPreLink(scope, formElement, attr, ctrls) {
20627 var controller = ctrls[0];
20629 // if `action` attr is not present on the form, prevent the default action (submission)
20630 if (!('action' in attr)) {
20631 // we can't use jq events because if a form is destroyed during submission the default
20632 // action is not prevented. see #1238
20634 // IE 9 is not affected because it doesn't fire a submit event and try to do a full
20635 // page reload if the form was destroyed by submission of the form via a click handler
20636 // on a button in the form. Looks like an IE9 specific bug.
20637 var handleFormSubmission = function(event) {
20638 scope.$apply(function() {
20639 controller.$commitViewValue();
20640 controller.$setSubmitted();
20643 event.preventDefault();
20646 addEventListenerFn(formElement[0], 'submit', handleFormSubmission);
20648 // unregister the preventDefault listener so that we don't not leak memory but in a
20649 // way that will achieve the prevention of the default action.
20650 formElement.on('$destroy', function() {
20651 $timeout(function() {
20652 removeEventListenerFn(formElement[0], 'submit', handleFormSubmission);
20657 var parentFormCtrl = ctrls[1] || controller.$$parentForm;
20658 parentFormCtrl.$addControl(controller);
20660 var setter = nameAttr ? getSetter(controller.$name) : noop;
20663 setter(scope, controller);
20664 attr.$observe(nameAttr, function(newValue) {
20665 if (controller.$name === newValue) return;
20666 setter(scope, undefined);
20667 controller.$$parentForm.$$renameControl(controller, newValue);
20668 setter = getSetter(controller.$name);
20669 setter(scope, controller);
20672 formElement.on('$destroy', function() {
20673 controller.$$parentForm.$removeControl(controller);
20674 setter(scope, undefined);
20675 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
20682 return formDirective;
20684 function getSetter(expression) {
20685 if (expression === '') {
20686 //create an assignable expression, so forms with an empty name can be renamed later
20687 return $parse('this[""]').assign;
20689 return $parse(expression).assign || noop;
20694 var formDirective = formDirectiveFactory();
20695 var ngFormDirective = formDirectiveFactory(true);
20697 /* global VALID_CLASS: false,
20698 INVALID_CLASS: false,
20699 PRISTINE_CLASS: false,
20700 DIRTY_CLASS: false,
20701 UNTOUCHED_CLASS: false,
20702 TOUCHED_CLASS: false,
20703 ngModelMinErr: false,
20706 // Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231
20707 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)/;
20708 // See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987)
20709 var URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.?+=&%@\-/]*)?$/;
20710 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;
20711 var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/;
20712 var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/;
20713 var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
20714 var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/;
20715 var MONTH_REGEXP = /^(\d{4})-(\d\d)$/;
20716 var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
20722 * @name input[text]
20725 * Standard HTML text input with angular data binding, inherited by most of the `input` elements.
20728 * @param {string} ngModel Assignable angular expression to data-bind to.
20729 * @param {string=} name Property name of the form under which the control is published.
20730 * @param {string=} required Adds `required` validation error key if the value is not entered.
20731 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20732 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20733 * `required` when you want to data-bind to the `required` attribute.
20734 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
20736 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
20737 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
20739 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
20740 * that contains the regular expression body that will be converted to a regular expression
20741 * as in the ngPattern directive.
20742 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
20743 * a RegExp found by evaluating the Angular expression given in the attribute value.
20744 * If the expression evaluates to a RegExp object, then this is used directly.
20745 * If the expression evaluates to a string, then it will be converted to a RegExp
20746 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
20747 * `new RegExp('^abc$')`.<br />
20748 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
20749 * start at the index of the last search's match, thus not taking the whole input value into
20751 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20752 * interaction with the input element.
20753 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
20754 * This parameter is ignored for input[type=password] controls, which will never trim the
20758 <example name="text-input-directive" module="textInputExample">
20759 <file name="index.html">
20761 angular.module('textInputExample', [])
20762 .controller('ExampleController', ['$scope', function($scope) {
20765 word: /^\s*\w*\s*$/
20769 <form name="myForm" ng-controller="ExampleController">
20770 <label>Single word:
20771 <input type="text" name="input" ng-model="example.text"
20772 ng-pattern="example.word" required ng-trim="false">
20775 <span class="error" ng-show="myForm.input.$error.required">
20777 <span class="error" ng-show="myForm.input.$error.pattern">
20778 Single word only!</span>
20780 <tt>text = {{example.text}}</tt><br/>
20781 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20782 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20783 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20784 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20787 <file name="protractor.js" type="protractor">
20788 var text = element(by.binding('example.text'));
20789 var valid = element(by.binding('myForm.input.$valid'));
20790 var input = element(by.model('example.text'));
20792 it('should initialize to model', function() {
20793 expect(text.getText()).toContain('guest');
20794 expect(valid.getText()).toContain('true');
20797 it('should be invalid if empty', function() {
20799 input.sendKeys('');
20801 expect(text.getText()).toEqual('text =');
20802 expect(valid.getText()).toContain('false');
20805 it('should be invalid if multi word', function() {
20807 input.sendKeys('hello world');
20809 expect(valid.getText()).toContain('false');
20814 'text': textInputType,
20818 * @name input[date]
20821 * Input with date validation and transformation. In browsers that do not yet support
20822 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601
20823 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many
20824 * modern browsers do not yet support this input type, it is important to provide cues to users on the
20825 * expected input format via a placeholder or label.
20827 * The model must always be a Date object, otherwise Angular will throw an error.
20828 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
20830 * The timezone to be used to read/write the `Date` instance in the model can be defined using
20831 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
20833 * @param {string} ngModel Assignable angular expression to data-bind to.
20834 * @param {string=} name Property name of the form under which the control is published.
20835 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
20836 * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
20837 * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5
20838 * constraint validation.
20839 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be
20840 * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
20841 * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5
20842 * constraint validation.
20843 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string
20844 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
20845 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string
20846 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
20847 * @param {string=} required Sets `required` validation error key if the value is not entered.
20848 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20849 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20850 * `required` when you want to data-bind to the `required` attribute.
20851 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20852 * interaction with the input element.
20855 <example name="date-input-directive" module="dateInputExample">
20856 <file name="index.html">
20858 angular.module('dateInputExample', [])
20859 .controller('DateController', ['$scope', function($scope) {
20861 value: new Date(2013, 9, 22)
20865 <form name="myForm" ng-controller="DateController as dateCtrl">
20866 <label for="exampleInput">Pick a date in 2013:</label>
20867 <input type="date" id="exampleInput" name="input" ng-model="example.value"
20868 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required />
20870 <span class="error" ng-show="myForm.input.$error.required">
20872 <span class="error" ng-show="myForm.input.$error.date">
20873 Not a valid date!</span>
20875 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/>
20876 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20877 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20878 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20879 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20882 <file name="protractor.js" type="protractor">
20883 var value = element(by.binding('example.value | date: "yyyy-MM-dd"'));
20884 var valid = element(by.binding('myForm.input.$valid'));
20885 var input = element(by.model('example.value'));
20887 // currently protractor/webdriver does not support
20888 // sending keys to all known HTML5 input controls
20889 // for various browsers (see https://github.com/angular/protractor/issues/562).
20890 function setInput(val) {
20891 // set the value of the element and force validation.
20892 var scr = "var ipt = document.getElementById('exampleInput'); " +
20893 "ipt.value = '" + val + "';" +
20894 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
20895 browser.executeScript(scr);
20898 it('should initialize to model', function() {
20899 expect(value.getText()).toContain('2013-10-22');
20900 expect(valid.getText()).toContain('myForm.input.$valid = true');
20903 it('should be invalid if empty', function() {
20905 expect(value.getText()).toEqual('value =');
20906 expect(valid.getText()).toContain('myForm.input.$valid = false');
20909 it('should be invalid if over max', function() {
20910 setInput('2015-01-01');
20911 expect(value.getText()).toContain('');
20912 expect(valid.getText()).toContain('myForm.input.$valid = false');
20917 'date': createDateInputType('date', DATE_REGEXP,
20918 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']),
20923 * @name input[datetime-local]
20926 * Input with datetime validation and transformation. In browsers that do not yet support
20927 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
20928 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`.
20930 * The model must always be a Date object, otherwise Angular will throw an error.
20931 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
20933 * The timezone to be used to read/write the `Date` instance in the model can be defined using
20934 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
20936 * @param {string} ngModel Assignable angular expression to data-bind to.
20937 * @param {string=} name Property name of the form under which the control is published.
20938 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
20939 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
20940 * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
20941 * Note that `min` will also add native HTML5 constraint validation.
20942 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
20943 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
20944 * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
20945 * Note that `max` will also add native HTML5 constraint validation.
20946 * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string
20947 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
20948 * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string
20949 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
20950 * @param {string=} required Sets `required` validation error key if the value is not entered.
20951 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
20952 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
20953 * `required` when you want to data-bind to the `required` attribute.
20954 * @param {string=} ngChange Angular expression to be executed when input changes due to user
20955 * interaction with the input element.
20958 <example name="datetimelocal-input-directive" module="dateExample">
20959 <file name="index.html">
20961 angular.module('dateExample', [])
20962 .controller('DateController', ['$scope', function($scope) {
20964 value: new Date(2010, 11, 28, 14, 57)
20968 <form name="myForm" ng-controller="DateController as dateCtrl">
20969 <label for="exampleInput">Pick a date between in 2013:</label>
20970 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value"
20971 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required />
20973 <span class="error" ng-show="myForm.input.$error.required">
20975 <span class="error" ng-show="myForm.input.$error.datetimelocal">
20976 Not a valid date!</span>
20978 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/>
20979 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
20980 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
20981 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
20982 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
20985 <file name="protractor.js" type="protractor">
20986 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"'));
20987 var valid = element(by.binding('myForm.input.$valid'));
20988 var input = element(by.model('example.value'));
20990 // currently protractor/webdriver does not support
20991 // sending keys to all known HTML5 input controls
20992 // for various browsers (https://github.com/angular/protractor/issues/562).
20993 function setInput(val) {
20994 // set the value of the element and force validation.
20995 var scr = "var ipt = document.getElementById('exampleInput'); " +
20996 "ipt.value = '" + val + "';" +
20997 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
20998 browser.executeScript(scr);
21001 it('should initialize to model', function() {
21002 expect(value.getText()).toContain('2010-12-28T14:57:00');
21003 expect(valid.getText()).toContain('myForm.input.$valid = true');
21006 it('should be invalid if empty', function() {
21008 expect(value.getText()).toEqual('value =');
21009 expect(valid.getText()).toContain('myForm.input.$valid = false');
21012 it('should be invalid if over max', function() {
21013 setInput('2015-01-01T23:59:00');
21014 expect(value.getText()).toContain('');
21015 expect(valid.getText()).toContain('myForm.input.$valid = false');
21020 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP,
21021 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']),
21022 'yyyy-MM-ddTHH:mm:ss.sss'),
21026 * @name input[time]
21029 * Input with time validation and transformation. In browsers that do not yet support
21030 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
21031 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a
21032 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`.
21034 * The model must always be a Date object, otherwise Angular will throw an error.
21035 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
21037 * The timezone to be used to read/write the `Date` instance in the model can be defined using
21038 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
21040 * @param {string} ngModel Assignable angular expression to data-bind to.
21041 * @param {string=} name Property name of the form under which the control is published.
21042 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21043 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
21044 * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add
21045 * native HTML5 constraint validation.
21046 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21047 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
21048 * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add
21049 * native HTML5 constraint validation.
21050 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the
21051 * `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
21052 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the
21053 * `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
21054 * @param {string=} required Sets `required` validation error key if the value is not entered.
21055 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21056 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21057 * `required` when you want to data-bind to the `required` attribute.
21058 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21059 * interaction with the input element.
21062 <example name="time-input-directive" module="timeExample">
21063 <file name="index.html">
21065 angular.module('timeExample', [])
21066 .controller('DateController', ['$scope', function($scope) {
21068 value: new Date(1970, 0, 1, 14, 57, 0)
21072 <form name="myForm" ng-controller="DateController as dateCtrl">
21073 <label for="exampleInput">Pick a between 8am and 5pm:</label>
21074 <input type="time" id="exampleInput" name="input" ng-model="example.value"
21075 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required />
21077 <span class="error" ng-show="myForm.input.$error.required">
21079 <span class="error" ng-show="myForm.input.$error.time">
21080 Not a valid date!</span>
21082 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/>
21083 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21084 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21085 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21086 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21089 <file name="protractor.js" type="protractor">
21090 var value = element(by.binding('example.value | date: "HH:mm:ss"'));
21091 var valid = element(by.binding('myForm.input.$valid'));
21092 var input = element(by.model('example.value'));
21094 // currently protractor/webdriver does not support
21095 // sending keys to all known HTML5 input controls
21096 // for various browsers (https://github.com/angular/protractor/issues/562).
21097 function setInput(val) {
21098 // set the value of the element and force validation.
21099 var scr = "var ipt = document.getElementById('exampleInput'); " +
21100 "ipt.value = '" + val + "';" +
21101 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21102 browser.executeScript(scr);
21105 it('should initialize to model', function() {
21106 expect(value.getText()).toContain('14:57:00');
21107 expect(valid.getText()).toContain('myForm.input.$valid = true');
21110 it('should be invalid if empty', function() {
21112 expect(value.getText()).toEqual('value =');
21113 expect(valid.getText()).toContain('myForm.input.$valid = false');
21116 it('should be invalid if over max', function() {
21117 setInput('23:59:00');
21118 expect(value.getText()).toContain('');
21119 expect(valid.getText()).toContain('myForm.input.$valid = false');
21124 'time': createDateInputType('time', TIME_REGEXP,
21125 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']),
21130 * @name input[week]
21133 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support
21134 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
21135 * week format (yyyy-W##), for example: `2013-W02`.
21137 * The model must always be a Date object, otherwise Angular will throw an error.
21138 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
21140 * The timezone to be used to read/write the `Date` instance in the model can be defined using
21141 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
21143 * @param {string} ngModel Assignable angular expression to data-bind to.
21144 * @param {string=} name Property name of the form under which the control is published.
21145 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21146 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
21147 * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add
21148 * native HTML5 constraint validation.
21149 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21150 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
21151 * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add
21152 * native HTML5 constraint validation.
21153 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
21154 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
21155 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
21156 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
21157 * @param {string=} required Sets `required` validation error key if the value is not entered.
21158 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21159 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21160 * `required` when you want to data-bind to the `required` attribute.
21161 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21162 * interaction with the input element.
21165 <example name="week-input-directive" module="weekExample">
21166 <file name="index.html">
21168 angular.module('weekExample', [])
21169 .controller('DateController', ['$scope', function($scope) {
21171 value: new Date(2013, 0, 3)
21175 <form name="myForm" ng-controller="DateController as dateCtrl">
21176 <label>Pick a date between in 2013:
21177 <input id="exampleInput" type="week" name="input" ng-model="example.value"
21178 placeholder="YYYY-W##" min="2012-W32"
21179 max="2013-W52" required />
21182 <span class="error" ng-show="myForm.input.$error.required">
21184 <span class="error" ng-show="myForm.input.$error.week">
21185 Not a valid date!</span>
21187 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/>
21188 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21189 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21190 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21191 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21194 <file name="protractor.js" type="protractor">
21195 var value = element(by.binding('example.value | date: "yyyy-Www"'));
21196 var valid = element(by.binding('myForm.input.$valid'));
21197 var input = element(by.model('example.value'));
21199 // currently protractor/webdriver does not support
21200 // sending keys to all known HTML5 input controls
21201 // for various browsers (https://github.com/angular/protractor/issues/562).
21202 function setInput(val) {
21203 // set the value of the element and force validation.
21204 var scr = "var ipt = document.getElementById('exampleInput'); " +
21205 "ipt.value = '" + val + "';" +
21206 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21207 browser.executeScript(scr);
21210 it('should initialize to model', function() {
21211 expect(value.getText()).toContain('2013-W01');
21212 expect(valid.getText()).toContain('myForm.input.$valid = true');
21215 it('should be invalid if empty', function() {
21217 expect(value.getText()).toEqual('value =');
21218 expect(valid.getText()).toContain('myForm.input.$valid = false');
21221 it('should be invalid if over max', function() {
21222 setInput('2015-W01');
21223 expect(value.getText()).toContain('');
21224 expect(valid.getText()).toContain('myForm.input.$valid = false');
21229 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'),
21233 * @name input[month]
21236 * Input with month validation and transformation. In browsers that do not yet support
21237 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
21238 * month format (yyyy-MM), for example: `2009-01`.
21240 * The model must always be a Date object, otherwise Angular will throw an error.
21241 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
21242 * If the model is not set to the first of the month, the next view to model update will set it
21243 * to the first of the month.
21245 * The timezone to be used to read/write the `Date` instance in the model can be defined using
21246 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
21248 * @param {string} ngModel Assignable angular expression to data-bind to.
21249 * @param {string=} name Property name of the form under which the control is published.
21250 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21251 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
21252 * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add
21253 * native HTML5 constraint validation.
21254 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21255 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
21256 * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add
21257 * native HTML5 constraint validation.
21258 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
21259 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
21260 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
21261 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
21263 * @param {string=} required Sets `required` validation error key if the value is not entered.
21264 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21265 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21266 * `required` when you want to data-bind to the `required` attribute.
21267 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21268 * interaction with the input element.
21271 <example name="month-input-directive" module="monthExample">
21272 <file name="index.html">
21274 angular.module('monthExample', [])
21275 .controller('DateController', ['$scope', function($scope) {
21277 value: new Date(2013, 9, 1)
21281 <form name="myForm" ng-controller="DateController as dateCtrl">
21282 <label for="exampleInput">Pick a month in 2013:</label>
21283 <input id="exampleInput" type="month" name="input" ng-model="example.value"
21284 placeholder="yyyy-MM" min="2013-01" max="2013-12" required />
21286 <span class="error" ng-show="myForm.input.$error.required">
21288 <span class="error" ng-show="myForm.input.$error.month">
21289 Not a valid month!</span>
21291 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/>
21292 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21293 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21294 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21295 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21298 <file name="protractor.js" type="protractor">
21299 var value = element(by.binding('example.value | date: "yyyy-MM"'));
21300 var valid = element(by.binding('myForm.input.$valid'));
21301 var input = element(by.model('example.value'));
21303 // currently protractor/webdriver does not support
21304 // sending keys to all known HTML5 input controls
21305 // for various browsers (https://github.com/angular/protractor/issues/562).
21306 function setInput(val) {
21307 // set the value of the element and force validation.
21308 var scr = "var ipt = document.getElementById('exampleInput'); " +
21309 "ipt.value = '" + val + "';" +
21310 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
21311 browser.executeScript(scr);
21314 it('should initialize to model', function() {
21315 expect(value.getText()).toContain('2013-10');
21316 expect(valid.getText()).toContain('myForm.input.$valid = true');
21319 it('should be invalid if empty', function() {
21321 expect(value.getText()).toEqual('value =');
21322 expect(valid.getText()).toContain('myForm.input.$valid = false');
21325 it('should be invalid if over max', function() {
21326 setInput('2015-01');
21327 expect(value.getText()).toContain('');
21328 expect(valid.getText()).toContain('myForm.input.$valid = false');
21333 'month': createDateInputType('month', MONTH_REGEXP,
21334 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']),
21339 * @name input[number]
21342 * Text input with number validation and transformation. Sets the `number` validation
21343 * error if not a valid number.
21345 * <div class="alert alert-warning">
21346 * The model must always be of type `number` otherwise Angular will throw an error.
21347 * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt}
21348 * error docs for more information and an example of how to convert your model if necessary.
21351 * ## Issues with HTML5 constraint validation
21353 * In browsers that follow the
21354 * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29),
21355 * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}.
21356 * If a non-number is entered in the input, the browser will report the value as an empty string,
21357 * which means the view / model values in `ngModel` and subsequently the scope value
21358 * will also be an empty string.
21361 * @param {string} ngModel Assignable angular expression to data-bind to.
21362 * @param {string=} name Property name of the form under which the control is published.
21363 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
21364 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
21365 * @param {string=} required Sets `required` validation error key if the value is not entered.
21366 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21367 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21368 * `required` when you want to data-bind to the `required` attribute.
21369 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21371 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21372 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21374 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21375 * that contains the regular expression body that will be converted to a regular expression
21376 * as in the ngPattern directive.
21377 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21378 * a RegExp found by evaluating the Angular expression given in the attribute value.
21379 * If the expression evaluates to a RegExp object, then this is used directly.
21380 * If the expression evaluates to a string, then it will be converted to a RegExp
21381 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21382 * `new RegExp('^abc$')`.<br />
21383 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21384 * start at the index of the last search's match, thus not taking the whole input value into
21386 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21387 * interaction with the input element.
21390 <example name="number-input-directive" module="numberExample">
21391 <file name="index.html">
21393 angular.module('numberExample', [])
21394 .controller('ExampleController', ['$scope', function($scope) {
21400 <form name="myForm" ng-controller="ExampleController">
21402 <input type="number" name="input" ng-model="example.value"
21403 min="0" max="99" required>
21406 <span class="error" ng-show="myForm.input.$error.required">
21408 <span class="error" ng-show="myForm.input.$error.number">
21409 Not valid number!</span>
21411 <tt>value = {{example.value}}</tt><br/>
21412 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21413 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21414 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21415 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21418 <file name="protractor.js" type="protractor">
21419 var value = element(by.binding('example.value'));
21420 var valid = element(by.binding('myForm.input.$valid'));
21421 var input = element(by.model('example.value'));
21423 it('should initialize to model', function() {
21424 expect(value.getText()).toContain('12');
21425 expect(valid.getText()).toContain('true');
21428 it('should be invalid if empty', function() {
21430 input.sendKeys('');
21431 expect(value.getText()).toEqual('value =');
21432 expect(valid.getText()).toContain('false');
21435 it('should be invalid if over max', function() {
21437 input.sendKeys('123');
21438 expect(value.getText()).toEqual('value =');
21439 expect(valid.getText()).toContain('false');
21444 'number': numberInputType,
21452 * Text input with URL validation. Sets the `url` validation error key if the content is not a
21455 * <div class="alert alert-warning">
21456 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex
21457 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify
21458 * the built-in validators (see the {@link guide/forms Forms guide})
21461 * @param {string} ngModel Assignable angular expression to data-bind to.
21462 * @param {string=} name Property name of the form under which the control is published.
21463 * @param {string=} required Sets `required` validation error key if the value is not entered.
21464 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21465 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21466 * `required` when you want to data-bind to the `required` attribute.
21467 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21469 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21470 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21472 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21473 * that contains the regular expression body that will be converted to a regular expression
21474 * as in the ngPattern directive.
21475 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21476 * a RegExp found by evaluating the Angular expression given in the attribute value.
21477 * If the expression evaluates to a RegExp object, then this is used directly.
21478 * If the expression evaluates to a string, then it will be converted to a RegExp
21479 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21480 * `new RegExp('^abc$')`.<br />
21481 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21482 * start at the index of the last search's match, thus not taking the whole input value into
21484 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21485 * interaction with the input element.
21488 <example name="url-input-directive" module="urlExample">
21489 <file name="index.html">
21491 angular.module('urlExample', [])
21492 .controller('ExampleController', ['$scope', function($scope) {
21494 text: 'http://google.com'
21498 <form name="myForm" ng-controller="ExampleController">
21500 <input type="url" name="input" ng-model="url.text" required>
21503 <span class="error" ng-show="myForm.input.$error.required">
21505 <span class="error" ng-show="myForm.input.$error.url">
21506 Not valid url!</span>
21508 <tt>text = {{url.text}}</tt><br/>
21509 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21510 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21511 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21512 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21513 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/>
21516 <file name="protractor.js" type="protractor">
21517 var text = element(by.binding('url.text'));
21518 var valid = element(by.binding('myForm.input.$valid'));
21519 var input = element(by.model('url.text'));
21521 it('should initialize to model', function() {
21522 expect(text.getText()).toContain('http://google.com');
21523 expect(valid.getText()).toContain('true');
21526 it('should be invalid if empty', function() {
21528 input.sendKeys('');
21530 expect(text.getText()).toEqual('text =');
21531 expect(valid.getText()).toContain('false');
21534 it('should be invalid if not url', function() {
21536 input.sendKeys('box');
21538 expect(valid.getText()).toContain('false');
21543 'url': urlInputType,
21548 * @name input[email]
21551 * Text input with email validation. Sets the `email` validation error key if not a valid email
21554 * <div class="alert alert-warning">
21555 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex
21556 * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can
21557 * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide})
21560 * @param {string} ngModel Assignable angular expression to data-bind to.
21561 * @param {string=} name Property name of the form under which the control is published.
21562 * @param {string=} required Sets `required` validation error key if the value is not entered.
21563 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
21564 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
21565 * `required` when you want to data-bind to the `required` attribute.
21566 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
21568 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
21569 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
21571 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
21572 * that contains the regular expression body that will be converted to a regular expression
21573 * as in the ngPattern directive.
21574 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
21575 * a RegExp found by evaluating the Angular expression given in the attribute value.
21576 * If the expression evaluates to a RegExp object, then this is used directly.
21577 * If the expression evaluates to a string, then it will be converted to a RegExp
21578 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
21579 * `new RegExp('^abc$')`.<br />
21580 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
21581 * start at the index of the last search's match, thus not taking the whole input value into
21583 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21584 * interaction with the input element.
21587 <example name="email-input-directive" module="emailExample">
21588 <file name="index.html">
21590 angular.module('emailExample', [])
21591 .controller('ExampleController', ['$scope', function($scope) {
21593 text: 'me@example.com'
21597 <form name="myForm" ng-controller="ExampleController">
21599 <input type="email" name="input" ng-model="email.text" required>
21602 <span class="error" ng-show="myForm.input.$error.required">
21604 <span class="error" ng-show="myForm.input.$error.email">
21605 Not valid email!</span>
21607 <tt>text = {{email.text}}</tt><br/>
21608 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
21609 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
21610 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
21611 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
21612 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/>
21615 <file name="protractor.js" type="protractor">
21616 var text = element(by.binding('email.text'));
21617 var valid = element(by.binding('myForm.input.$valid'));
21618 var input = element(by.model('email.text'));
21620 it('should initialize to model', function() {
21621 expect(text.getText()).toContain('me@example.com');
21622 expect(valid.getText()).toContain('true');
21625 it('should be invalid if empty', function() {
21627 input.sendKeys('');
21628 expect(text.getText()).toEqual('text =');
21629 expect(valid.getText()).toContain('false');
21632 it('should be invalid if not email', function() {
21634 input.sendKeys('xxx');
21636 expect(valid.getText()).toContain('false');
21641 'email': emailInputType,
21646 * @name input[radio]
21649 * HTML radio button.
21651 * @param {string} ngModel Assignable angular expression to data-bind to.
21652 * @param {string} value The value to which the `ngModel` expression should be set when selected.
21653 * Note that `value` only supports `string` values, i.e. the scope model needs to be a string,
21654 * too. Use `ngValue` if you need complex models (`number`, `object`, ...).
21655 * @param {string=} name Property name of the form under which the control is published.
21656 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21657 * interaction with the input element.
21658 * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio
21659 * is selected. Should be used instead of the `value` attribute if you need
21660 * a non-string `ngModel` (`boolean`, `array`, ...).
21663 <example name="radio-input-directive" module="radioExample">
21664 <file name="index.html">
21666 angular.module('radioExample', [])
21667 .controller('ExampleController', ['$scope', function($scope) {
21671 $scope.specialValue = {
21677 <form name="myForm" ng-controller="ExampleController">
21679 <input type="radio" ng-model="color.name" value="red">
21683 <input type="radio" ng-model="color.name" ng-value="specialValue">
21687 <input type="radio" ng-model="color.name" value="blue">
21690 <tt>color = {{color.name | json}}</tt><br/>
21692 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
21694 <file name="protractor.js" type="protractor">
21695 it('should change state', function() {
21696 var color = element(by.binding('color.name'));
21698 expect(color.getText()).toContain('blue');
21700 element.all(by.model('color.name')).get(0).click();
21702 expect(color.getText()).toContain('red');
21707 'radio': radioInputType,
21712 * @name input[checkbox]
21717 * @param {string} ngModel Assignable angular expression to data-bind to.
21718 * @param {string=} name Property name of the form under which the control is published.
21719 * @param {expression=} ngTrueValue The value to which the expression should be set when selected.
21720 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected.
21721 * @param {string=} ngChange Angular expression to be executed when input changes due to user
21722 * interaction with the input element.
21725 <example name="checkbox-input-directive" module="checkboxExample">
21726 <file name="index.html">
21728 angular.module('checkboxExample', [])
21729 .controller('ExampleController', ['$scope', function($scope) {
21730 $scope.checkboxModel = {
21736 <form name="myForm" ng-controller="ExampleController">
21738 <input type="checkbox" ng-model="checkboxModel.value1">
21741 <input type="checkbox" ng-model="checkboxModel.value2"
21742 ng-true-value="'YES'" ng-false-value="'NO'">
21744 <tt>value1 = {{checkboxModel.value1}}</tt><br/>
21745 <tt>value2 = {{checkboxModel.value2}}</tt><br/>
21748 <file name="protractor.js" type="protractor">
21749 it('should change state', function() {
21750 var value1 = element(by.binding('checkboxModel.value1'));
21751 var value2 = element(by.binding('checkboxModel.value2'));
21753 expect(value1.getText()).toContain('true');
21754 expect(value2.getText()).toContain('YES');
21756 element(by.model('checkboxModel.value1')).click();
21757 element(by.model('checkboxModel.value2')).click();
21759 expect(value1.getText()).toContain('false');
21760 expect(value2.getText()).toContain('NO');
21765 'checkbox': checkboxInputType,
21774 function stringBasedInputType(ctrl) {
21775 ctrl.$formatters.push(function(value) {
21776 return ctrl.$isEmpty(value) ? value : value.toString();
21780 function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
21781 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
21782 stringBasedInputType(ctrl);
21785 function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
21786 var type = lowercase(element[0].type);
21788 // In composition mode, users are still inputing intermediate text buffer,
21789 // hold the listener until composition is done.
21790 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
21791 if (!$sniffer.android) {
21792 var composing = false;
21794 element.on('compositionstart', function(data) {
21798 element.on('compositionend', function() {
21804 var listener = function(ev) {
21806 $browser.defer.cancel(timeout);
21809 if (composing) return;
21810 var value = element.val(),
21811 event = ev && ev.type;
21813 // By default we will trim the value
21814 // If the attribute ng-trim exists we will avoid trimming
21815 // If input type is 'password', the value is never trimmed
21816 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) {
21817 value = trim(value);
21820 // If a control is suffering from bad input (due to native validators), browsers discard its
21821 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the
21822 // control's value is the same empty value twice in a row.
21823 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) {
21824 ctrl.$setViewValue(value, event);
21828 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the
21829 // input event on backspace, delete or cut
21830 if ($sniffer.hasEvent('input')) {
21831 element.on('input', listener);
21835 var deferListener = function(ev, input, origValue) {
21837 timeout = $browser.defer(function() {
21839 if (!input || input.value !== origValue) {
21846 element.on('keydown', function(event) {
21847 var key = event.keyCode;
21850 // command modifiers arrows
21851 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
21853 deferListener(event, this, this.value);
21856 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
21857 if ($sniffer.hasEvent('paste')) {
21858 element.on('paste cut', deferListener);
21862 // if user paste into input using mouse on older browser
21863 // or form autocomplete on newer browser, we need "change" event to catch it
21864 element.on('change', listener);
21866 ctrl.$render = function() {
21867 // Workaround for Firefox validation #12102.
21868 var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue;
21869 if (element.val() !== value) {
21870 element.val(value);
21875 function weekParser(isoWeek, existingDate) {
21876 if (isDate(isoWeek)) {
21880 if (isString(isoWeek)) {
21881 WEEK_REGEXP.lastIndex = 0;
21882 var parts = WEEK_REGEXP.exec(isoWeek);
21884 var year = +parts[1],
21890 firstThurs = getFirstThursdayOfYear(year),
21891 addDays = (week - 1) * 7;
21893 if (existingDate) {
21894 hours = existingDate.getHours();
21895 minutes = existingDate.getMinutes();
21896 seconds = existingDate.getSeconds();
21897 milliseconds = existingDate.getMilliseconds();
21900 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds);
21907 function createDateParser(regexp, mapping) {
21908 return function(iso, date) {
21915 if (isString(iso)) {
21916 // When a date is JSON'ified to wraps itself inside of an extra
21917 // set of double quotes. This makes the date parsing code unable
21918 // to match the date string and parse it as a date.
21919 if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') {
21920 iso = iso.substring(1, iso.length - 1);
21922 if (ISO_DATE_REGEXP.test(iso)) {
21923 return new Date(iso);
21925 regexp.lastIndex = 0;
21926 parts = regexp.exec(iso);
21932 yyyy: date.getFullYear(),
21933 MM: date.getMonth() + 1,
21934 dd: date.getDate(),
21935 HH: date.getHours(),
21936 mm: date.getMinutes(),
21937 ss: date.getSeconds(),
21938 sss: date.getMilliseconds() / 1000
21941 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 };
21944 forEach(parts, function(part, index) {
21945 if (index < mapping.length) {
21946 map[mapping[index]] = +part;
21949 return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0);
21957 function createDateInputType(type, regexp, parseDate, format) {
21958 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
21959 badInputChecker(scope, element, attr, ctrl);
21960 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
21961 var timezone = ctrl && ctrl.$options && ctrl.$options.timezone;
21964 ctrl.$$parserName = type;
21965 ctrl.$parsers.push(function(value) {
21966 if (ctrl.$isEmpty(value)) return null;
21967 if (regexp.test(value)) {
21968 // Note: We cannot read ctrl.$modelValue, as there might be a different
21969 // parser/formatter in the processing chain so that the model
21970 // contains some different data format!
21971 var parsedDate = parseDate(value, previousDate);
21973 parsedDate = convertTimezoneToLocal(parsedDate, timezone);
21980 ctrl.$formatters.push(function(value) {
21981 if (value && !isDate(value)) {
21982 throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
21984 if (isValidDate(value)) {
21985 previousDate = value;
21986 if (previousDate && timezone) {
21987 previousDate = convertTimezoneToLocal(previousDate, timezone, true);
21989 return $filter('date')(value, format, timezone);
21991 previousDate = null;
21996 if (isDefined(attr.min) || attr.ngMin) {
21998 ctrl.$validators.min = function(value) {
21999 return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal;
22001 attr.$observe('min', function(val) {
22002 minVal = parseObservedDateValue(val);
22007 if (isDefined(attr.max) || attr.ngMax) {
22009 ctrl.$validators.max = function(value) {
22010 return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal;
22012 attr.$observe('max', function(val) {
22013 maxVal = parseObservedDateValue(val);
22018 function isValidDate(value) {
22019 // Invalid Date: getTime() returns NaN
22020 return value && !(value.getTime && value.getTime() !== value.getTime());
22023 function parseObservedDateValue(val) {
22024 return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val;
22029 function badInputChecker(scope, element, attr, ctrl) {
22030 var node = element[0];
22031 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity);
22032 if (nativeValidation) {
22033 ctrl.$parsers.push(function(value) {
22034 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {};
22035 // Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430):
22036 // - also sets validity.badInput (should only be validity.typeMismatch).
22037 // - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email)
22038 // - can ignore this case as we can still read out the erroneous email...
22039 return validity.badInput && !validity.typeMismatch ? undefined : value;
22044 function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
22045 badInputChecker(scope, element, attr, ctrl);
22046 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
22048 ctrl.$$parserName = 'number';
22049 ctrl.$parsers.push(function(value) {
22050 if (ctrl.$isEmpty(value)) return null;
22051 if (NUMBER_REGEXP.test(value)) return parseFloat(value);
22055 ctrl.$formatters.push(function(value) {
22056 if (!ctrl.$isEmpty(value)) {
22057 if (!isNumber(value)) {
22058 throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value);
22060 value = value.toString();
22065 if (isDefined(attr.min) || attr.ngMin) {
22067 ctrl.$validators.min = function(value) {
22068 return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal;
22071 attr.$observe('min', function(val) {
22072 if (isDefined(val) && !isNumber(val)) {
22073 val = parseFloat(val, 10);
22075 minVal = isNumber(val) && !isNaN(val) ? val : undefined;
22076 // TODO(matsko): implement validateLater to reduce number of validations
22081 if (isDefined(attr.max) || attr.ngMax) {
22083 ctrl.$validators.max = function(value) {
22084 return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal;
22087 attr.$observe('max', function(val) {
22088 if (isDefined(val) && !isNumber(val)) {
22089 val = parseFloat(val, 10);
22091 maxVal = isNumber(val) && !isNaN(val) ? val : undefined;
22092 // TODO(matsko): implement validateLater to reduce number of validations
22098 function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
22099 // Note: no badInputChecker here by purpose as `url` is only a validation
22100 // in browsers, i.e. we can always read out input.value even if it is not valid!
22101 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
22102 stringBasedInputType(ctrl);
22104 ctrl.$$parserName = 'url';
22105 ctrl.$validators.url = function(modelValue, viewValue) {
22106 var value = modelValue || viewValue;
22107 return ctrl.$isEmpty(value) || URL_REGEXP.test(value);
22111 function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
22112 // Note: no badInputChecker here by purpose as `url` is only a validation
22113 // in browsers, i.e. we can always read out input.value even if it is not valid!
22114 baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
22115 stringBasedInputType(ctrl);
22117 ctrl.$$parserName = 'email';
22118 ctrl.$validators.email = function(modelValue, viewValue) {
22119 var value = modelValue || viewValue;
22120 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value);
22124 function radioInputType(scope, element, attr, ctrl) {
22125 // make the name unique, if not defined
22126 if (isUndefined(attr.name)) {
22127 element.attr('name', nextUid());
22130 var listener = function(ev) {
22131 if (element[0].checked) {
22132 ctrl.$setViewValue(attr.value, ev && ev.type);
22136 element.on('click', listener);
22138 ctrl.$render = function() {
22139 var value = attr.value;
22140 element[0].checked = (value == ctrl.$viewValue);
22143 attr.$observe('value', ctrl.$render);
22146 function parseConstantExpr($parse, context, name, expression, fallback) {
22148 if (isDefined(expression)) {
22149 parseFn = $parse(expression);
22150 if (!parseFn.constant) {
22151 throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' +
22152 '`{1}`.', name, expression);
22154 return parseFn(context);
22159 function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
22160 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true);
22161 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false);
22163 var listener = function(ev) {
22164 ctrl.$setViewValue(element[0].checked, ev && ev.type);
22167 element.on('click', listener);
22169 ctrl.$render = function() {
22170 element[0].checked = ctrl.$viewValue;
22173 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false`
22174 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert
22175 // it to a boolean.
22176 ctrl.$isEmpty = function(value) {
22177 return value === false;
22180 ctrl.$formatters.push(function(value) {
22181 return equals(value, trueValue);
22184 ctrl.$parsers.push(function(value) {
22185 return value ? trueValue : falseValue;
22196 * HTML textarea element control with angular data-binding. The data-binding and validation
22197 * properties of this element are exactly the same as those of the
22198 * {@link ng.directive:input input element}.
22200 * @param {string} ngModel Assignable angular expression to data-bind to.
22201 * @param {string=} name Property name of the form under which the control is published.
22202 * @param {string=} required Sets `required` validation error key if the value is not entered.
22203 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
22204 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
22205 * `required` when you want to data-bind to the `required` attribute.
22206 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
22208 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
22209 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
22211 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
22212 * a RegExp found by evaluating the Angular expression given in the attribute value.
22213 * If the expression evaluates to a RegExp object, then this is used directly.
22214 * If the expression evaluates to a string, then it will be converted to a RegExp
22215 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
22216 * `new RegExp('^abc$')`.<br />
22217 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
22218 * start at the index of the last search's match, thus not taking the whole input value into
22220 * @param {string=} ngChange Angular expression to be executed when input changes due to user
22221 * interaction with the input element.
22222 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
22232 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding,
22233 * input state control, and validation.
22234 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers.
22236 * <div class="alert alert-warning">
22237 * **Note:** Not every feature offered is available for all input types.
22238 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`.
22241 * @param {string} ngModel Assignable angular expression to data-bind to.
22242 * @param {string=} name Property name of the form under which the control is published.
22243 * @param {string=} required Sets `required` validation error key if the value is not entered.
22244 * @param {boolean=} ngRequired Sets `required` attribute if set to true
22245 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
22247 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
22248 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
22250 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
22251 * a RegExp found by evaluating the Angular expression given in the attribute value.
22252 * If the expression evaluates to a RegExp object, then this is used directly.
22253 * If the expression evaluates to a string, then it will be converted to a RegExp
22254 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
22255 * `new RegExp('^abc$')`.<br />
22256 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
22257 * start at the index of the last search's match, thus not taking the whole input value into
22259 * @param {string=} ngChange Angular expression to be executed when input changes due to user
22260 * interaction with the input element.
22261 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
22262 * This parameter is ignored for input[type=password] controls, which will never trim the
22266 <example name="input-directive" module="inputExample">
22267 <file name="index.html">
22269 angular.module('inputExample', [])
22270 .controller('ExampleController', ['$scope', function($scope) {
22271 $scope.user = {name: 'guest', last: 'visitor'};
22274 <div ng-controller="ExampleController">
22275 <form name="myForm">
22278 <input type="text" name="userName" ng-model="user.name" required>
22281 <span class="error" ng-show="myForm.userName.$error.required">
22286 <input type="text" name="lastName" ng-model="user.last"
22287 ng-minlength="3" ng-maxlength="10">
22290 <span class="error" ng-show="myForm.lastName.$error.minlength">
22292 <span class="error" ng-show="myForm.lastName.$error.maxlength">
22297 <tt>user = {{user}}</tt><br/>
22298 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/>
22299 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/>
22300 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/>
22301 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/>
22302 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
22303 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
22304 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/>
22305 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/>
22308 <file name="protractor.js" type="protractor">
22309 var user = element(by.exactBinding('user'));
22310 var userNameValid = element(by.binding('myForm.userName.$valid'));
22311 var lastNameValid = element(by.binding('myForm.lastName.$valid'));
22312 var lastNameError = element(by.binding('myForm.lastName.$error'));
22313 var formValid = element(by.binding('myForm.$valid'));
22314 var userNameInput = element(by.model('user.name'));
22315 var userLastInput = element(by.model('user.last'));
22317 it('should initialize to model', function() {
22318 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}');
22319 expect(userNameValid.getText()).toContain('true');
22320 expect(formValid.getText()).toContain('true');
22323 it('should be invalid if empty when required', function() {
22324 userNameInput.clear();
22325 userNameInput.sendKeys('');
22327 expect(user.getText()).toContain('{"last":"visitor"}');
22328 expect(userNameValid.getText()).toContain('false');
22329 expect(formValid.getText()).toContain('false');
22332 it('should be valid if empty when min length is set', function() {
22333 userLastInput.clear();
22334 userLastInput.sendKeys('');
22336 expect(user.getText()).toContain('{"name":"guest","last":""}');
22337 expect(lastNameValid.getText()).toContain('true');
22338 expect(formValid.getText()).toContain('true');
22341 it('should be invalid if less than required min length', function() {
22342 userLastInput.clear();
22343 userLastInput.sendKeys('xx');
22345 expect(user.getText()).toContain('{"name":"guest"}');
22346 expect(lastNameValid.getText()).toContain('false');
22347 expect(lastNameError.getText()).toContain('minlength');
22348 expect(formValid.getText()).toContain('false');
22351 it('should be invalid if longer than max length', function() {
22352 userLastInput.clear();
22353 userLastInput.sendKeys('some ridiculously long name');
22355 expect(user.getText()).toContain('{"name":"guest"}');
22356 expect(lastNameValid.getText()).toContain('false');
22357 expect(lastNameError.getText()).toContain('maxlength');
22358 expect(formValid.getText()).toContain('false');
22363 var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
22364 function($browser, $sniffer, $filter, $parse) {
22367 require: ['?ngModel'],
22369 pre: function(scope, element, attr, ctrls) {
22371 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
22372 $browser, $filter, $parse);
22381 var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
22387 * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`},
22388 * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to
22391 * `ngValue` is useful when dynamically generating lists of radio buttons using
22392 * {@link ngRepeat `ngRepeat`}, as shown below.
22394 * Likewise, `ngValue` can be used to generate `<option>` elements for
22395 * the {@link select `select`} element. In that case however, only strings are supported
22396 * for the `value `attribute, so the resulting `ngModel` will always be a string.
22397 * Support for `select` models with non-string values is available via `ngOptions`.
22400 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute
22401 * of the `input` element
22404 <example name="ngValue-directive" module="valueExample">
22405 <file name="index.html">
22407 angular.module('valueExample', [])
22408 .controller('ExampleController', ['$scope', function($scope) {
22409 $scope.names = ['pizza', 'unicorns', 'robots'];
22410 $scope.my = { favorite: 'unicorns' };
22413 <form ng-controller="ExampleController">
22414 <h2>Which is your favorite?</h2>
22415 <label ng-repeat="name in names" for="{{name}}">
22417 <input type="radio"
22418 ng-model="my.favorite"
22423 <div>You chose {{my.favorite}}</div>
22426 <file name="protractor.js" type="protractor">
22427 var favorite = element(by.binding('my.favorite'));
22429 it('should initialize to model', function() {
22430 expect(favorite.getText()).toContain('unicorns');
22432 it('should bind the values to the inputs', function() {
22433 element.all(by.model('my.favorite')).get(0).click();
22434 expect(favorite.getText()).toContain('pizza');
22439 var ngValueDirective = function() {
22443 compile: function(tpl, tplAttr) {
22444 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) {
22445 return function ngValueConstantLink(scope, elm, attr) {
22446 attr.$set('value', scope.$eval(attr.ngValue));
22449 return function ngValueLink(scope, elm, attr) {
22450 scope.$watch(attr.ngValue, function valueWatchAction(value) {
22451 attr.$set('value', value);
22465 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element
22466 * with the value of a given expression, and to update the text content when the value of that
22467 * expression changes.
22469 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like
22470 * `{{ expression }}` which is similar but less verbose.
22472 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily
22473 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an
22474 * element attribute, it makes the bindings invisible to the user while the page is loading.
22476 * An alternative solution to this problem would be using the
22477 * {@link ng.directive:ngCloak ngCloak} directive.
22481 * @param {expression} ngBind {@link guide/expression Expression} to evaluate.
22484 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly.
22485 <example module="bindExample">
22486 <file name="index.html">
22488 angular.module('bindExample', [])
22489 .controller('ExampleController', ['$scope', function($scope) {
22490 $scope.name = 'Whirled';
22493 <div ng-controller="ExampleController">
22494 <label>Enter name: <input type="text" ng-model="name"></label><br>
22495 Hello <span ng-bind="name"></span>!
22498 <file name="protractor.js" type="protractor">
22499 it('should check ng-bind', function() {
22500 var nameInput = element(by.model('name'));
22502 expect(element(by.binding('name')).getText()).toBe('Whirled');
22504 nameInput.sendKeys('world');
22505 expect(element(by.binding('name')).getText()).toBe('world');
22510 var ngBindDirective = ['$compile', function($compile) {
22513 compile: function ngBindCompile(templateElement) {
22514 $compile.$$addBindingClass(templateElement);
22515 return function ngBindLink(scope, element, attr) {
22516 $compile.$$addBindingInfo(element, attr.ngBind);
22517 element = element[0];
22518 scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
22519 element.textContent = isUndefined(value) ? '' : value;
22529 * @name ngBindTemplate
22532 * The `ngBindTemplate` directive specifies that the element
22533 * text content should be replaced with the interpolation of the template
22534 * in the `ngBindTemplate` attribute.
22535 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}`
22536 * expressions. This directive is needed since some HTML elements
22537 * (such as TITLE and OPTION) cannot contain SPAN elements.
22540 * @param {string} ngBindTemplate template of form
22541 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval.
22544 * Try it here: enter text in text box and watch the greeting change.
22545 <example module="bindExample">
22546 <file name="index.html">
22548 angular.module('bindExample', [])
22549 .controller('ExampleController', ['$scope', function($scope) {
22550 $scope.salutation = 'Hello';
22551 $scope.name = 'World';
22554 <div ng-controller="ExampleController">
22555 <label>Salutation: <input type="text" ng-model="salutation"></label><br>
22556 <label>Name: <input type="text" ng-model="name"></label><br>
22557 <pre ng-bind-template="{{salutation}} {{name}}!"></pre>
22560 <file name="protractor.js" type="protractor">
22561 it('should check ng-bind', function() {
22562 var salutationElem = element(by.binding('salutation'));
22563 var salutationInput = element(by.model('salutation'));
22564 var nameInput = element(by.model('name'));
22566 expect(salutationElem.getText()).toBe('Hello World!');
22568 salutationInput.clear();
22569 salutationInput.sendKeys('Greetings');
22571 nameInput.sendKeys('user');
22573 expect(salutationElem.getText()).toBe('Greetings user!');
22578 var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) {
22580 compile: function ngBindTemplateCompile(templateElement) {
22581 $compile.$$addBindingClass(templateElement);
22582 return function ngBindTemplateLink(scope, element, attr) {
22583 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
22584 $compile.$$addBindingInfo(element, interpolateFn.expressions);
22585 element = element[0];
22586 attr.$observe('ngBindTemplate', function(value) {
22587 element.textContent = isUndefined(value) ? '' : value;
22600 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default,
22601 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service.
22602 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link
22603 * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize}
22604 * in your module's dependencies, you need to include "angular-sanitize.js" in your application.
22606 * You may also bypass sanitization for values you know are safe. To do so, bind to
22607 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
22608 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}.
22610 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you
22611 * will have an exception (instead of an exploit.)
22614 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
22618 <example module="bindHtmlExample" deps="angular-sanitize.js">
22619 <file name="index.html">
22620 <div ng-controller="ExampleController">
22621 <p ng-bind-html="myHTML"></p>
22625 <file name="script.js">
22626 angular.module('bindHtmlExample', ['ngSanitize'])
22627 .controller('ExampleController', ['$scope', function($scope) {
22629 'I am an <code>HTML</code>string with ' +
22630 '<a href="#">links!</a> and other <em>stuff</em>';
22634 <file name="protractor.js" type="protractor">
22635 it('should check ng-bind-html', function() {
22636 expect(element(by.binding('myHTML')).getText()).toBe(
22637 'I am an HTMLstring with links! and other stuff');
22642 var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) {
22645 compile: function ngBindHtmlCompile(tElement, tAttrs) {
22646 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml);
22647 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) {
22648 return (value || '').toString();
22650 $compile.$$addBindingClass(tElement);
22652 return function ngBindHtmlLink(scope, element, attr) {
22653 $compile.$$addBindingInfo(element, attr.ngBindHtml);
22655 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() {
22656 // we re-evaluate the expr because we want a TrustedValueHolderType
22657 // for $sce, not a string
22658 element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || '');
22670 * Evaluate the given expression when the user changes the input.
22671 * The expression is evaluated immediately, unlike the JavaScript onchange event
22672 * which only triggers at the end of a change (usually, when the user leaves the
22673 * form element or presses the return key).
22675 * The `ngChange` expression is only evaluated when a change in the input value causes
22676 * a new value to be committed to the model.
22678 * It will not be evaluated:
22679 * * if the value returned from the `$parsers` transformation pipeline has not changed
22680 * * if the input has continued to be invalid since the model will stay `null`
22681 * * if the model is changed programmatically and not by a change to the input value
22684 * Note, this directive requires `ngModel` to be present.
22687 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change
22691 * <example name="ngChange-directive" module="changeExample">
22692 * <file name="index.html">
22694 * angular.module('changeExample', [])
22695 * .controller('ExampleController', ['$scope', function($scope) {
22696 * $scope.counter = 0;
22697 * $scope.change = function() {
22698 * $scope.counter++;
22702 * <div ng-controller="ExampleController">
22703 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
22704 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
22705 * <label for="ng-change-example2">Confirmed</label><br />
22706 * <tt>debug = {{confirmed}}</tt><br/>
22707 * <tt>counter = {{counter}}</tt><br/>
22710 * <file name="protractor.js" type="protractor">
22711 * var counter = element(by.binding('counter'));
22712 * var debug = element(by.binding('confirmed'));
22714 * it('should evaluate the expression if changing from view', function() {
22715 * expect(counter.getText()).toContain('0');
22717 * element(by.id('ng-change-example1')).click();
22719 * expect(counter.getText()).toContain('1');
22720 * expect(debug.getText()).toContain('true');
22723 * it('should not evaluate the expression if changing from model', function() {
22724 * element(by.id('ng-change-example2')).click();
22726 * expect(counter.getText()).toContain('0');
22727 * expect(debug.getText()).toContain('true');
22732 var ngChangeDirective = valueFn({
22734 require: 'ngModel',
22735 link: function(scope, element, attr, ctrl) {
22736 ctrl.$viewChangeListeners.push(function() {
22737 scope.$eval(attr.ngChange);
22742 function classDirective(name, selector) {
22743 name = 'ngClass' + name;
22744 return ['$animate', function($animate) {
22747 link: function(scope, element, attr) {
22750 scope.$watch(attr[name], ngClassWatchAction, true);
22752 attr.$observe('class', function(value) {
22753 ngClassWatchAction(scope.$eval(attr[name]));
22757 if (name !== 'ngClass') {
22758 scope.$watch('$index', function($index, old$index) {
22759 // jshint bitwise: false
22760 var mod = $index & 1;
22761 if (mod !== (old$index & 1)) {
22762 var classes = arrayClasses(scope.$eval(attr[name]));
22764 addClasses(classes) :
22765 removeClasses(classes);
22770 function addClasses(classes) {
22771 var newClasses = digestClassCounts(classes, 1);
22772 attr.$addClass(newClasses);
22775 function removeClasses(classes) {
22776 var newClasses = digestClassCounts(classes, -1);
22777 attr.$removeClass(newClasses);
22780 function digestClassCounts(classes, count) {
22781 // Use createMap() to prevent class assumptions involving property
22782 // names in Object.prototype
22783 var classCounts = element.data('$classCounts') || createMap();
22784 var classesToUpdate = [];
22785 forEach(classes, function(className) {
22786 if (count > 0 || classCounts[className]) {
22787 classCounts[className] = (classCounts[className] || 0) + count;
22788 if (classCounts[className] === +(count > 0)) {
22789 classesToUpdate.push(className);
22793 element.data('$classCounts', classCounts);
22794 return classesToUpdate.join(' ');
22797 function updateClasses(oldClasses, newClasses) {
22798 var toAdd = arrayDifference(newClasses, oldClasses);
22799 var toRemove = arrayDifference(oldClasses, newClasses);
22800 toAdd = digestClassCounts(toAdd, 1);
22801 toRemove = digestClassCounts(toRemove, -1);
22802 if (toAdd && toAdd.length) {
22803 $animate.addClass(element, toAdd);
22805 if (toRemove && toRemove.length) {
22806 $animate.removeClass(element, toRemove);
22810 function ngClassWatchAction(newVal) {
22811 if (selector === true || scope.$index % 2 === selector) {
22812 var newClasses = arrayClasses(newVal || []);
22814 addClasses(newClasses);
22815 } else if (!equals(newVal,oldVal)) {
22816 var oldClasses = arrayClasses(oldVal);
22817 updateClasses(oldClasses, newClasses);
22820 oldVal = shallowCopy(newVal);
22825 function arrayDifference(tokens1, tokens2) {
22829 for (var i = 0; i < tokens1.length; i++) {
22830 var token = tokens1[i];
22831 for (var j = 0; j < tokens2.length; j++) {
22832 if (token == tokens2[j]) continue outer;
22834 values.push(token);
22839 function arrayClasses(classVal) {
22841 if (isArray(classVal)) {
22842 forEach(classVal, function(v) {
22843 classes = classes.concat(arrayClasses(v));
22846 } else if (isString(classVal)) {
22847 return classVal.split(' ');
22848 } else if (isObject(classVal)) {
22849 forEach(classVal, function(v, k) {
22851 classes = classes.concat(k.split(' '));
22867 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding
22868 * an expression that represents all classes to be added.
22870 * The directive operates in three different ways, depending on which of three types the expression
22873 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class
22876 * 2. If the expression evaluates to an object, then for each key-value pair of the
22877 * object with a truthy value the corresponding key is used as a class name.
22879 * 3. If the expression evaluates to an array, each element of the array should either be a string as in
22880 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array
22881 * to give you more control over what CSS classes appear. See the code below for an example of this.
22884 * The directive won't add duplicate classes if a particular class was already set.
22886 * When the expression changes, the previously added classes are removed and only then are the
22887 * new classes added.
22890 * **add** - happens just before the class is applied to the elements
22892 * **remove** - happens just before the class is removed from the element
22895 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result
22896 * of the evaluation can be a string representing space delimited class
22897 * names, an array, or a map of class names to boolean values. In the case of a map, the
22898 * names of the properties whose values are truthy will be added as css classes to the
22901 * @example Example that demonstrates basic bindings via ngClass directive.
22903 <file name="index.html">
22904 <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p>
22906 <input type="checkbox" ng-model="deleted">
22907 deleted (apply "strike" class)
22910 <input type="checkbox" ng-model="important">
22911 important (apply "bold" class)
22914 <input type="checkbox" ng-model="error">
22915 error (apply "has-error" class)
22918 <p ng-class="style">Using String Syntax</p>
22919 <input type="text" ng-model="style"
22920 placeholder="Type: bold strike red" aria-label="Type: bold strike red">
22922 <p ng-class="[style1, style2, style3]">Using Array Syntax</p>
22923 <input ng-model="style1"
22924 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br>
22925 <input ng-model="style2"
22926 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br>
22927 <input ng-model="style3"
22928 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br>
22930 <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p>
22931 <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br>
22932 <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label>
22934 <file name="style.css">
22936 text-decoration: line-through;
22946 background-color: yellow;
22952 <file name="protractor.js" type="protractor">
22953 var ps = element.all(by.css('p'));
22955 it('should let you toggle the class', function() {
22957 expect(ps.first().getAttribute('class')).not.toMatch(/bold/);
22958 expect(ps.first().getAttribute('class')).not.toMatch(/has-error/);
22960 element(by.model('important')).click();
22961 expect(ps.first().getAttribute('class')).toMatch(/bold/);
22963 element(by.model('error')).click();
22964 expect(ps.first().getAttribute('class')).toMatch(/has-error/);
22967 it('should let you toggle string example', function() {
22968 expect(ps.get(1).getAttribute('class')).toBe('');
22969 element(by.model('style')).clear();
22970 element(by.model('style')).sendKeys('red');
22971 expect(ps.get(1).getAttribute('class')).toBe('red');
22974 it('array example should have 3 classes', function() {
22975 expect(ps.get(2).getAttribute('class')).toBe('');
22976 element(by.model('style1')).sendKeys('bold');
22977 element(by.model('style2')).sendKeys('strike');
22978 element(by.model('style3')).sendKeys('red');
22979 expect(ps.get(2).getAttribute('class')).toBe('bold strike red');
22982 it('array with map example should have 2 classes', function() {
22983 expect(ps.last().getAttribute('class')).toBe('');
22984 element(by.model('style4')).sendKeys('bold');
22985 element(by.model('warning')).click();
22986 expect(ps.last().getAttribute('class')).toBe('bold orange');
22993 The example below demonstrates how to perform animations using ngClass.
22995 <example module="ngAnimate" deps="angular-animate.js" animations="true">
22996 <file name="index.html">
22997 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'">
22998 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''">
23000 <span class="base-class" ng-class="myVar">Sample Text</span>
23002 <file name="style.css">
23004 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
23007 .base-class.my-class {
23012 <file name="protractor.js" type="protractor">
23013 it('should check ng-class', function() {
23014 expect(element(by.css('.base-class')).getAttribute('class')).not.
23015 toMatch(/my-class/);
23017 element(by.id('setbtn')).click();
23019 expect(element(by.css('.base-class')).getAttribute('class')).
23020 toMatch(/my-class/);
23022 element(by.id('clearbtn')).click();
23024 expect(element(by.css('.base-class')).getAttribute('class')).not.
23025 toMatch(/my-class/);
23031 ## ngClass and pre-existing CSS3 Transitions/Animations
23032 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure.
23033 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder
23034 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure
23035 to view the step by step details of {@link $animate#addClass $animate.addClass} and
23036 {@link $animate#removeClass $animate.removeClass}.
23038 var ngClassDirective = classDirective('', true);
23046 * The `ngClassOdd` and `ngClassEven` directives work exactly as
23047 * {@link ng.directive:ngClass ngClass}, except they work in
23048 * conjunction with `ngRepeat` and take effect only on odd (even) rows.
23050 * This directive can be applied only within the scope of an
23051 * {@link ng.directive:ngRepeat ngRepeat}.
23054 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result
23055 * of the evaluation can be a string representing space delimited class names or an array.
23059 <file name="index.html">
23060 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
23061 <li ng-repeat="name in names">
23062 <span ng-class-odd="'odd'" ng-class-even="'even'">
23068 <file name="style.css">
23076 <file name="protractor.js" type="protractor">
23077 it('should check ng-class-odd and ng-class-even', function() {
23078 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
23080 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
23086 var ngClassOddDirective = classDirective('Odd', 0);
23090 * @name ngClassEven
23094 * The `ngClassOdd` and `ngClassEven` directives work exactly as
23095 * {@link ng.directive:ngClass ngClass}, except they work in
23096 * conjunction with `ngRepeat` and take effect only on odd (even) rows.
23098 * This directive can be applied only within the scope of an
23099 * {@link ng.directive:ngRepeat ngRepeat}.
23102 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The
23103 * result of the evaluation can be a string representing space delimited class names or an array.
23107 <file name="index.html">
23108 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
23109 <li ng-repeat="name in names">
23110 <span ng-class-odd="'odd'" ng-class-even="'even'">
23111 {{name}}
23116 <file name="style.css">
23124 <file name="protractor.js" type="protractor">
23125 it('should check ng-class-odd and ng-class-even', function() {
23126 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
23128 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
23134 var ngClassEvenDirective = classDirective('Even', 1);
23142 * The `ngCloak` directive is used to prevent the Angular html template from being briefly
23143 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this
23144 * directive to avoid the undesirable flicker effect caused by the html template display.
23146 * The directive can be applied to the `<body>` element, but the preferred usage is to apply
23147 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering
23148 * of the browser view.
23150 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and
23151 * `angular.min.js`.
23152 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
23155 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
23156 * display: none !important;
23160 * When this css rule is loaded by the browser, all html elements (including their children) that
23161 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive
23162 * during the compilation of the template it deletes the `ngCloak` element attribute, making
23163 * the compiled element visible.
23165 * For the best result, the `angular.js` script must be loaded in the head section of the html
23166 * document; alternatively, the css rule above must be included in the external stylesheet of the
23173 <file name="index.html">
23174 <div id="template1" ng-cloak>{{ 'hello' }}</div>
23175 <div id="template2" class="ng-cloak">{{ 'world' }}</div>
23177 <file name="protractor.js" type="protractor">
23178 it('should remove the template directive and css class', function() {
23179 expect($('#template1').getAttribute('ng-cloak')).
23181 expect($('#template2').getAttribute('ng-cloak')).
23188 var ngCloakDirective = ngDirective({
23189 compile: function(element, attr) {
23190 attr.$set('ngCloak', undefined);
23191 element.removeClass('ng-cloak');
23197 * @name ngController
23200 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular
23201 * supports the principles behind the Model-View-Controller design pattern.
23203 * MVC components in angular:
23205 * * Model — Models are the properties of a scope; scopes are attached to the DOM where scope properties
23206 * are accessed through bindings.
23207 * * View — The template (HTML with data bindings) that is rendered into the View.
23208 * * Controller — The `ngController` directive specifies a Controller class; the class contains business
23209 * logic behind the application to decorate the scope with functions and values
23211 * Note that you can also attach controllers to the DOM by declaring it in a route definition
23212 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller
23213 * again using `ng-controller` in the template itself. This will cause the controller to be attached
23214 * and executed twice.
23219 * @param {expression} ngController Name of a constructor function registered with the current
23220 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression}
23221 * that on the current scope evaluates to a constructor function.
23223 * The controller instance can be published into a scope property by specifying
23224 * `ng-controller="as propertyName"`.
23226 * If the current `$controllerProvider` is configured to use globals (via
23227 * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may
23228 * also be the name of a globally accessible constructor function (not recommended).
23231 * Here is a simple form for editing user contact information. Adding, removing, clearing, and
23232 * greeting are methods declared on the controller (see source tab). These methods can
23233 * easily be called from the angular markup. Any changes to the data are automatically reflected
23234 * in the View without the need for a manual update.
23236 * Two different declaration styles are included below:
23238 * * one binds methods and properties directly onto the controller using `this`:
23239 * `ng-controller="SettingsController1 as settings"`
23240 * * one injects `$scope` into the controller:
23241 * `ng-controller="SettingsController2"`
23243 * The second option is more common in the Angular community, and is generally used in boilerplates
23244 * and in this guide. However, there are advantages to binding properties directly to the controller
23245 * and avoiding scope.
23247 * * Using `controller as` makes it obvious which controller you are accessing in the template when
23248 * multiple controllers apply to an element.
23249 * * If you are writing your controllers as classes you have easier access to the properties and
23250 * methods, which will appear on the scope, from inside the controller code.
23251 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal
23252 * inheritance masking primitives.
23254 * This example demonstrates the `controller as` syntax.
23256 * <example name="ngControllerAs" module="controllerAsExample">
23257 * <file name="index.html">
23258 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings">
23259 * <label>Name: <input type="text" ng-model="settings.name"/></label>
23260 * <button ng-click="settings.greet()">greet</button><br/>
23263 * <li ng-repeat="contact in settings.contacts">
23264 * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}">
23265 * <option>phone</option>
23266 * <option>email</option>
23268 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
23269 * <button ng-click="settings.clearContact(contact)">clear</button>
23270 * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button>
23272 * <li><button ng-click="settings.addContact()">add</button></li>
23276 * <file name="app.js">
23277 * angular.module('controllerAsExample', [])
23278 * .controller('SettingsController1', SettingsController1);
23280 * function SettingsController1() {
23281 * this.name = "John Smith";
23282 * this.contacts = [
23283 * {type: 'phone', value: '408 555 1212'},
23284 * {type: 'email', value: 'john.smith@example.org'} ];
23287 * SettingsController1.prototype.greet = function() {
23288 * alert(this.name);
23291 * SettingsController1.prototype.addContact = function() {
23292 * this.contacts.push({type: 'email', value: 'yourname@example.org'});
23295 * SettingsController1.prototype.removeContact = function(contactToRemove) {
23296 * var index = this.contacts.indexOf(contactToRemove);
23297 * this.contacts.splice(index, 1);
23300 * SettingsController1.prototype.clearContact = function(contact) {
23301 * contact.type = 'phone';
23302 * contact.value = '';
23305 * <file name="protractor.js" type="protractor">
23306 * it('should check controller as', function() {
23307 * var container = element(by.id('ctrl-as-exmpl'));
23308 * expect(container.element(by.model('settings.name'))
23309 * .getAttribute('value')).toBe('John Smith');
23311 * var firstRepeat =
23312 * container.element(by.repeater('contact in settings.contacts').row(0));
23313 * var secondRepeat =
23314 * container.element(by.repeater('contact in settings.contacts').row(1));
23316 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23317 * .toBe('408 555 1212');
23319 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
23320 * .toBe('john.smith@example.org');
23322 * firstRepeat.element(by.buttonText('clear')).click();
23324 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23327 * container.element(by.buttonText('add')).click();
23329 * expect(container.element(by.repeater('contact in settings.contacts').row(2))
23330 * .element(by.model('contact.value'))
23331 * .getAttribute('value'))
23332 * .toBe('yourname@example.org');
23337 * This example demonstrates the "attach to `$scope`" style of controller.
23339 * <example name="ngController" module="controllerExample">
23340 * <file name="index.html">
23341 * <div id="ctrl-exmpl" ng-controller="SettingsController2">
23342 * <label>Name: <input type="text" ng-model="name"/></label>
23343 * <button ng-click="greet()">greet</button><br/>
23346 * <li ng-repeat="contact in contacts">
23347 * <select ng-model="contact.type" id="select_{{$index}}">
23348 * <option>phone</option>
23349 * <option>email</option>
23351 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
23352 * <button ng-click="clearContact(contact)">clear</button>
23353 * <button ng-click="removeContact(contact)">X</button>
23355 * <li>[ <button ng-click="addContact()">add</button> ]</li>
23359 * <file name="app.js">
23360 * angular.module('controllerExample', [])
23361 * .controller('SettingsController2', ['$scope', SettingsController2]);
23363 * function SettingsController2($scope) {
23364 * $scope.name = "John Smith";
23365 * $scope.contacts = [
23366 * {type:'phone', value:'408 555 1212'},
23367 * {type:'email', value:'john.smith@example.org'} ];
23369 * $scope.greet = function() {
23370 * alert($scope.name);
23373 * $scope.addContact = function() {
23374 * $scope.contacts.push({type:'email', value:'yourname@example.org'});
23377 * $scope.removeContact = function(contactToRemove) {
23378 * var index = $scope.contacts.indexOf(contactToRemove);
23379 * $scope.contacts.splice(index, 1);
23382 * $scope.clearContact = function(contact) {
23383 * contact.type = 'phone';
23384 * contact.value = '';
23388 * <file name="protractor.js" type="protractor">
23389 * it('should check controller', function() {
23390 * var container = element(by.id('ctrl-exmpl'));
23392 * expect(container.element(by.model('name'))
23393 * .getAttribute('value')).toBe('John Smith');
23395 * var firstRepeat =
23396 * container.element(by.repeater('contact in contacts').row(0));
23397 * var secondRepeat =
23398 * container.element(by.repeater('contact in contacts').row(1));
23400 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23401 * .toBe('408 555 1212');
23402 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
23403 * .toBe('john.smith@example.org');
23405 * firstRepeat.element(by.buttonText('clear')).click();
23407 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
23410 * container.element(by.buttonText('add')).click();
23412 * expect(container.element(by.repeater('contact in contacts').row(2))
23413 * .element(by.model('contact.value'))
23414 * .getAttribute('value'))
23415 * .toBe('yourname@example.org');
23421 var ngControllerDirective = [function() {
23437 * Angular has some features that can break certain
23438 * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules.
23440 * If you intend to implement these rules then you must tell Angular not to use these features.
23442 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps.
23445 * The following rules affect Angular:
23447 * * `unsafe-eval`: this rule forbids apps to use `eval` or `Function(string)` generated functions
23448 * (among other things). Angular makes use of this in the {@link $parse} service to provide a 30%
23449 * increase in the speed of evaluating Angular expressions.
23451 * * `unsafe-inline`: this rule forbids apps from inject custom styles into the document. Angular
23452 * makes use of this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}).
23453 * To make these directives work when a CSP rule is blocking inline styles, you must link to the
23454 * `angular-csp.css` in your HTML manually.
23456 * If you do not provide `ngCsp` then Angular tries to autodetect if CSP is blocking unsafe-eval
23457 * and automatically deactivates this feature in the {@link $parse} service. This autodetection,
23458 * however, triggers a CSP error to be logged in the console:
23461 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of
23462 * script in the following Content Security Policy directive: "default-src 'self'". Note that
23463 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
23466 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp`
23467 * directive on an element of the HTML document that appears before the `<script>` tag that loads
23468 * the `angular.js` file.
23470 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.*
23472 * You can specify which of the CSP related Angular features should be deactivated by providing
23473 * a value for the `ng-csp` attribute. The options are as follows:
23475 * * no-inline-style: this stops Angular from injecting CSS styles into the DOM
23477 * * no-unsafe-eval: this stops Angular from optimising $parse with unsafe eval of strings
23479 * You can use these values in the following combinations:
23482 * * No declaration means that Angular will assume that you can do inline styles, but it will do
23483 * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous versions
23486 * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell Angular to deactivate both inline
23487 * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous versions
23490 * * Specifying only `no-unsafe-eval` tells Angular that we must not use eval, but that we can inject
23491 * inline styles. E.g. `<body ng-csp="no-unsafe-eval">`.
23493 * * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can
23494 * run eval - no automcatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">`
23496 * * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject
23497 * styles nor use eval, which is the same as an empty: ng-csp.
23498 * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">`
23501 * This example shows how to apply the `ngCsp` directive to the `html` tag.
23504 <html ng-app ng-csp>
23510 // Note: the suffix `.csp` in the example name triggers
23511 // csp mode in our http server!
23512 <example name="example.csp" module="cspExample" ng-csp="true">
23513 <file name="index.html">
23514 <div ng-controller="MainController as ctrl">
23516 <button ng-click="ctrl.inc()" id="inc">Increment</button>
23517 <span id="counter">
23523 <button ng-click="ctrl.evil()" id="evil">Evil</button>
23524 <span id="evilError">
23530 <file name="script.js">
23531 angular.module('cspExample', [])
23532 .controller('MainController', function() {
23534 this.inc = function() {
23537 this.evil = function() {
23538 // jshint evil:true
23542 this.evilError = e.message;
23547 <file name="protractor.js" type="protractor">
23548 var util, webdriver;
23550 var incBtn = element(by.id('inc'));
23551 var counter = element(by.id('counter'));
23552 var evilBtn = element(by.id('evil'));
23553 var evilError = element(by.id('evilError'));
23555 function getAndClearSevereErrors() {
23556 return browser.manage().logs().get('browser').then(function(browserLog) {
23557 return browserLog.filter(function(logEntry) {
23558 return logEntry.level.value > webdriver.logging.Level.WARNING.value;
23563 function clearErrors() {
23564 getAndClearSevereErrors();
23567 function expectNoErrors() {
23568 getAndClearSevereErrors().then(function(filteredLog) {
23569 expect(filteredLog.length).toEqual(0);
23570 if (filteredLog.length) {
23571 console.log('browser console errors: ' + util.inspect(filteredLog));
23576 function expectError(regex) {
23577 getAndClearSevereErrors().then(function(filteredLog) {
23579 filteredLog.forEach(function(log) {
23580 if (log.message.match(regex)) {
23585 throw new Error('expected an error that matches ' + regex);
23590 beforeEach(function() {
23591 util = require('util');
23592 webdriver = require('protractor/node_modules/selenium-webdriver');
23595 // For now, we only test on Chrome,
23596 // as Safari does not load the page with Protractor's injected scripts,
23597 // and Firefox webdriver always disables content security policy (#6358)
23598 if (browser.params.browser !== 'chrome') {
23602 it('should not report errors when the page is loaded', function() {
23603 // clear errors so we are not dependent on previous tests
23605 // Need to reload the page as the page is already loaded when
23607 browser.driver.getCurrentUrl().then(function(url) {
23613 it('should evaluate expressions', function() {
23614 expect(counter.getText()).toEqual('0');
23616 expect(counter.getText()).toEqual('1');
23620 it('should throw and report an error when using "eval"', function() {
23622 expect(evilError.getText()).toMatch(/Content Security Policy/);
23623 expectError(/Content Security Policy/);
23629 // ngCsp is not implemented as a proper directive any more, because we need it be processed while we
23630 // bootstrap the system (before $parse is instantiated), for this reason we just have
23631 // the csp() fn that looks for the `ng-csp` attribute anywhere in the current doc
23638 * The ngClick directive allows you to specify custom behavior when
23639 * an element is clicked.
23643 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
23644 * click. ({@link guide/expression#-event- Event object is available as `$event`})
23648 <file name="index.html">
23649 <button ng-click="count = count + 1" ng-init="count=0">
23656 <file name="protractor.js" type="protractor">
23657 it('should check ng-click', function() {
23658 expect(element(by.binding('count')).getText()).toMatch('0');
23659 element(by.css('button')).click();
23660 expect(element(by.binding('count')).getText()).toMatch('1');
23666 * A collection of directives that allows creation of custom event handlers that are defined as
23667 * angular expressions and are compiled and executed within the current scope.
23669 var ngEventDirectives = {};
23671 // For events that might fire synchronously during DOM manipulation
23672 // we need to execute their event handlers asynchronously using $evalAsync,
23673 // so that they are not executed in an inconsistent state.
23674 var forceAsyncEvents = {
23679 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
23680 function(eventName) {
23681 var directiveName = directiveNormalize('ng-' + eventName);
23682 ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) {
23685 compile: function($element, attr) {
23686 // We expose the powerful $event object on the scope that provides access to the Window,
23687 // etc. that isn't protected by the fast paths in $parse. We explicitly request better
23688 // checks at the cost of speed since event handler expressions are not executed as
23689 // frequently as regular change detection.
23690 var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true);
23691 return function ngEventHandler(scope, element) {
23692 element.on(eventName, function(event) {
23693 var callback = function() {
23694 fn(scope, {$event:event});
23696 if (forceAsyncEvents[eventName] && $rootScope.$$phase) {
23697 scope.$evalAsync(callback);
23699 scope.$apply(callback);
23714 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event.
23718 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon
23719 * a dblclick. (The Event object is available as `$event`)
23723 <file name="index.html">
23724 <button ng-dblclick="count = count + 1" ng-init="count=0">
23725 Increment (on double click)
23735 * @name ngMousedown
23738 * The ngMousedown directive allows you to specify custom behavior on mousedown event.
23742 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon
23743 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`})
23747 <file name="index.html">
23748 <button ng-mousedown="count = count + 1" ng-init="count=0">
23749 Increment (on mouse down)
23762 * Specify custom behavior on mouseup event.
23766 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon
23767 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`})
23771 <file name="index.html">
23772 <button ng-mouseup="count = count + 1" ng-init="count=0">
23773 Increment (on mouse up)
23782 * @name ngMouseover
23785 * Specify custom behavior on mouseover event.
23789 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon
23790 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`})
23794 <file name="index.html">
23795 <button ng-mouseover="count = count + 1" ng-init="count=0">
23796 Increment (when mouse is over)
23806 * @name ngMouseenter
23809 * Specify custom behavior on mouseenter event.
23813 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon
23814 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`})
23818 <file name="index.html">
23819 <button ng-mouseenter="count = count + 1" ng-init="count=0">
23820 Increment (when mouse enters)
23830 * @name ngMouseleave
23833 * Specify custom behavior on mouseleave event.
23837 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon
23838 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`})
23842 <file name="index.html">
23843 <button ng-mouseleave="count = count + 1" ng-init="count=0">
23844 Increment (when mouse leaves)
23854 * @name ngMousemove
23857 * Specify custom behavior on mousemove event.
23861 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon
23862 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`})
23866 <file name="index.html">
23867 <button ng-mousemove="count = count + 1" ng-init="count=0">
23868 Increment (when mouse moves)
23881 * Specify custom behavior on keydown event.
23885 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon
23886 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
23890 <file name="index.html">
23891 <input ng-keydown="count = count + 1" ng-init="count=0">
23892 key down count: {{count}}
23903 * Specify custom behavior on keyup event.
23907 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon
23908 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
23912 <file name="index.html">
23913 <p>Typing in the input box below updates the key count</p>
23914 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}}
23916 <p>Typing in the input box below updates the keycode</p>
23917 <input ng-keyup="event=$event">
23918 <p>event keyCode: {{ event.keyCode }}</p>
23919 <p>event altKey: {{ event.altKey }}</p>
23930 * Specify custom behavior on keypress event.
23933 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon
23934 * keypress. ({@link guide/expression#-event- Event object is available as `$event`}
23935 * and can be interrogated for keyCode, altKey, etc.)
23939 <file name="index.html">
23940 <input ng-keypress="count = count + 1" ng-init="count=0">
23941 key press count: {{count}}
23952 * Enables binding angular expressions to onsubmit events.
23954 * Additionally it prevents the default action (which for form means sending the request to the
23955 * server and reloading the current page), but only if the form does not contain `action`,
23956 * `data-action`, or `x-action` attributes.
23958 * <div class="alert alert-warning">
23959 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and
23960 * `ngSubmit` handlers together. See the
23961 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation}
23962 * for a detailed discussion of when `ngSubmit` may be triggered.
23967 * @param {expression} ngSubmit {@link guide/expression Expression} to eval.
23968 * ({@link guide/expression#-event- Event object is available as `$event`})
23971 <example module="submitExample">
23972 <file name="index.html">
23974 angular.module('submitExample', [])
23975 .controller('ExampleController', ['$scope', function($scope) {
23977 $scope.text = 'hello';
23978 $scope.submit = function() {
23980 $scope.list.push(this.text);
23986 <form ng-submit="submit()" ng-controller="ExampleController">
23987 Enter text and hit enter:
23988 <input type="text" ng-model="text" name="text" />
23989 <input type="submit" id="submit" value="Submit" />
23990 <pre>list={{list}}</pre>
23993 <file name="protractor.js" type="protractor">
23994 it('should check ng-submit', function() {
23995 expect(element(by.binding('list')).getText()).toBe('list=[]');
23996 element(by.css('#submit')).click();
23997 expect(element(by.binding('list')).getText()).toContain('hello');
23998 expect(element(by.model('text')).getAttribute('value')).toBe('');
24000 it('should ignore empty strings', function() {
24001 expect(element(by.binding('list')).getText()).toBe('list=[]');
24002 element(by.css('#submit')).click();
24003 element(by.css('#submit')).click();
24004 expect(element(by.binding('list')).getText()).toContain('hello');
24015 * Specify custom behavior on focus event.
24017 * Note: As the `focus` event is executed synchronously when calling `input.focus()`
24018 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired
24019 * during an `$apply` to ensure a consistent state.
24021 * @element window, input, select, textarea, a
24023 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon
24024 * focus. ({@link guide/expression#-event- Event object is available as `$event`})
24027 * See {@link ng.directive:ngClick ngClick}
24035 * Specify custom behavior on blur event.
24037 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when
24038 * an element has lost focus.
24040 * Note: As the `blur` event is executed synchronously also during DOM manipulations
24041 * (e.g. removing a focussed input),
24042 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired
24043 * during an `$apply` to ensure a consistent state.
24045 * @element window, input, select, textarea, a
24047 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon
24048 * blur. ({@link guide/expression#-event- Event object is available as `$event`})
24051 * See {@link ng.directive:ngClick ngClick}
24059 * Specify custom behavior on copy event.
24061 * @element window, input, select, textarea, a
24063 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon
24064 * copy. ({@link guide/expression#-event- Event object is available as `$event`})
24068 <file name="index.html">
24069 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value">
24080 * Specify custom behavior on cut event.
24082 * @element window, input, select, textarea, a
24084 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon
24085 * cut. ({@link guide/expression#-event- Event object is available as `$event`})
24089 <file name="index.html">
24090 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value">
24101 * Specify custom behavior on paste event.
24103 * @element window, input, select, textarea, a
24105 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon
24106 * paste. ({@link guide/expression#-event- Event object is available as `$event`})
24110 <file name="index.html">
24111 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'>
24124 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an
24125 * {expression}. If the expression assigned to `ngIf` evaluates to a false
24126 * value then the element is removed from the DOM, otherwise a clone of the
24127 * element is reinserted into the DOM.
24129 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the
24130 * element in the DOM rather than changing its visibility via the `display` css property. A common
24131 * case when this difference is significant is when using css selectors that rely on an element's
24132 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classes.
24134 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope
24135 * is created when the element is restored. The scope created within `ngIf` inherits from
24136 * its parent scope using
24137 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance).
24138 * An important implication of this is if `ngModel` is used within `ngIf` to bind to
24139 * a javascript primitive defined in the parent scope. In this case any modifications made to the
24140 * variable within the child scope will override (hide) the value in the parent scope.
24142 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior
24143 * is if an element's class attribute is directly modified after it's compiled, using something like
24144 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element
24145 * the added class will be lost because the original compiled state is used to regenerate the element.
24147 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter`
24148 * and `leave` effects.
24151 * enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container
24152 * leave - happens just before the `ngIf` contents are removed from the DOM
24157 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then
24158 * the element is removed from the DOM tree. If it is truthy a copy of the compiled
24159 * element is added to the DOM tree.
24162 <example module="ngAnimate" deps="angular-animate.js" animations="true">
24163 <file name="index.html">
24164 <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/>
24166 <span ng-if="checked" class="animate-if">
24167 This is removed when the checkbox is unchecked.
24170 <file name="animations.css">
24173 border:1px solid black;
24177 .animate-if.ng-enter, .animate-if.ng-leave {
24178 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
24181 .animate-if.ng-enter,
24182 .animate-if.ng-leave.ng-leave-active {
24186 .animate-if.ng-leave,
24187 .animate-if.ng-enter.ng-enter-active {
24193 var ngIfDirective = ['$animate', function($animate) {
24195 multiElement: true,
24196 transclude: 'element',
24201 link: function($scope, $element, $attr, ctrl, $transclude) {
24202 var block, childScope, previousElements;
24203 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) {
24207 $transclude(function(clone, newScope) {
24208 childScope = newScope;
24209 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ');
24210 // Note: We only need the first/last node of the cloned nodes.
24211 // However, we need to keep the reference to the jqlite wrapper as it might be changed later
24212 // by a directive with templateUrl when its template arrives.
24216 $animate.enter(clone, $element.parent(), $element);
24220 if (previousElements) {
24221 previousElements.remove();
24222 previousElements = null;
24225 childScope.$destroy();
24229 previousElements = getBlockNodes(block.clone);
24230 $animate.leave(previousElements).then(function() {
24231 previousElements = null;
24247 * Fetches, compiles and includes an external HTML fragment.
24249 * By default, the template URL is restricted to the same domain and protocol as the
24250 * application document. This is done by calling {@link $sce#getTrustedResourceUrl
24251 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols
24252 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or
24253 * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link
24254 * ng.$sce Strict Contextual Escaping}.
24256 * In addition, the browser's
24257 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
24258 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
24259 * policy may further restrict whether the template is successfully loaded.
24260 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://`
24261 * access on some browsers.
24264 * enter - animation is used to bring new content into the browser.
24265 * leave - animation is used to animate existing content away.
24267 * The enter and leave animation occur concurrently.
24272 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
24273 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`.
24274 * @param {string=} onload Expression to evaluate when a new partial is loaded.
24275 * <div class="alert alert-warning">
24276 * **Note:** When using onload on SVG elements in IE11, the browser will try to call
24277 * a function with the name on the window element, which will usually throw a
24278 * "function is undefined" error. To fix this, you can instead use `data-onload` or a
24279 * different form that {@link guide/directive#normalization matches} `onload`.
24282 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll
24283 * $anchorScroll} to scroll the viewport after the content is loaded.
24285 * - If the attribute is not set, disable scrolling.
24286 * - If the attribute is set without value, enable scrolling.
24287 * - Otherwise enable scrolling only if the expression evaluates to truthy value.
24290 <example module="includeExample" deps="angular-animate.js" animations="true">
24291 <file name="index.html">
24292 <div ng-controller="ExampleController">
24293 <select ng-model="template" ng-options="t.name for t in templates">
24294 <option value="">(blank)</option>
24296 url of the template: <code>{{template.url}}</code>
24298 <div class="slide-animate-container">
24299 <div class="slide-animate" ng-include="template.url"></div>
24303 <file name="script.js">
24304 angular.module('includeExample', ['ngAnimate'])
24305 .controller('ExampleController', ['$scope', function($scope) {
24307 [ { name: 'template1.html', url: 'template1.html'},
24308 { name: 'template2.html', url: 'template2.html'} ];
24309 $scope.template = $scope.templates[0];
24312 <file name="template1.html">
24313 Content of template1.html
24315 <file name="template2.html">
24316 Content of template2.html
24318 <file name="animations.css">
24319 .slide-animate-container {
24322 border:1px solid black;
24331 .slide-animate.ng-enter, .slide-animate.ng-leave {
24332 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
24343 .slide-animate.ng-enter {
24346 .slide-animate.ng-enter.ng-enter-active {
24350 .slide-animate.ng-leave {
24353 .slide-animate.ng-leave.ng-leave-active {
24357 <file name="protractor.js" type="protractor">
24358 var templateSelect = element(by.model('template'));
24359 var includeElem = element(by.css('[ng-include]'));
24361 it('should load template1.html', function() {
24362 expect(includeElem.getText()).toMatch(/Content of template1.html/);
24365 it('should load template2.html', function() {
24366 if (browser.params.browser == 'firefox') {
24367 // Firefox can't handle using selects
24368 // See https://github.com/angular/protractor/issues/480
24371 templateSelect.click();
24372 templateSelect.all(by.css('option')).get(2).click();
24373 expect(includeElem.getText()).toMatch(/Content of template2.html/);
24376 it('should change to blank', function() {
24377 if (browser.params.browser == 'firefox') {
24378 // Firefox can't handle using selects
24381 templateSelect.click();
24382 templateSelect.all(by.css('option')).get(0).click();
24383 expect(includeElem.isPresent()).toBe(false);
24392 * @name ngInclude#$includeContentRequested
24393 * @eventType emit on the scope ngInclude was declared in
24395 * Emitted every time the ngInclude content is requested.
24397 * @param {Object} angularEvent Synthetic event object.
24398 * @param {String} src URL of content to load.
24404 * @name ngInclude#$includeContentLoaded
24405 * @eventType emit on the current ngInclude scope
24407 * Emitted every time the ngInclude content is reloaded.
24409 * @param {Object} angularEvent Synthetic event object.
24410 * @param {String} src URL of content to load.
24416 * @name ngInclude#$includeContentError
24417 * @eventType emit on the scope ngInclude was declared in
24419 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299)
24421 * @param {Object} angularEvent Synthetic event object.
24422 * @param {String} src URL of content to load.
24424 var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate',
24425 function($templateRequest, $anchorScroll, $animate) {
24430 transclude: 'element',
24431 controller: angular.noop,
24432 compile: function(element, attr) {
24433 var srcExp = attr.ngInclude || attr.src,
24434 onloadExp = attr.onload || '',
24435 autoScrollExp = attr.autoscroll;
24437 return function(scope, $element, $attr, ctrl, $transclude) {
24438 var changeCounter = 0,
24443 var cleanupLastIncludeContent = function() {
24444 if (previousElement) {
24445 previousElement.remove();
24446 previousElement = null;
24448 if (currentScope) {
24449 currentScope.$destroy();
24450 currentScope = null;
24452 if (currentElement) {
24453 $animate.leave(currentElement).then(function() {
24454 previousElement = null;
24456 previousElement = currentElement;
24457 currentElement = null;
24461 scope.$watch(srcExp, function ngIncludeWatchAction(src) {
24462 var afterAnimation = function() {
24463 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
24467 var thisChangeId = ++changeCounter;
24470 //set the 2nd param to true to ignore the template request error so that the inner
24471 //contents and scope can be cleaned up.
24472 $templateRequest(src, true).then(function(response) {
24473 if (thisChangeId !== changeCounter) return;
24474 var newScope = scope.$new();
24475 ctrl.template = response;
24477 // Note: This will also link all children of ng-include that were contained in the original
24478 // html. If that content contains controllers, ... they could pollute/change the scope.
24479 // However, using ng-include on an element with additional content does not make sense...
24480 // Note: We can't remove them in the cloneAttchFn of $transclude as that
24481 // function is called before linking the content, which would apply child
24482 // directives to non existing elements.
24483 var clone = $transclude(newScope, function(clone) {
24484 cleanupLastIncludeContent();
24485 $animate.enter(clone, null, $element).then(afterAnimation);
24488 currentScope = newScope;
24489 currentElement = clone;
24491 currentScope.$emit('$includeContentLoaded', src);
24492 scope.$eval(onloadExp);
24494 if (thisChangeId === changeCounter) {
24495 cleanupLastIncludeContent();
24496 scope.$emit('$includeContentError', src);
24499 scope.$emit('$includeContentRequested', src);
24501 cleanupLastIncludeContent();
24502 ctrl.template = null;
24510 // This directive is called during the $transclude call of the first `ngInclude` directive.
24511 // It will replace and compile the content of the element with the loaded template.
24512 // We need this directive so that the element content is already filled when
24513 // the link function of another directive on the same element as ngInclude
24515 var ngIncludeFillContentDirective = ['$compile',
24516 function($compile) {
24520 require: 'ngInclude',
24521 link: function(scope, $element, $attr, ctrl) {
24522 if (/SVG/.test($element[0].toString())) {
24523 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not
24524 // support innerHTML, so detect this here and try to generate the contents
24527 $compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope,
24528 function namespaceAdaptedClone(clone) {
24529 $element.append(clone);
24530 }, {futureParentElement: $element});
24534 $element.html(ctrl.template);
24535 $compile($element.contents())(scope);
24546 * The `ngInit` directive allows you to evaluate an expression in the
24549 * <div class="alert alert-danger">
24550 * This directive can be abused to add unnecessary amounts of logic into your templates.
24551 * There are only a few appropriate uses of `ngInit`, such as for aliasing special properties of
24552 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below; and for injecting data via
24553 * server side scripting. Besides these few cases, you should use {@link guide/controller controllers}
24554 * rather than `ngInit` to initialize values on a scope.
24557 * <div class="alert alert-warning">
24558 * **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make
24559 * sure you have parentheses to ensure correct operator precedence:
24560 * <pre class="prettyprint">
24561 * `<div ng-init="test1 = ($index | toString)"></div>`
24568 * @param {expression} ngInit {@link guide/expression Expression} to eval.
24571 <example module="initExample">
24572 <file name="index.html">
24574 angular.module('initExample', [])
24575 .controller('ExampleController', ['$scope', function($scope) {
24576 $scope.list = [['a', 'b'], ['c', 'd']];
24579 <div ng-controller="ExampleController">
24580 <div ng-repeat="innerList in list" ng-init="outerIndex = $index">
24581 <div ng-repeat="value in innerList" ng-init="innerIndex = $index">
24582 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span>
24587 <file name="protractor.js" type="protractor">
24588 it('should alias index positions', function() {
24589 var elements = element.all(by.css('.example-init'));
24590 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;');
24591 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;');
24592 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;');
24593 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;');
24598 var ngInitDirective = ngDirective({
24600 compile: function() {
24602 pre: function(scope, element, attrs) {
24603 scope.$eval(attrs.ngInit);
24614 * Text input that converts between a delimited string and an array of strings. The default
24615 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom
24616 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`.
24618 * The behaviour of the directive is affected by the use of the `ngTrim` attribute.
24619 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each
24620 * list item is respected. This implies that the user of the directive is responsible for
24621 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a
24622 * tab or newline character.
24623 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected
24624 * when joining the list items back together) and whitespace around each list item is stripped
24625 * before it is added to the model.
24627 * ### Example with Validation
24629 * <example name="ngList-directive" module="listExample">
24630 * <file name="app.js">
24631 * angular.module('listExample', [])
24632 * .controller('ExampleController', ['$scope', function($scope) {
24633 * $scope.names = ['morpheus', 'neo', 'trinity'];
24636 * <file name="index.html">
24637 * <form name="myForm" ng-controller="ExampleController">
24638 * <label>List: <input name="namesInput" ng-model="names" ng-list required></label>
24639 * <span role="alert">
24640 * <span class="error" ng-show="myForm.namesInput.$error.required">
24644 * <tt>names = {{names}}</tt><br/>
24645 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
24646 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
24647 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
24648 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
24651 * <file name="protractor.js" type="protractor">
24652 * var listInput = element(by.model('names'));
24653 * var names = element(by.exactBinding('names'));
24654 * var valid = element(by.binding('myForm.namesInput.$valid'));
24655 * var error = element(by.css('span.error'));
24657 * it('should initialize to model', function() {
24658 * expect(names.getText()).toContain('["morpheus","neo","trinity"]');
24659 * expect(valid.getText()).toContain('true');
24660 * expect(error.getCssValue('display')).toBe('none');
24663 * it('should be invalid if empty', function() {
24664 * listInput.clear();
24665 * listInput.sendKeys('');
24667 * expect(names.getText()).toContain('');
24668 * expect(valid.getText()).toContain('false');
24669 * expect(error.getCssValue('display')).not.toBe('none');
24674 * ### Example - splitting on newline
24675 * <example name="ngList-directive-newlines">
24676 * <file name="index.html">
24677 * <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea>
24678 * <pre>{{ list | json }}</pre>
24680 * <file name="protractor.js" type="protractor">
24681 * it("should split the text by newlines", function() {
24682 * var listInput = element(by.model('list'));
24683 * var output = element(by.binding('list | json'));
24684 * listInput.sendKeys('abc\ndef\nghi');
24685 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]');
24691 * @param {string=} ngList optional delimiter that should be used to split the value.
24693 var ngListDirective = function() {
24697 require: 'ngModel',
24698 link: function(scope, element, attr, ctrl) {
24699 // We want to control whitespace trimming so we use this convoluted approach
24700 // to access the ngList attribute, which doesn't pre-trim the attribute
24701 var ngList = element.attr(attr.$attr.ngList) || ', ';
24702 var trimValues = attr.ngTrim !== 'false';
24703 var separator = trimValues ? trim(ngList) : ngList;
24705 var parse = function(viewValue) {
24706 // If the viewValue is invalid (say required but empty) it will be `undefined`
24707 if (isUndefined(viewValue)) return;
24712 forEach(viewValue.split(separator), function(value) {
24713 if (value) list.push(trimValues ? trim(value) : value);
24720 ctrl.$parsers.push(parse);
24721 ctrl.$formatters.push(function(value) {
24722 if (isArray(value)) {
24723 return value.join(ngList);
24729 // Override the standard $isEmpty because an empty array means the input is empty.
24730 ctrl.$isEmpty = function(value) {
24731 return !value || !value.length;
24737 /* global VALID_CLASS: true,
24738 INVALID_CLASS: true,
24739 PRISTINE_CLASS: true,
24741 UNTOUCHED_CLASS: true,
24742 TOUCHED_CLASS: true,
24745 var VALID_CLASS = 'ng-valid',
24746 INVALID_CLASS = 'ng-invalid',
24747 PRISTINE_CLASS = 'ng-pristine',
24748 DIRTY_CLASS = 'ng-dirty',
24749 UNTOUCHED_CLASS = 'ng-untouched',
24750 TOUCHED_CLASS = 'ng-touched',
24751 PENDING_CLASS = 'ng-pending';
24753 var ngModelMinErr = minErr('ngModel');
24757 * @name ngModel.NgModelController
24759 * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a
24760 * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue
24762 * @property {*} $modelValue The value in the model that the control is bound to.
24763 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
24764 the control reads value from the DOM. The functions are called in array order, each passing
24765 its return value through to the next. The last return value is forwarded to the
24766 {@link ngModel.NgModelController#$validators `$validators`} collection.
24768 Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue
24771 Returning `undefined` from a parser means a parse error occurred. In that case,
24772 no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel`
24773 will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`}
24774 is set to `true`. The parse error is stored in `ngModel.$error.parse`.
24777 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
24778 the model value changes. The functions are called in reverse array order, each passing the value through to the
24779 next. The last return value is used as the actual DOM value.
24780 Used to format / convert values for display in the control.
24782 * function formatter(value) {
24784 * return value.toUpperCase();
24787 * ngModel.$formatters.push(formatter);
24790 * @property {Object.<string, function>} $validators A collection of validators that are applied
24791 * whenever the model value changes. The key value within the object refers to the name of the
24792 * validator while the function refers to the validation operation. The validation operation is
24793 * provided with the model value as an argument and must return a true or false value depending
24794 * on the response of that validation.
24797 * ngModel.$validators.validCharacters = function(modelValue, viewValue) {
24798 * var value = modelValue || viewValue;
24799 * return /[0-9]+/.test(value) &&
24800 * /[a-z]+/.test(value) &&
24801 * /[A-Z]+/.test(value) &&
24802 * /\W+/.test(value);
24806 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to
24807 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided
24808 * is expected to return a promise when it is run during the model validation process. Once the promise
24809 * is delivered then the validation status will be set to true when fulfilled and false when rejected.
24810 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model
24811 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator
24812 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators
24813 * will only run once all synchronous validators have passed.
24815 * Please note that if $http is used then it is important that the server returns a success HTTP response code
24816 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation.
24819 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
24820 * var value = modelValue || viewValue;
24822 * // Lookup user by username
24823 * return $http.get('/api/users/' + value).
24824 * then(function resolved() {
24825 * //username exists, this means validation fails
24826 * return $q.reject('exists');
24827 * }, function rejected() {
24828 * //username does not exist, therefore this validation passes
24834 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the
24835 * view value has changed. It is called with no arguments, and its return value is ignored.
24836 * This can be used in place of additional $watches against the model value.
24838 * @property {Object} $error An object hash with all failing validator ids as keys.
24839 * @property {Object} $pending An object hash with all pending validator ids as keys.
24841 * @property {boolean} $untouched True if control has not lost focus yet.
24842 * @property {boolean} $touched True if control has lost focus.
24843 * @property {boolean} $pristine True if user has not interacted with the control yet.
24844 * @property {boolean} $dirty True if user has already interacted with the control.
24845 * @property {boolean} $valid True if there is no error.
24846 * @property {boolean} $invalid True if at least one error on the control.
24847 * @property {string} $name The name attribute of the control.
24851 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive.
24852 * The controller contains services for data-binding, validation, CSS updates, and value formatting
24853 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or
24854 * listening to DOM events.
24855 * Such DOM related logic should be provided by other directives which make use of
24856 * `NgModelController` for data-binding to control elements.
24857 * Angular provides this DOM logic for most {@link input `input`} elements.
24858 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example
24859 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements.
24862 * ### Custom Control Example
24863 * This example shows how to use `NgModelController` with a custom control to achieve
24864 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
24865 * collaborate together to achieve the desired result.
24867 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element
24868 * contents be edited in place by the user.
24870 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize}
24871 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`).
24872 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks
24873 * that content using the `$sce` service.
24875 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js">
24876 <file name="style.css">
24877 [contenteditable] {
24878 border: 1px solid black;
24879 background-color: white;
24884 border: 1px solid red;
24888 <file name="script.js">
24889 angular.module('customControl', ['ngSanitize']).
24890 directive('contenteditable', ['$sce', function($sce) {
24892 restrict: 'A', // only activate on element attribute
24893 require: '?ngModel', // get a hold of NgModelController
24894 link: function(scope, element, attrs, ngModel) {
24895 if (!ngModel) return; // do nothing if no ng-model
24897 // Specify how UI should be updated
24898 ngModel.$render = function() {
24899 element.html($sce.getTrustedHtml(ngModel.$viewValue || ''));
24902 // Listen for change events to enable binding
24903 element.on('blur keyup change', function() {
24904 scope.$evalAsync(read);
24906 read(); // initialize
24908 // Write data to the model
24910 var html = element.html();
24911 // When we clear the content editable the browser leaves a <br> behind
24912 // If strip-br attribute is provided then we strip this out
24913 if ( attrs.stripBr && html == '<br>' ) {
24916 ngModel.$setViewValue(html);
24922 <file name="index.html">
24923 <form name="myForm">
24924 <div contenteditable
24925 name="myWidget" ng-model="userContent"
24927 required>Change me!</div>
24928 <span ng-show="myForm.myWidget.$error.required">Required!</span>
24930 <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea>
24933 <file name="protractor.js" type="protractor">
24934 it('should data-bind and become invalid', function() {
24935 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') {
24936 // SafariDriver can't handle contenteditable
24937 // and Firefox driver can't clear contenteditables very well
24940 var contentEditable = element(by.css('[contenteditable]'));
24941 var content = 'Change me!';
24943 expect(contentEditable.getText()).toEqual(content);
24945 contentEditable.clear();
24946 contentEditable.sendKeys(protractor.Key.BACK_SPACE);
24947 expect(contentEditable.getText()).toEqual('');
24948 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/);
24955 var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate',
24956 function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) {
24957 this.$viewValue = Number.NaN;
24958 this.$modelValue = Number.NaN;
24959 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity.
24960 this.$validators = {};
24961 this.$asyncValidators = {};
24962 this.$parsers = [];
24963 this.$formatters = [];
24964 this.$viewChangeListeners = [];
24965 this.$untouched = true;
24966 this.$touched = false;
24967 this.$pristine = true;
24968 this.$dirty = false;
24969 this.$valid = true;
24970 this.$invalid = false;
24971 this.$error = {}; // keep invalid keys here
24972 this.$$success = {}; // keep valid keys here
24973 this.$pending = undefined; // keep pending keys here
24974 this.$name = $interpolate($attr.name || '', false)($scope);
24975 this.$$parentForm = nullFormCtrl;
24977 var parsedNgModel = $parse($attr.ngModel),
24978 parsedNgModelAssign = parsedNgModel.assign,
24979 ngModelGet = parsedNgModel,
24980 ngModelSet = parsedNgModelAssign,
24981 pendingDebounce = null,
24985 this.$$setOptions = function(options) {
24986 ctrl.$options = options;
24987 if (options && options.getterSetter) {
24988 var invokeModelGetter = $parse($attr.ngModel + '()'),
24989 invokeModelSetter = $parse($attr.ngModel + '($$$p)');
24991 ngModelGet = function($scope) {
24992 var modelValue = parsedNgModel($scope);
24993 if (isFunction(modelValue)) {
24994 modelValue = invokeModelGetter($scope);
24998 ngModelSet = function($scope, newValue) {
24999 if (isFunction(parsedNgModel($scope))) {
25000 invokeModelSetter($scope, {$$$p: ctrl.$modelValue});
25002 parsedNgModelAssign($scope, ctrl.$modelValue);
25005 } else if (!parsedNgModel.assign) {
25006 throw ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}",
25007 $attr.ngModel, startingTag($element));
25013 * @name ngModel.NgModelController#$render
25016 * Called when the view needs to be updated. It is expected that the user of the ng-model
25017 * directive will implement this method.
25019 * The `$render()` method is invoked in the following situations:
25021 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last
25022 * committed value then `$render()` is called to update the input control.
25023 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and
25024 * the `$viewValue` are different from last time.
25026 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of
25027 * `$modelValue` and `$viewValue` are actually different from their previous value. If `$modelValue`
25028 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be
25029 * invoked if you only change a property on the objects.
25031 this.$render = noop;
25035 * @name ngModel.NgModelController#$isEmpty
25038 * This is called when we need to determine if the value of an input is empty.
25040 * For instance, the required directive does this to work out if the input has data or not.
25042 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`.
25044 * You can override this for input directives whose concept of being empty is different from the
25045 * default. The `checkboxInputType` directive does this because in its case a value of `false`
25048 * @param {*} value The value of the input to check for emptiness.
25049 * @returns {boolean} True if `value` is "empty".
25051 this.$isEmpty = function(value) {
25052 return isUndefined(value) || value === '' || value === null || value !== value;
25055 var currentValidationRunId = 0;
25059 * @name ngModel.NgModelController#$setValidity
25062 * Change the validity state, and notify the form.
25064 * This method can be called within $parsers/$formatters or a custom validation implementation.
25065 * However, in most cases it should be sufficient to use the `ngModel.$validators` and
25066 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically.
25068 * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned
25069 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]`
25070 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding.
25071 * The `validationErrorKey` should be in camelCase and will get converted into dash-case
25072 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
25073 * class and can be bound to as `{{someForm.someControl.$error.myError}}` .
25074 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined),
25075 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`.
25076 * Skipped is used by Angular when validators do not run because of parse errors and
25077 * when `$asyncValidators` do not run because any of the `$validators` failed.
25079 addSetValidityMethod({
25081 $element: $element,
25082 set: function(object, property) {
25083 object[property] = true;
25085 unset: function(object, property) {
25086 delete object[property];
25093 * @name ngModel.NgModelController#$setPristine
25096 * Sets the control to its pristine state.
25098 * This method can be called to remove the `ng-dirty` class and set the control to its pristine
25099 * state (`ng-pristine` class). A model is considered to be pristine when the control
25100 * has not been changed from when first compiled.
25102 this.$setPristine = function() {
25103 ctrl.$dirty = false;
25104 ctrl.$pristine = true;
25105 $animate.removeClass($element, DIRTY_CLASS);
25106 $animate.addClass($element, PRISTINE_CLASS);
25111 * @name ngModel.NgModelController#$setDirty
25114 * Sets the control to its dirty state.
25116 * This method can be called to remove the `ng-pristine` class and set the control to its dirty
25117 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed
25118 * from when first compiled.
25120 this.$setDirty = function() {
25121 ctrl.$dirty = true;
25122 ctrl.$pristine = false;
25123 $animate.removeClass($element, PRISTINE_CLASS);
25124 $animate.addClass($element, DIRTY_CLASS);
25125 ctrl.$$parentForm.$setDirty();
25130 * @name ngModel.NgModelController#$setUntouched
25133 * Sets the control to its untouched state.
25135 * This method can be called to remove the `ng-touched` class and set the control to its
25136 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched
25137 * by default, however this function can be used to restore that state if the model has
25138 * already been touched by the user.
25140 this.$setUntouched = function() {
25141 ctrl.$touched = false;
25142 ctrl.$untouched = true;
25143 $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS);
25148 * @name ngModel.NgModelController#$setTouched
25151 * Sets the control to its touched state.
25153 * This method can be called to remove the `ng-untouched` class and set the control to its
25154 * touched state (`ng-touched` class). A model is considered to be touched when the user has
25155 * first focused the control element and then shifted focus away from the control (blur event).
25157 this.$setTouched = function() {
25158 ctrl.$touched = true;
25159 ctrl.$untouched = false;
25160 $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS);
25165 * @name ngModel.NgModelController#$rollbackViewValue
25168 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`,
25169 * which may be caused by a pending debounced event or because the input is waiting for a some
25172 * If you have an input that uses `ng-model-options` to set up debounced events or events such
25173 * as blur you can have a situation where there is a period when the `$viewValue`
25174 * is out of synch with the ngModel's `$modelValue`.
25176 * In this case, you can run into difficulties if you try to update the ngModel's `$modelValue`
25177 * programmatically before these debounced/future events have resolved/occurred, because Angular's
25178 * dirty checking mechanism is not able to tell whether the model has actually changed or not.
25180 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an
25181 * input which may have such events pending. This is important in order to make sure that the
25182 * input field will be updated with the new model value and any pending operations are cancelled.
25184 * <example name="ng-model-cancel-update" module="cancel-update-example">
25185 * <file name="app.js">
25186 * angular.module('cancel-update-example', [])
25188 * .controller('CancelUpdateController', ['$scope', function($scope) {
25189 * $scope.resetWithCancel = function(e) {
25190 * if (e.keyCode == 27) {
25191 * $scope.myForm.myInput1.$rollbackViewValue();
25192 * $scope.myValue = '';
25195 * $scope.resetWithoutCancel = function(e) {
25196 * if (e.keyCode == 27) {
25197 * $scope.myValue = '';
25202 * <file name="index.html">
25203 * <div ng-controller="CancelUpdateController">
25204 * <p>Try typing something in each input. See that the model only updates when you
25205 * blur off the input.
25207 * <p>Now see what happens if you start typing then press the Escape key</p>
25209 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }">
25210 * <p id="inputDescription1">With $rollbackViewValue()</p>
25211 * <input name="myInput1" aria-describedby="inputDescription1" ng-model="myValue"
25212 * ng-keydown="resetWithCancel($event)"><br/>
25213 * myValue: "{{ myValue }}"
25215 * <p id="inputDescription2">Without $rollbackViewValue()</p>
25216 * <input name="myInput2" aria-describedby="inputDescription2" ng-model="myValue"
25217 * ng-keydown="resetWithoutCancel($event)"><br/>
25218 * myValue: "{{ myValue }}"
25224 this.$rollbackViewValue = function() {
25225 $timeout.cancel(pendingDebounce);
25226 ctrl.$viewValue = ctrl.$$lastCommittedViewValue;
25232 * @name ngModel.NgModelController#$validate
25235 * Runs each of the registered validators (first synchronous validators and then
25236 * asynchronous validators).
25237 * If the validity changes to invalid, the model will be set to `undefined`,
25238 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`.
25239 * If the validity changes to valid, it will set the model to the last available valid
25240 * `$modelValue`, i.e. either the last parsed value or the last value set from the scope.
25242 this.$validate = function() {
25243 // ignore $validate before model is initialized
25244 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
25248 var viewValue = ctrl.$$lastCommittedViewValue;
25249 // Note: we use the $$rawModelValue as $modelValue might have been
25250 // set to undefined during a view -> model update that found validation
25251 // errors. We can't parse the view here, since that could change
25252 // the model although neither viewValue nor the model on the scope changed
25253 var modelValue = ctrl.$$rawModelValue;
25255 var prevValid = ctrl.$valid;
25256 var prevModelValue = ctrl.$modelValue;
25258 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
25260 ctrl.$$runValidators(modelValue, viewValue, function(allValid) {
25261 // If there was no change in validity, don't update the model
25262 // This prevents changing an invalid modelValue to undefined
25263 if (!allowInvalid && prevValid !== allValid) {
25264 // Note: Don't check ctrl.$valid here, as we could have
25265 // external validators (e.g. calculated on the server),
25266 // that just call $setValidity and need the model value
25267 // to calculate their validity.
25268 ctrl.$modelValue = allValid ? modelValue : undefined;
25270 if (ctrl.$modelValue !== prevModelValue) {
25271 ctrl.$$writeModelToScope();
25278 this.$$runValidators = function(modelValue, viewValue, doneCallback) {
25279 currentValidationRunId++;
25280 var localValidationRunId = currentValidationRunId;
25282 // check parser error
25283 if (!processParseErrors()) {
25284 validationDone(false);
25287 if (!processSyncValidators()) {
25288 validationDone(false);
25291 processAsyncValidators();
25293 function processParseErrors() {
25294 var errorKey = ctrl.$$parserName || 'parse';
25295 if (isUndefined(parserValid)) {
25296 setValidity(errorKey, null);
25298 if (!parserValid) {
25299 forEach(ctrl.$validators, function(v, name) {
25300 setValidity(name, null);
25302 forEach(ctrl.$asyncValidators, function(v, name) {
25303 setValidity(name, null);
25306 // Set the parse error last, to prevent unsetting it, should a $validators key == parserName
25307 setValidity(errorKey, parserValid);
25308 return parserValid;
25313 function processSyncValidators() {
25314 var syncValidatorsValid = true;
25315 forEach(ctrl.$validators, function(validator, name) {
25316 var result = validator(modelValue, viewValue);
25317 syncValidatorsValid = syncValidatorsValid && result;
25318 setValidity(name, result);
25320 if (!syncValidatorsValid) {
25321 forEach(ctrl.$asyncValidators, function(v, name) {
25322 setValidity(name, null);
25329 function processAsyncValidators() {
25330 var validatorPromises = [];
25331 var allValid = true;
25332 forEach(ctrl.$asyncValidators, function(validator, name) {
25333 var promise = validator(modelValue, viewValue);
25334 if (!isPromiseLike(promise)) {
25335 throw ngModelMinErr("$asyncValidators",
25336 "Expected asynchronous validator to return a promise but got '{0}' instead.", promise);
25338 setValidity(name, undefined);
25339 validatorPromises.push(promise.then(function() {
25340 setValidity(name, true);
25341 }, function(error) {
25343 setValidity(name, false);
25346 if (!validatorPromises.length) {
25347 validationDone(true);
25349 $q.all(validatorPromises).then(function() {
25350 validationDone(allValid);
25355 function setValidity(name, isValid) {
25356 if (localValidationRunId === currentValidationRunId) {
25357 ctrl.$setValidity(name, isValid);
25361 function validationDone(allValid) {
25362 if (localValidationRunId === currentValidationRunId) {
25364 doneCallback(allValid);
25371 * @name ngModel.NgModelController#$commitViewValue
25374 * Commit a pending update to the `$modelValue`.
25376 * Updates may be pending by a debounced event or because the input is waiting for a some future
25377 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController`
25378 * usually handles calling this in response to input events.
25380 this.$commitViewValue = function() {
25381 var viewValue = ctrl.$viewValue;
25383 $timeout.cancel(pendingDebounce);
25385 // If the view value has not changed then we should just exit, except in the case where there is
25386 // a native validator on the element. In this case the validation state may have changed even though
25387 // the viewValue has stayed empty.
25388 if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) {
25391 ctrl.$$lastCommittedViewValue = viewValue;
25394 if (ctrl.$pristine) {
25397 this.$$parseAndValidate();
25400 this.$$parseAndValidate = function() {
25401 var viewValue = ctrl.$$lastCommittedViewValue;
25402 var modelValue = viewValue;
25403 parserValid = isUndefined(modelValue) ? undefined : true;
25406 for (var i = 0; i < ctrl.$parsers.length; i++) {
25407 modelValue = ctrl.$parsers[i](modelValue);
25408 if (isUndefined(modelValue)) {
25409 parserValid = false;
25414 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
25415 // ctrl.$modelValue has not been touched yet...
25416 ctrl.$modelValue = ngModelGet($scope);
25418 var prevModelValue = ctrl.$modelValue;
25419 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
25420 ctrl.$$rawModelValue = modelValue;
25422 if (allowInvalid) {
25423 ctrl.$modelValue = modelValue;
25424 writeToModelIfNeeded();
25427 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
25428 // This can happen if e.g. $setViewValue is called from inside a parser
25429 ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
25430 if (!allowInvalid) {
25431 // Note: Don't check ctrl.$valid here, as we could have
25432 // external validators (e.g. calculated on the server),
25433 // that just call $setValidity and need the model value
25434 // to calculate their validity.
25435 ctrl.$modelValue = allValid ? modelValue : undefined;
25436 writeToModelIfNeeded();
25440 function writeToModelIfNeeded() {
25441 if (ctrl.$modelValue !== prevModelValue) {
25442 ctrl.$$writeModelToScope();
25447 this.$$writeModelToScope = function() {
25448 ngModelSet($scope, ctrl.$modelValue);
25449 forEach(ctrl.$viewChangeListeners, function(listener) {
25453 $exceptionHandler(e);
25460 * @name ngModel.NgModelController#$setViewValue
25463 * Update the view value.
25465 * This method should be called when a control wants to change the view value; typically,
25466 * this is done from within a DOM event handler. For example, the {@link ng.directive:input input}
25467 * directive calls it when the value of the input changes and {@link ng.directive:select select}
25468 * calls it when an option is selected.
25470 * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers`
25471 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged
25472 * value sent directly for processing, finally to be applied to `$modelValue` and then the
25473 * **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners,
25474 * in the `$viewChangeListeners` list, are called.
25476 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn`
25477 * and the `default` trigger is not listed, all those actions will remain pending until one of the
25478 * `updateOn` events is triggered on the DOM element.
25479 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions}
25480 * directive is used with a custom debounce for this particular event.
25481 * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce`
25482 * is specified, once the timer runs out.
25484 * When used with standard inputs, the view value will always be a string (which is in some cases
25485 * parsed into another type, such as a `Date` object for `input[date]`.)
25486 * However, custom controls might also pass objects to this method. In this case, we should make
25487 * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not
25488 * perform a deep watch of objects, it only looks for a change of identity. If you only change
25489 * the property of the object then ngModel will not realise that the object has changed and
25490 * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should
25491 * not change properties of the copy once it has been passed to `$setViewValue`.
25492 * Otherwise you may cause the model value on the scope to change incorrectly.
25494 * <div class="alert alert-info">
25495 * In any case, the value passed to the method should always reflect the current value
25496 * of the control. For example, if you are calling `$setViewValue` for an input element,
25497 * you should pass the input DOM value. Otherwise, the control and the scope model become
25498 * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change
25499 * the control's DOM value in any way. If we want to change the control's DOM value
25500 * programmatically, we should update the `ngModel` scope expression. Its new value will be
25501 * picked up by the model controller, which will run it through the `$formatters`, `$render` it
25502 * to update the DOM, and finally call `$validate` on it.
25505 * @param {*} value value from the view.
25506 * @param {string} trigger Event that triggered the update.
25508 this.$setViewValue = function(value, trigger) {
25509 ctrl.$viewValue = value;
25510 if (!ctrl.$options || ctrl.$options.updateOnDefault) {
25511 ctrl.$$debounceViewValueCommit(trigger);
25515 this.$$debounceViewValueCommit = function(trigger) {
25516 var debounceDelay = 0,
25517 options = ctrl.$options,
25520 if (options && isDefined(options.debounce)) {
25521 debounce = options.debounce;
25522 if (isNumber(debounce)) {
25523 debounceDelay = debounce;
25524 } else if (isNumber(debounce[trigger])) {
25525 debounceDelay = debounce[trigger];
25526 } else if (isNumber(debounce['default'])) {
25527 debounceDelay = debounce['default'];
25531 $timeout.cancel(pendingDebounce);
25532 if (debounceDelay) {
25533 pendingDebounce = $timeout(function() {
25534 ctrl.$commitViewValue();
25536 } else if ($rootScope.$$phase) {
25537 ctrl.$commitViewValue();
25539 $scope.$apply(function() {
25540 ctrl.$commitViewValue();
25546 // Note: we cannot use a normal scope.$watch as we want to detect the following:
25547 // 1. scope value is 'a'
25548 // 2. user enters 'b'
25549 // 3. ng-change kicks in and reverts scope value to 'a'
25550 // -> scope value did not change since the last digest as
25551 // ng-change executes in apply phase
25552 // 4. view should be changed back to 'a'
25553 $scope.$watch(function ngModelWatch() {
25554 var modelValue = ngModelGet($scope);
25556 // if scope model value and ngModel value are out of sync
25557 // TODO(perf): why not move this to the action fn?
25558 if (modelValue !== ctrl.$modelValue &&
25559 // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator
25560 (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue)
25562 ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
25563 parserValid = undefined;
25565 var formatters = ctrl.$formatters,
25566 idx = formatters.length;
25568 var viewValue = modelValue;
25570 viewValue = formatters[idx](viewValue);
25572 if (ctrl.$viewValue !== viewValue) {
25573 ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
25576 ctrl.$$runValidators(modelValue, viewValue, noop);
25593 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a
25594 * property on the scope using {@link ngModel.NgModelController NgModelController},
25595 * which is created and exposed by this directive.
25597 * `ngModel` is responsible for:
25599 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select`
25601 * - Providing validation behavior (i.e. required, number, email, url).
25602 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors).
25603 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations.
25604 * - Registering the control with its parent {@link ng.directive:form form}.
25606 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the
25607 * current scope. If the property doesn't already exist on this scope, it will be created
25608 * implicitly and added to the scope.
25610 * For best practices on using `ngModel`, see:
25612 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes)
25614 * For basic examples, how to use `ngModel`, see:
25616 * - {@link ng.directive:input input}
25617 * - {@link input[text] text}
25618 * - {@link input[checkbox] checkbox}
25619 * - {@link input[radio] radio}
25620 * - {@link input[number] number}
25621 * - {@link input[email] email}
25622 * - {@link input[url] url}
25623 * - {@link input[date] date}
25624 * - {@link input[datetime-local] datetime-local}
25625 * - {@link input[time] time}
25626 * - {@link input[month] month}
25627 * - {@link input[week] week}
25628 * - {@link ng.directive:select select}
25629 * - {@link ng.directive:textarea textarea}
25632 * The following CSS classes are added and removed on the associated input/select/textarea element
25633 * depending on the validity of the model.
25635 * - `ng-valid`: the model is valid
25636 * - `ng-invalid`: the model is invalid
25637 * - `ng-valid-[key]`: for each valid key added by `$setValidity`
25638 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity`
25639 * - `ng-pristine`: the control hasn't been interacted with yet
25640 * - `ng-dirty`: the control has been interacted with
25641 * - `ng-touched`: the control has been blurred
25642 * - `ng-untouched`: the control hasn't been blurred
25643 * - `ng-pending`: any `$asyncValidators` are unfulfilled
25645 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
25647 * ## Animation Hooks
25649 * Animations within models are triggered when any of the associated CSS classes are added and removed
25650 * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`,
25651 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself.
25652 * The animations that are triggered within ngModel are similar to how they work in ngClass and
25653 * animations can be hooked into using CSS transitions, keyframes as well as JS animations.
25655 * The following example shows a simple way to utilize CSS transitions to style an input element
25656 * that has been rendered as invalid after it has been validated:
25659 * //be sure to include ngAnimate as a module to hook into more
25660 * //advanced animations
25662 * transition:0.5s linear all;
25663 * background: white;
25665 * .my-input.ng-invalid {
25672 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample">
25673 <file name="index.html">
25675 angular.module('inputExample', [])
25676 .controller('ExampleController', ['$scope', function($scope) {
25682 transition:all linear 0.5s;
25683 background: transparent;
25685 .my-input.ng-invalid {
25690 <p id="inputDescription">
25691 Update input to see transitions when valid/invalid.
25692 Integer is a valid value.
25694 <form name="testForm" ng-controller="ExampleController">
25695 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input"
25696 aria-describedby="inputDescription" />
25701 * ## Binding to a getter/setter
25703 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a
25704 * function that returns a representation of the model when called with zero arguments, and sets
25705 * the internal state of a model when called with an argument. It's sometimes useful to use this
25706 * for models that have an internal representation that's different from what the model exposes
25709 * <div class="alert alert-success">
25710 * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more
25711 * frequently than other parts of your code.
25714 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that
25715 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to
25716 * a `<form>`, which will enable this behavior for all `<input>`s within it. See
25717 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more.
25719 * The following example shows how to use `ngModel` with a getter/setter:
25722 * <example name="ngModel-getter-setter" module="getterSetterExample">
25723 <file name="index.html">
25724 <div ng-controller="ExampleController">
25725 <form name="userForm">
25727 <input type="text" name="userName"
25728 ng-model="user.name"
25729 ng-model-options="{ getterSetter: true }" />
25732 <pre>user.name = <span ng-bind="user.name()"></span></pre>
25735 <file name="app.js">
25736 angular.module('getterSetterExample', [])
25737 .controller('ExampleController', ['$scope', function($scope) {
25738 var _name = 'Brian';
25740 name: function(newName) {
25741 // Note that newName can be undefined for two reasons:
25742 // 1. Because it is called as a getter and thus called with no arguments
25743 // 2. Because the property should actually be set to undefined. This happens e.g. if the
25744 // input is invalid
25745 return arguments.length ? (_name = newName) : _name;
25752 var ngModelDirective = ['$rootScope', function($rootScope) {
25755 require: ['ngModel', '^?form', '^?ngModelOptions'],
25756 controller: NgModelController,
25757 // Prelink needs to run before any input directive
25758 // so that we can set the NgModelOptions in NgModelController
25759 // before anyone else uses it.
25761 compile: function ngModelCompile(element) {
25762 // Setup initial state of the control
25763 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);
25766 pre: function ngModelPreLink(scope, element, attr, ctrls) {
25767 var modelCtrl = ctrls[0],
25768 formCtrl = ctrls[1] || modelCtrl.$$parentForm;
25770 modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
25772 // notify others, especially parent forms
25773 formCtrl.$addControl(modelCtrl);
25775 attr.$observe('name', function(newValue) {
25776 if (modelCtrl.$name !== newValue) {
25777 modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue);
25781 scope.$on('$destroy', function() {
25782 modelCtrl.$$parentForm.$removeControl(modelCtrl);
25785 post: function ngModelPostLink(scope, element, attr, ctrls) {
25786 var modelCtrl = ctrls[0];
25787 if (modelCtrl.$options && modelCtrl.$options.updateOn) {
25788 element.on(modelCtrl.$options.updateOn, function(ev) {
25789 modelCtrl.$$debounceViewValueCommit(ev && ev.type);
25793 element.on('blur', function(ev) {
25794 if (modelCtrl.$touched) return;
25796 if ($rootScope.$$phase) {
25797 scope.$evalAsync(modelCtrl.$setTouched);
25799 scope.$apply(modelCtrl.$setTouched);
25808 var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
25812 * @name ngModelOptions
25815 * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of
25816 * events that will trigger a model update and/or a debouncing delay so that the actual update only
25817 * takes place when a timer expires; this timer will be reset after another change takes place.
25819 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might
25820 * be different from the value in the actual model. This means that if you update the model you
25821 * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in
25822 * order to make sure it is synchronized with the model and that any debounced action is canceled.
25824 * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`}
25825 * method is by making sure the input is placed inside a form that has a `name` attribute. This is
25826 * important because `form` controllers are published to the related scope under the name in their
25827 * `name` attribute.
25829 * Any pending changes will take place immediately when an enclosing form is submitted via the
25830 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
25831 * to have access to the updated model.
25833 * `ngModelOptions` has an effect on the element it's declared on and its descendants.
25835 * @param {Object} ngModelOptions options to apply to the current model. Valid keys are:
25836 * - `updateOn`: string specifying which event should the input be bound to. You can set several
25837 * events using an space delimited list. There is a special event called `default` that
25838 * matches the default events belonging of the control.
25839 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A
25840 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a
25841 * custom value for each event. For example:
25842 * `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }"`
25843 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did
25844 * not validate correctly instead of the default behavior of setting the model to undefined.
25845 * - `getterSetter`: boolean value which determines whether or not to treat functions bound to
25846 `ngModel` as getters/setters.
25847 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
25848 * `<input type="date">`, `<input type="time">`, ... . It understands UTC/GMT and the
25849 * continental US time zone abbreviations, but for general use, use a time zone offset, for
25850 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
25851 * If not specified, the timezone of the browser will be used.
25855 The following example shows how to override immediate updates. Changes on the inputs within the
25856 form will update the model only when the control loses focus (blur event). If `escape` key is
25857 pressed while the input field is focused, the value is reset to the value in the current model.
25859 <example name="ngModelOptions-directive-blur" module="optionsExample">
25860 <file name="index.html">
25861 <div ng-controller="ExampleController">
25862 <form name="userForm">
25864 <input type="text" name="userName"
25865 ng-model="user.name"
25866 ng-model-options="{ updateOn: 'blur' }"
25867 ng-keyup="cancel($event)" />
25870 <input type="text" ng-model="user.data" />
25873 <pre>user.name = <span ng-bind="user.name"></span></pre>
25874 <pre>user.data = <span ng-bind="user.data"></span></pre>
25877 <file name="app.js">
25878 angular.module('optionsExample', [])
25879 .controller('ExampleController', ['$scope', function($scope) {
25880 $scope.user = { name: 'John', data: '' };
25882 $scope.cancel = function(e) {
25883 if (e.keyCode == 27) {
25884 $scope.userForm.userName.$rollbackViewValue();
25889 <file name="protractor.js" type="protractor">
25890 var model = element(by.binding('user.name'));
25891 var input = element(by.model('user.name'));
25892 var other = element(by.model('user.data'));
25894 it('should allow custom events', function() {
25895 input.sendKeys(' Doe');
25897 expect(model.getText()).toEqual('John');
25899 expect(model.getText()).toEqual('John Doe');
25902 it('should $rollbackViewValue when model changes', function() {
25903 input.sendKeys(' Doe');
25904 expect(input.getAttribute('value')).toEqual('John Doe');
25905 input.sendKeys(protractor.Key.ESCAPE);
25906 expect(input.getAttribute('value')).toEqual('John');
25908 expect(model.getText()).toEqual('John');
25913 This one shows how to debounce model changes. Model will be updated only 1 sec after last change.
25914 If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.
25916 <example name="ngModelOptions-directive-debounce" module="optionsExample">
25917 <file name="index.html">
25918 <div ng-controller="ExampleController">
25919 <form name="userForm">
25921 <input type="text" name="userName"
25922 ng-model="user.name"
25923 ng-model-options="{ debounce: 1000 }" />
25925 <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button>
25928 <pre>user.name = <span ng-bind="user.name"></span></pre>
25931 <file name="app.js">
25932 angular.module('optionsExample', [])
25933 .controller('ExampleController', ['$scope', function($scope) {
25934 $scope.user = { name: 'Igor' };
25939 This one shows how to bind to getter/setters:
25941 <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample">
25942 <file name="index.html">
25943 <div ng-controller="ExampleController">
25944 <form name="userForm">
25946 <input type="text" name="userName"
25947 ng-model="user.name"
25948 ng-model-options="{ getterSetter: true }" />
25951 <pre>user.name = <span ng-bind="user.name()"></span></pre>
25954 <file name="app.js">
25955 angular.module('getterSetterExample', [])
25956 .controller('ExampleController', ['$scope', function($scope) {
25957 var _name = 'Brian';
25959 name: function(newName) {
25960 // Note that newName can be undefined for two reasons:
25961 // 1. Because it is called as a getter and thus called with no arguments
25962 // 2. Because the property should actually be set to undefined. This happens e.g. if the
25963 // input is invalid
25964 return arguments.length ? (_name = newName) : _name;
25971 var ngModelOptionsDirective = function() {
25974 controller: ['$scope', '$attrs', function($scope, $attrs) {
25976 this.$options = copy($scope.$eval($attrs.ngModelOptions));
25977 // Allow adding/overriding bound events
25978 if (isDefined(this.$options.updateOn)) {
25979 this.$options.updateOnDefault = false;
25980 // extract "default" pseudo-event from list of events that can trigger a model update
25981 this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() {
25982 that.$options.updateOnDefault = true;
25986 this.$options.updateOnDefault = true;
25995 function addSetValidityMethod(context) {
25996 var ctrl = context.ctrl,
25997 $element = context.$element,
26000 unset = context.unset,
26001 $animate = context.$animate;
26003 classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS));
26005 ctrl.$setValidity = setValidity;
26007 function setValidity(validationErrorKey, state, controller) {
26008 if (isUndefined(state)) {
26009 createAndSet('$pending', validationErrorKey, controller);
26011 unsetAndCleanup('$pending', validationErrorKey, controller);
26013 if (!isBoolean(state)) {
26014 unset(ctrl.$error, validationErrorKey, controller);
26015 unset(ctrl.$$success, validationErrorKey, controller);
26018 unset(ctrl.$error, validationErrorKey, controller);
26019 set(ctrl.$$success, validationErrorKey, controller);
26021 set(ctrl.$error, validationErrorKey, controller);
26022 unset(ctrl.$$success, validationErrorKey, controller);
26025 if (ctrl.$pending) {
26026 cachedToggleClass(PENDING_CLASS, true);
26027 ctrl.$valid = ctrl.$invalid = undefined;
26028 toggleValidationCss('', null);
26030 cachedToggleClass(PENDING_CLASS, false);
26031 ctrl.$valid = isObjectEmpty(ctrl.$error);
26032 ctrl.$invalid = !ctrl.$valid;
26033 toggleValidationCss('', ctrl.$valid);
26036 // re-read the state as the set/unset methods could have
26037 // combined state in ctrl.$error[validationError] (used for forms),
26038 // where setting/unsetting only increments/decrements the value,
26039 // and does not replace it.
26041 if (ctrl.$pending && ctrl.$pending[validationErrorKey]) {
26042 combinedState = undefined;
26043 } else if (ctrl.$error[validationErrorKey]) {
26044 combinedState = false;
26045 } else if (ctrl.$$success[validationErrorKey]) {
26046 combinedState = true;
26048 combinedState = null;
26051 toggleValidationCss(validationErrorKey, combinedState);
26052 ctrl.$$parentForm.$setValidity(validationErrorKey, combinedState, ctrl);
26055 function createAndSet(name, value, controller) {
26059 set(ctrl[name], value, controller);
26062 function unsetAndCleanup(name, value, controller) {
26064 unset(ctrl[name], value, controller);
26066 if (isObjectEmpty(ctrl[name])) {
26067 ctrl[name] = undefined;
26071 function cachedToggleClass(className, switchValue) {
26072 if (switchValue && !classCache[className]) {
26073 $animate.addClass($element, className);
26074 classCache[className] = true;
26075 } else if (!switchValue && classCache[className]) {
26076 $animate.removeClass($element, className);
26077 classCache[className] = false;
26081 function toggleValidationCss(validationErrorKey, isValid) {
26082 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
26084 cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true);
26085 cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false);
26089 function isObjectEmpty(obj) {
26091 for (var prop in obj) {
26092 if (obj.hasOwnProperty(prop)) {
26102 * @name ngNonBindable
26107 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current
26108 * DOM element. This is useful if the element contains what appears to be Angular directives and
26109 * bindings but which should be ignored by Angular. This could be the case if you have a site that
26110 * displays snippets of code, for instance.
26115 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present,
26116 * but the one wrapped in `ngNonBindable` is left alone.
26120 <file name="index.html">
26121 <div>Normal: {{1 + 2}}</div>
26122 <div ng-non-bindable>Ignored: {{1 + 2}}</div>
26124 <file name="protractor.js" type="protractor">
26125 it('should check ng-non-bindable', function() {
26126 expect(element(by.binding('1 + 2')).getText()).toContain('3');
26127 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/);
26132 var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
26134 /* global jqLiteRemove */
26136 var ngOptionsMinErr = minErr('ngOptions');
26145 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
26146 * elements for the `<select>` element using the array or object obtained by evaluating the
26147 * `ngOptions` comprehension expression.
26149 * In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
26150 * similar result. However, `ngOptions` provides some benefits such as reducing memory and
26151 * increasing speed by not creating a new scope for each repeated instance, as well as providing
26152 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
26153 * comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound
26154 * to a non-string value. This is because an option element can only be bound to string values at
26157 * When an item in the `<select>` menu is selected, the array element or object property
26158 * represented by the selected option will be bound to the model identified by the `ngModel`
26161 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
26162 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
26163 * option. See example below for demonstration.
26165 * ## Complex Models (objects or collections)
26167 * By default, `ngModel` watches the model by reference, not value. This is important to know when
26168 * binding the select to a model that is an object or a collection.
26170 * One issue occurs if you want to preselect an option. For example, if you set
26171 * the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection,
26172 * because the objects are not identical. So by default, you should always reference the item in your collection
26173 * for preselections, e.g.: `$scope.selected = $scope.collection[3]`.
26175 * Another solution is to use a `track by` clause, because then `ngOptions` will track the identity
26176 * of the item not by reference, but by the result of the `track by` expression. For example, if your
26177 * collection items have an id property, you would `track by item.id`.
26179 * A different issue with objects or collections is that ngModel won't detect if an object property or
26180 * a collection item changes. For that reason, `ngOptions` additionally watches the model using
26181 * `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute.
26182 * This allows ngOptions to trigger a re-rendering of the options even if the actual object/collection
26183 * has not changed identity, but only a property on the object or an item in the collection changes.
26185 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection
26186 * if the model is an array). This means that changing a property deeper than the first level inside the
26187 * object/collection will not trigger a re-rendering.
26189 * ## `select` **`as`**
26191 * Using `select` **`as`** will bind the result of the `select` expression to the model, but
26192 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
26193 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression
26194 * is used, the result of that expression will be set as the value of the `option` and `select` elements.
26197 * ### `select` **`as`** and **`track by`**
26199 * <div class="alert alert-warning">
26200 * Be careful when using `select` **`as`** and **`track by`** in the same expression.
26203 * Given this array of items on the $scope:
26206 * $scope.items = [{
26209 * subItem: { name: 'aSubItem' }
26213 * subItem: { name: 'bSubItem' }
26220 * <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select>
26223 * $scope.selected = $scope.items[0];
26226 * but this will not work:
26229 * <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select>
26232 * $scope.selected = $scope.items[0].subItem;
26235 * In both examples, the **`track by`** expression is applied successfully to each `item` in the
26236 * `items` array. Because the selected option has been set programmatically in the controller, the
26237 * **`track by`** expression is also applied to the `ngModel` value. In the first example, the
26238 * `ngModel` value is `items[0]` and the **`track by`** expression evaluates to `items[0].id` with
26239 * no issue. In the second example, the `ngModel` value is `items[0].subItem` and the **`track by`**
26240 * expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value
26241 * is not matched against any `<option>` and the `<select>` appears as having no selected value.
26244 * @param {string} ngModel Assignable angular expression to data-bind to.
26245 * @param {string=} name Property name of the form under which the control is published.
26246 * @param {string=} required The control is considered valid only if value is entered.
26247 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
26248 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
26249 * `required` when you want to data-bind to the `required` attribute.
26250 * @param {comprehension_expression=} ngOptions in one of the following forms:
26252 * * for array data sources:
26253 * * `label` **`for`** `value` **`in`** `array`
26254 * * `select` **`as`** `label` **`for`** `value` **`in`** `array`
26255 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
26256 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array`
26257 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
26258 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
26259 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
26260 * (for including a filter with `track by`)
26261 * * for object data sources:
26262 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
26263 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
26264 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
26265 * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object`
26266 * * `select` **`as`** `label` **`group by`** `group`
26267 * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
26268 * * `select` **`as`** `label` **`disable when`** `disable`
26269 * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
26273 * * `array` / `object`: an expression which evaluates to an array / object to iterate over.
26274 * * `value`: local variable which will refer to each item in the `array` or each property value
26275 * of `object` during iteration.
26276 * * `key`: local variable which will refer to a property name in `object` during iteration.
26277 * * `label`: The result of this expression will be the label for `<option>` element. The
26278 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
26279 * * `select`: The result of this expression will be bound to the model of the parent `<select>`
26280 * element. If not specified, `select` expression will default to `value`.
26281 * * `group`: The result of this expression will be used to group options using the `<optgroup>`
26283 * * `disable`: The result of this expression will be used to disable the rendered `<option>`
26284 * element. Return `true` to disable.
26285 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be
26286 * used to identify the objects in the array. The `trackexpr` will most likely refer to the
26287 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved
26288 * even when the options are recreated (e.g. reloaded from the server).
26291 <example module="selectExample">
26292 <file name="index.html">
26294 angular.module('selectExample', [])
26295 .controller('ExampleController', ['$scope', function($scope) {
26297 {name:'black', shade:'dark'},
26298 {name:'white', shade:'light', notAnOption: true},
26299 {name:'red', shade:'dark'},
26300 {name:'blue', shade:'dark', notAnOption: true},
26301 {name:'yellow', shade:'light', notAnOption: false}
26303 $scope.myColor = $scope.colors[2]; // red
26306 <div ng-controller="ExampleController">
26308 <li ng-repeat="color in colors">
26309 <label>Name: <input ng-model="color.name"></label>
26310 <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label>
26311 <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button>
26314 <button ng-click="colors.push({})">add</button>
26318 <label>Color (null not allowed):
26319 <select ng-model="myColor" ng-options="color.name for color in colors"></select>
26321 <label>Color (null allowed):
26322 <span class="nullable">
26323 <select ng-model="myColor" ng-options="color.name for color in colors">
26324 <option value="">-- choose color --</option>
26326 </span></label><br/>
26328 <label>Color grouped by shade:
26329 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors">
26333 <label>Color grouped by shade, with some disabled:
26334 <select ng-model="myColor"
26335 ng-options="color.name group by color.shade disable when color.notAnOption for color in colors">
26341 Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>.
26344 Currently selected: {{ {selected_color:myColor} }}
26345 <div style="border:solid 1px black; height:20px"
26346 ng-style="{'background-color':myColor.name}">
26350 <file name="protractor.js" type="protractor">
26351 it('should check ng-options', function() {
26352 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red');
26353 element.all(by.model('myColor')).first().click();
26354 element.all(by.css('select[ng-model="myColor"] option')).first().click();
26355 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black');
26356 element(by.css('.nullable select[ng-model="myColor"]')).click();
26357 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click();
26358 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null');
26364 // jshint maxlen: false
26365 // //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555550000000006666666666666660000000777777777777777000000000000000888888888800000000000000000009999999999
26366 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]+?))?$/;
26367 // 1: value expression (valueFn)
26368 // 2: label expression (displayFn)
26369 // 3: group by expression (groupByFn)
26370 // 4: disable when expression (disableWhenFn)
26371 // 5: array item variable name
26372 // 6: object item key variable name
26373 // 7: object item value variable name
26374 // 8: collection expression
26375 // 9: track by expression
26376 // jshint maxlen: 100
26379 var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
26381 function parseOptionsExpression(optionsExp, selectElement, scope) {
26383 var match = optionsExp.match(NG_OPTIONS_REGEXP);
26385 throw ngOptionsMinErr('iexp',
26386 "Expected expression in form of " +
26387 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
26388 " but got '{0}'. Element: {1}",
26389 optionsExp, startingTag(selectElement));
26392 // Extract the parts from the ngOptions expression
26394 // The variable name for the value of the item in the collection
26395 var valueName = match[5] || match[7];
26396 // The variable name for the key of the item in the collection
26397 var keyName = match[6];
26399 // An expression that generates the viewValue for an option if there is a label expression
26400 var selectAs = / as /.test(match[0]) && match[1];
26401 // An expression that is used to track the id of each object in the options collection
26402 var trackBy = match[9];
26403 // An expression that generates the viewValue for an option if there is no label expression
26404 var valueFn = $parse(match[2] ? match[1] : valueName);
26405 var selectAsFn = selectAs && $parse(selectAs);
26406 var viewValueFn = selectAsFn || valueFn;
26407 var trackByFn = trackBy && $parse(trackBy);
26409 // Get the value by which we are going to track the option
26410 // if we have a trackFn then use that (passing scope and locals)
26411 // otherwise just hash the given viewValue
26412 var getTrackByValueFn = trackBy ?
26413 function(value, locals) { return trackByFn(scope, locals); } :
26414 function getHashOfValue(value) { return hashKey(value); };
26415 var getTrackByValue = function(value, key) {
26416 return getTrackByValueFn(value, getLocals(value, key));
26419 var displayFn = $parse(match[2] || match[1]);
26420 var groupByFn = $parse(match[3] || '');
26421 var disableWhenFn = $parse(match[4] || '');
26422 var valuesFn = $parse(match[8]);
26425 var getLocals = keyName ? function(value, key) {
26426 locals[keyName] = key;
26427 locals[valueName] = value;
26429 } : function(value) {
26430 locals[valueName] = value;
26435 function Option(selectValue, viewValue, label, group, disabled) {
26436 this.selectValue = selectValue;
26437 this.viewValue = viewValue;
26438 this.label = label;
26439 this.group = group;
26440 this.disabled = disabled;
26443 function getOptionValuesKeys(optionValues) {
26444 var optionValuesKeys;
26446 if (!keyName && isArrayLike(optionValues)) {
26447 optionValuesKeys = optionValues;
26449 // if object, extract keys, in enumeration order, unsorted
26450 optionValuesKeys = [];
26451 for (var itemKey in optionValues) {
26452 if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') {
26453 optionValuesKeys.push(itemKey);
26457 return optionValuesKeys;
26462 getTrackByValue: getTrackByValue,
26463 getWatchables: $parse(valuesFn, function(optionValues) {
26464 // Create a collection of things that we would like to watch (watchedArray)
26465 // so that they can all be watched using a single $watchCollection
26466 // that only runs the handler once if anything changes
26467 var watchedArray = [];
26468 optionValues = optionValues || [];
26470 var optionValuesKeys = getOptionValuesKeys(optionValues);
26471 var optionValuesLength = optionValuesKeys.length;
26472 for (var index = 0; index < optionValuesLength; index++) {
26473 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
26474 var value = optionValues[key];
26476 var locals = getLocals(optionValues[key], key);
26477 var selectValue = getTrackByValueFn(optionValues[key], locals);
26478 watchedArray.push(selectValue);
26480 // Only need to watch the displayFn if there is a specific label expression
26481 if (match[2] || match[1]) {
26482 var label = displayFn(scope, locals);
26483 watchedArray.push(label);
26486 // Only need to watch the disableWhenFn if there is a specific disable expression
26488 var disableWhen = disableWhenFn(scope, locals);
26489 watchedArray.push(disableWhen);
26492 return watchedArray;
26495 getOptions: function() {
26497 var optionItems = [];
26498 var selectValueMap = {};
26500 // The option values were already computed in the `getWatchables` fn,
26501 // which must have been called to trigger `getOptions`
26502 var optionValues = valuesFn(scope) || [];
26503 var optionValuesKeys = getOptionValuesKeys(optionValues);
26504 var optionValuesLength = optionValuesKeys.length;
26506 for (var index = 0; index < optionValuesLength; index++) {
26507 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
26508 var value = optionValues[key];
26509 var locals = getLocals(value, key);
26510 var viewValue = viewValueFn(scope, locals);
26511 var selectValue = getTrackByValueFn(viewValue, locals);
26512 var label = displayFn(scope, locals);
26513 var group = groupByFn(scope, locals);
26514 var disabled = disableWhenFn(scope, locals);
26515 var optionItem = new Option(selectValue, viewValue, label, group, disabled);
26517 optionItems.push(optionItem);
26518 selectValueMap[selectValue] = optionItem;
26522 items: optionItems,
26523 selectValueMap: selectValueMap,
26524 getOptionFromViewValue: function(value) {
26525 return selectValueMap[getTrackByValue(value)];
26527 getViewValueFromOption: function(option) {
26528 // If the viewValue could be an object that may be mutated by the application,
26529 // we need to make a copy and not return the reference to the value on the option.
26530 return trackBy ? angular.copy(option.viewValue) : option.viewValue;
26538 // we can't just jqLite('<option>') since jqLite is not smart enough
26539 // to create it in <select> and IE barfs otherwise.
26540 var optionTemplate = document.createElement('option'),
26541 optGroupTemplate = document.createElement('optgroup');
26544 function ngOptionsPostLink(scope, selectElement, attr, ctrls) {
26546 // if ngModel is not defined, we don't need to do anything
26547 var ngModelCtrl = ctrls[1];
26548 if (!ngModelCtrl) return;
26550 var selectCtrl = ctrls[0];
26551 var multiple = attr.multiple;
26553 // The emptyOption allows the application developer to provide their own custom "empty"
26554 // option when the viewValue does not match any of the option values.
26556 for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) {
26557 if (children[i].value === '') {
26558 emptyOption = children.eq(i);
26563 var providedEmptyOption = !!emptyOption;
26565 var unknownOption = jqLite(optionTemplate.cloneNode(false));
26566 unknownOption.val('?');
26569 var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope);
26572 var renderEmptyOption = function() {
26573 if (!providedEmptyOption) {
26574 selectElement.prepend(emptyOption);
26576 selectElement.val('');
26577 emptyOption.prop('selected', true); // needed for IE
26578 emptyOption.attr('selected', true);
26581 var removeEmptyOption = function() {
26582 if (!providedEmptyOption) {
26583 emptyOption.remove();
26588 var renderUnknownOption = function() {
26589 selectElement.prepend(unknownOption);
26590 selectElement.val('?');
26591 unknownOption.prop('selected', true); // needed for IE
26592 unknownOption.attr('selected', true);
26595 var removeUnknownOption = function() {
26596 unknownOption.remove();
26599 // Update the controller methods for multiple selectable options
26602 selectCtrl.writeValue = function writeNgOptionsValue(value) {
26603 var option = options.getOptionFromViewValue(value);
26605 if (option && !option.disabled) {
26606 if (selectElement[0].value !== option.selectValue) {
26607 removeUnknownOption();
26608 removeEmptyOption();
26610 selectElement[0].value = option.selectValue;
26611 option.element.selected = true;
26612 option.element.setAttribute('selected', 'selected');
26615 if (value === null || providedEmptyOption) {
26616 removeUnknownOption();
26617 renderEmptyOption();
26619 removeEmptyOption();
26620 renderUnknownOption();
26625 selectCtrl.readValue = function readNgOptionsValue() {
26627 var selectedOption = options.selectValueMap[selectElement.val()];
26629 if (selectedOption && !selectedOption.disabled) {
26630 removeEmptyOption();
26631 removeUnknownOption();
26632 return options.getViewValueFromOption(selectedOption);
26637 // If we are using `track by` then we must watch the tracked value on the model
26638 // since ngModel only watches for object identity change
26639 if (ngOptions.trackBy) {
26641 function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); },
26642 function() { ngModelCtrl.$render(); }
26648 ngModelCtrl.$isEmpty = function(value) {
26649 return !value || value.length === 0;
26653 selectCtrl.writeValue = function writeNgOptionsMultiple(value) {
26654 options.items.forEach(function(option) {
26655 option.element.selected = false;
26659 value.forEach(function(item) {
26660 var option = options.getOptionFromViewValue(item);
26661 if (option && !option.disabled) option.element.selected = true;
26667 selectCtrl.readValue = function readNgOptionsMultiple() {
26668 var selectedValues = selectElement.val() || [],
26671 forEach(selectedValues, function(value) {
26672 var option = options.selectValueMap[value];
26673 if (option && !option.disabled) selections.push(options.getViewValueFromOption(option));
26679 // If we are using `track by` then we must watch these tracked values on the model
26680 // since ngModel only watches for object identity change
26681 if (ngOptions.trackBy) {
26683 scope.$watchCollection(function() {
26684 if (isArray(ngModelCtrl.$viewValue)) {
26685 return ngModelCtrl.$viewValue.map(function(value) {
26686 return ngOptions.getTrackByValue(value);
26690 ngModelCtrl.$render();
26697 if (providedEmptyOption) {
26699 // we need to remove it before calling selectElement.empty() because otherwise IE will
26700 // remove the label from the element. wtf?
26701 emptyOption.remove();
26703 // compile the element since there might be bindings in it
26704 $compile(emptyOption)(scope);
26706 // remove the class, which is added automatically because we recompile the element and it
26707 // becomes the compilation root
26708 emptyOption.removeClass('ng-scope');
26710 emptyOption = jqLite(optionTemplate.cloneNode(false));
26713 // We need to do this here to ensure that the options object is defined
26714 // when we first hit it in writeNgOptionsValue
26717 // We will re-render the option elements if the option values or labels change
26718 scope.$watchCollection(ngOptions.getWatchables, updateOptions);
26720 // ------------------------------------------------------------------ //
26723 function updateOptionElement(option, element) {
26724 option.element = element;
26725 element.disabled = option.disabled;
26726 // NOTE: The label must be set before the value, otherwise IE10/11/EDGE create unresponsive
26727 // selects in certain circumstances when multiple selects are next to each other and display
26728 // the option list in listbox style, i.e. the select is [multiple], or specifies a [size].
26729 // See https://github.com/angular/angular.js/issues/11314 for more info.
26730 // This is unfortunately untestable with unit / e2e tests
26731 if (option.label !== element.label) {
26732 element.label = option.label;
26733 element.textContent = option.label;
26735 if (option.value !== element.value) element.value = option.selectValue;
26738 function addOrReuseElement(parent, current, type, templateElement) {
26740 // Check whether we can reuse the next element
26741 if (current && lowercase(current.nodeName) === type) {
26742 // The next element is the right type so reuse it
26745 // The next element is not the right type so create a new one
26746 element = templateElement.cloneNode(false);
26748 // There are no more elements so just append it to the select
26749 parent.appendChild(element);
26751 // The next element is not a group so insert the new one
26752 parent.insertBefore(element, current);
26759 function removeExcessElements(current) {
26762 next = current.nextSibling;
26763 jqLiteRemove(current);
26769 function skipEmptyAndUnknownOptions(current) {
26770 var emptyOption_ = emptyOption && emptyOption[0];
26771 var unknownOption_ = unknownOption && unknownOption[0];
26773 // We cannot rely on the extracted empty option being the same as the compiled empty option,
26774 // because the compiled empty option might have been replaced by a comment because
26775 // it had an "element" transclusion directive on it (such as ngIf)
26776 if (emptyOption_ || unknownOption_) {
26778 (current === emptyOption_ ||
26779 current === unknownOption_ ||
26780 current.nodeType === NODE_TYPE_COMMENT ||
26781 current.value === '')) {
26782 current = current.nextSibling;
26789 function updateOptions() {
26791 var previousValue = options && selectCtrl.readValue();
26793 options = ngOptions.getOptions();
26796 var currentElement = selectElement[0].firstChild;
26798 // Ensure that the empty option is always there if it was explicitly provided
26799 if (providedEmptyOption) {
26800 selectElement.prepend(emptyOption);
26803 currentElement = skipEmptyAndUnknownOptions(currentElement);
26805 options.items.forEach(function updateOption(option) {
26810 if (option.group) {
26812 // This option is to live in a group
26813 // See if we have already created this group
26814 group = groupMap[option.group];
26818 // We have not already created this group
26819 groupElement = addOrReuseElement(selectElement[0],
26823 // Move to the next element
26824 currentElement = groupElement.nextSibling;
26826 // Update the label on the group element
26827 groupElement.label = option.group;
26829 // Store it for use later
26830 group = groupMap[option.group] = {
26831 groupElement: groupElement,
26832 currentOptionElement: groupElement.firstChild
26837 // So now we have a group for this option we add the option to the group
26838 optionElement = addOrReuseElement(group.groupElement,
26839 group.currentOptionElement,
26842 updateOptionElement(option, optionElement);
26843 // Move to the next element
26844 group.currentOptionElement = optionElement.nextSibling;
26848 // This option is not in a group
26849 optionElement = addOrReuseElement(selectElement[0],
26853 updateOptionElement(option, optionElement);
26854 // Move to the next element
26855 currentElement = optionElement.nextSibling;
26860 // Now remove all excess options and group
26861 Object.keys(groupMap).forEach(function(key) {
26862 removeExcessElements(groupMap[key].currentOptionElement);
26864 removeExcessElements(currentElement);
26866 ngModelCtrl.$render();
26868 // Check to see if the value has changed due to the update to the options
26869 if (!ngModelCtrl.$isEmpty(previousValue)) {
26870 var nextValue = selectCtrl.readValue();
26871 if (ngOptions.trackBy ? !equals(previousValue, nextValue) : previousValue !== nextValue) {
26872 ngModelCtrl.$setViewValue(nextValue);
26873 ngModelCtrl.$render();
26883 require: ['select', '?ngModel'],
26885 pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) {
26886 // Deactivate the SelectController.register method to prevent
26887 // option directives from accidentally registering themselves
26888 // (and unwanted $destroy handlers etc.)
26889 ctrls[0].registerOption = noop;
26891 post: ngOptionsPostLink
26898 * @name ngPluralize
26902 * `ngPluralize` is a directive that displays messages according to en-US localization rules.
26903 * These rules are bundled with angular.js, but can be overridden
26904 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive
26905 * by specifying the mappings between
26906 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
26907 * and the strings to be displayed.
26909 * # Plural categories and explicit number rules
26911 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
26912 * in Angular's default en-US locale: "one" and "other".
26914 * While a plural category may match many numbers (for example, in en-US locale, "other" can match
26915 * any number that is not 1), an explicit number rule can only match one number. For example, the
26916 * explicit number rule for "3" matches the number 3. There are examples of plural categories
26917 * and explicit number rules throughout the rest of this documentation.
26919 * # Configuring ngPluralize
26920 * You configure ngPluralize by providing 2 attributes: `count` and `when`.
26921 * You can also provide an optional attribute, `offset`.
26923 * The value of the `count` attribute can be either a string or an {@link guide/expression
26924 * Angular expression}; these are evaluated on the current scope for its bound value.
26926 * The `when` attribute specifies the mappings between plural categories and the actual
26927 * string to be displayed. The value of the attribute should be a JSON object.
26929 * The following example shows how to configure ngPluralize:
26932 * <ng-pluralize count="personCount"
26933 when="{'0': 'Nobody is viewing.',
26934 * 'one': '1 person is viewing.',
26935 * 'other': '{} people are viewing.'}">
26939 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not
26940 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing"
26941 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for
26942 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can
26943 * show "a dozen people are viewing".
26945 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted
26946 * into pluralized strings. In the previous example, Angular will replace `{}` with
26947 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
26948 * for <span ng-non-bindable>{{numberExpression}}</span>.
26950 * If no rule is defined for a category, then an empty string is displayed and a warning is generated.
26951 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`.
26953 * # Configuring ngPluralize with offset
26954 * The `offset` attribute allows further customization of pluralized text, which can result in
26955 * a better user experience. For example, instead of the message "4 people are viewing this document",
26956 * you might display "John, Kate and 2 others are viewing this document".
26957 * The offset attribute allows you to offset a number by any desired value.
26958 * Let's take a look at an example:
26961 * <ng-pluralize count="personCount" offset=2
26962 * when="{'0': 'Nobody is viewing.',
26963 * '1': '{{person1}} is viewing.',
26964 * '2': '{{person1}} and {{person2}} are viewing.',
26965 * 'one': '{{person1}}, {{person2}} and one other person are viewing.',
26966 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
26970 * Notice that we are still using two plural categories(one, other), but we added
26971 * three explicit number rules 0, 1 and 2.
26972 * When one person, perhaps John, views the document, "John is viewing" will be shown.
26973 * When three people view the document, no explicit number rule is found, so
26974 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category.
26975 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing"
26978 * Note that when you specify offsets, you must provide explicit number rules for
26979 * numbers from 0 up to and including the offset. If you use an offset of 3, for example,
26980 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for
26981 * plural categories "one" and "other".
26983 * @param {string|expression} count The variable to be bound to.
26984 * @param {string} when The mapping between plural category to its corresponding strings.
26985 * @param {number=} offset Offset to deduct from the total number.
26988 <example module="pluralizeExample">
26989 <file name="index.html">
26991 angular.module('pluralizeExample', [])
26992 .controller('ExampleController', ['$scope', function($scope) {
26993 $scope.person1 = 'Igor';
26994 $scope.person2 = 'Misko';
26995 $scope.personCount = 1;
26998 <div ng-controller="ExampleController">
26999 <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/>
27000 <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/>
27001 <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/>
27003 <!--- Example with simple pluralization rules for en locale --->
27005 <ng-pluralize count="personCount"
27006 when="{'0': 'Nobody is viewing.',
27007 'one': '1 person is viewing.',
27008 'other': '{} people are viewing.'}">
27009 </ng-pluralize><br>
27011 <!--- Example with offset --->
27013 <ng-pluralize count="personCount" offset=2
27014 when="{'0': 'Nobody is viewing.',
27015 '1': '{{person1}} is viewing.',
27016 '2': '{{person1}} and {{person2}} are viewing.',
27017 'one': '{{person1}}, {{person2}} and one other person are viewing.',
27018 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
27022 <file name="protractor.js" type="protractor">
27023 it('should show correct pluralized string', function() {
27024 var withoutOffset = element.all(by.css('ng-pluralize')).get(0);
27025 var withOffset = element.all(by.css('ng-pluralize')).get(1);
27026 var countInput = element(by.model('personCount'));
27028 expect(withoutOffset.getText()).toEqual('1 person is viewing.');
27029 expect(withOffset.getText()).toEqual('Igor is viewing.');
27031 countInput.clear();
27032 countInput.sendKeys('0');
27034 expect(withoutOffset.getText()).toEqual('Nobody is viewing.');
27035 expect(withOffset.getText()).toEqual('Nobody is viewing.');
27037 countInput.clear();
27038 countInput.sendKeys('2');
27040 expect(withoutOffset.getText()).toEqual('2 people are viewing.');
27041 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.');
27043 countInput.clear();
27044 countInput.sendKeys('3');
27046 expect(withoutOffset.getText()).toEqual('3 people are viewing.');
27047 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.');
27049 countInput.clear();
27050 countInput.sendKeys('4');
27052 expect(withoutOffset.getText()).toEqual('4 people are viewing.');
27053 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.');
27055 it('should show data-bound names', function() {
27056 var withOffset = element.all(by.css('ng-pluralize')).get(1);
27057 var personCount = element(by.model('personCount'));
27058 var person1 = element(by.model('person1'));
27059 var person2 = element(by.model('person2'));
27060 personCount.clear();
27061 personCount.sendKeys('4');
27063 person1.sendKeys('Di');
27065 person2.sendKeys('Vojta');
27066 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.');
27071 var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) {
27073 IS_WHEN = /^when(Minus)?(.+)$/;
27076 link: function(scope, element, attr) {
27077 var numberExp = attr.count,
27078 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs
27079 offset = attr.offset || 0,
27080 whens = scope.$eval(whenExp) || {},
27082 startSymbol = $interpolate.startSymbol(),
27083 endSymbol = $interpolate.endSymbol(),
27084 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol,
27085 watchRemover = angular.noop,
27088 forEach(attr, function(expression, attributeName) {
27089 var tmpMatch = IS_WHEN.exec(attributeName);
27091 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]);
27092 whens[whenKey] = element.attr(attr.$attr[attributeName]);
27095 forEach(whens, function(expression, key) {
27096 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement));
27100 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) {
27101 var count = parseFloat(newVal);
27102 var countIsNaN = isNaN(count);
27104 if (!countIsNaN && !(count in whens)) {
27105 // If an explicit number rule such as 1, 2, 3... is defined, just use it.
27106 // Otherwise, check it against pluralization rules in $locale service.
27107 count = $locale.pluralCat(count - offset);
27110 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch.
27111 // In JS `NaN !== NaN`, so we have to exlicitly check.
27112 if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) {
27114 var whenExpFn = whensExpFns[count];
27115 if (isUndefined(whenExpFn)) {
27116 if (newVal != null) {
27117 $log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp);
27119 watchRemover = noop;
27120 updateElementText();
27122 watchRemover = scope.$watch(whenExpFn, updateElementText);
27128 function updateElementText(newText) {
27129 element.text(newText || '');
27141 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template
27142 * instance gets its own scope, where the given loop variable is set to the current collection item,
27143 * and `$index` is set to the item index or key.
27145 * Special properties are exposed on the local scope of each template instance, including:
27147 * | Variable | Type | Details |
27148 * |-----------|-----------------|-----------------------------------------------------------------------------|
27149 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) |
27150 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. |
27151 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. |
27152 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. |
27153 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). |
27154 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). |
27156 * <div class="alert alert-info">
27157 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
27158 * This may be useful when, for instance, nesting ngRepeats.
27162 * # Iterating over object properties
27164 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following
27168 * <div ng-repeat="(key, value) in myObj"> ... </div>
27171 * You need to be aware that the JavaScript specification does not define the order of keys
27172 * returned for an object. (To mitigate this in Angular 1.3 the `ngRepeat` directive
27173 * used to sort the keys alphabetically.)
27175 * Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser
27176 * when running `for key in myObj`. It seems that browsers generally follow the strategy of providing
27177 * keys in the order in which they were defined, although there are exceptions when keys are deleted
27178 * 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).
27180 * If this is not desired, the recommended workaround is to convert your object into an array
27181 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
27182 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter)
27183 * or implement a `$watch` on the object yourself.
27186 * # Tracking and Duplicates
27188 * `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in
27189 * the collection. When a change happens, ngRepeat then makes the corresponding changes to the DOM:
27191 * * When an item is added, a new instance of the template is added to the DOM.
27192 * * When an item is removed, its template instance is removed from the DOM.
27193 * * When items are reordered, their respective templates are reordered in the DOM.
27195 * To minimize creation of DOM elements, `ngRepeat` uses a function
27196 * to "keep track" of all items in the collection and their corresponding DOM elements.
27197 * For example, if an item is added to the collection, ngRepeat will know that all other items
27198 * already have DOM elements, and will not re-render them.
27200 * The default tracking function (which tracks items by their identity) does not allow
27201 * duplicate items in arrays. This is because when there are duplicates, it is not possible
27202 * to maintain a one-to-one mapping between collection items and DOM elements.
27204 * If you do need to repeat duplicate items, you can substitute the default tracking behavior
27205 * with your own using the `track by` expression.
27207 * For example, you may track items by the index of each item in the collection, using the
27208 * special scope property `$index`:
27210 * <div ng-repeat="n in [42, 42, 43, 43] track by $index">
27215 * You may also use arbitrary expressions in `track by`, including references to custom functions
27218 * <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)">
27223 * <div class="alert alert-success">
27224 * If you are working with objects that have an identifier property, you should track
27225 * by the identifier instead of the whole object. Should you reload your data later, `ngRepeat`
27226 * will not have to rebuild the DOM elements for items it has already rendered, even if the
27227 * JavaScript objects in the collection have been substituted for new ones. For large collections,
27228 * this signifincantly improves rendering performance. If you don't have a unique identifier,
27229 * `track by $index` can also provide a performance boost.
27232 * <div ng-repeat="model in collection track by model.id">
27237 * When no `track by` expression is provided, it is equivalent to tracking by the built-in
27238 * `$id` function, which tracks items by their identity:
27240 * <div ng-repeat="obj in collection track by $id(obj)">
27245 * <div class="alert alert-warning">
27246 * **Note:** `track by` must always be the last expression:
27249 * <div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id">
27254 * # Special repeat start and end points
27255 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
27256 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively.
27257 * 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)
27258 * up to and including the ending HTML tag where **ng-repeat-end** is placed.
27260 * The example below makes use of this feature:
27262 * <header ng-repeat-start="item in items">
27263 * Header {{ item }}
27265 * <div class="body">
27268 * <footer ng-repeat-end>
27269 * Footer {{ item }}
27273 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to:
27278 * <div class="body">
27287 * <div class="body">
27295 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such
27296 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**).
27299 * **.enter** - when a new item is added to the list or when an item is revealed after a filter
27301 * **.leave** - when an item is removed from the list or when an item is filtered out
27303 * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered
27308 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These
27309 * formats are currently supported:
27311 * * `variable in expression` – where variable is the user defined loop variable and `expression`
27312 * is a scope expression giving the collection to enumerate.
27314 * For example: `album in artist.albums`.
27316 * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers,
27317 * and `expression` is the scope expression giving the collection to enumerate.
27319 * For example: `(name, age) in {'adam':10, 'amalie':12}`.
27321 * * `variable in expression track by tracking_expression` – You can also provide an optional tracking expression
27322 * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression
27323 * is specified, ng-repeat associates elements by identity. It is an error to have
27324 * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are
27325 * mapped to the same DOM element, which is not possible.)
27327 * Note that the tracking expression must come last, after any filters, and the alias expression.
27329 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements
27330 * will be associated by item identity in the array.
27332 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique
27333 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements
27334 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM
27335 * element in the same way in the DOM.
27337 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this
27338 * case the object identity does not matter. Two objects are considered equivalent as long as their `id`
27339 * property is same.
27341 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter
27342 * to items in conjunction with a tracking expression.
27344 * * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the
27345 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message
27346 * when a filter is active on the repeater, but the filtered result set is empty.
27348 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after
27349 * the items have been processed through the filter.
27351 * 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
27352 * (and not as operator, inside an expression).
27354 * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` .
27357 * This example initializes the scope to a list of names and
27358 * then uses `ngRepeat` to display every person:
27359 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27360 <file name="index.html">
27361 <div ng-init="friends = [
27362 {name:'John', age:25, gender:'boy'},
27363 {name:'Jessie', age:30, gender:'girl'},
27364 {name:'Johanna', age:28, gender:'girl'},
27365 {name:'Joy', age:15, gender:'girl'},
27366 {name:'Mary', age:28, gender:'girl'},
27367 {name:'Peter', age:95, gender:'boy'},
27368 {name:'Sebastian', age:50, gender:'boy'},
27369 {name:'Erika', age:27, gender:'girl'},
27370 {name:'Patrick', age:40, gender:'boy'},
27371 {name:'Samantha', age:60, gender:'girl'}
27373 I have {{friends.length}} friends. They are:
27374 <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" />
27375 <ul class="example-animate-container">
27376 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results">
27377 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
27379 <li class="animate-repeat" ng-if="results.length == 0">
27380 <strong>No results found...</strong>
27385 <file name="animations.css">
27386 .example-animate-container {
27388 border:1px solid black;
27397 box-sizing:border-box;
27400 .animate-repeat.ng-move,
27401 .animate-repeat.ng-enter,
27402 .animate-repeat.ng-leave {
27403 transition:all linear 0.5s;
27406 .animate-repeat.ng-leave.ng-leave-active,
27407 .animate-repeat.ng-move,
27408 .animate-repeat.ng-enter {
27413 .animate-repeat.ng-leave,
27414 .animate-repeat.ng-move.ng-move-active,
27415 .animate-repeat.ng-enter.ng-enter-active {
27420 <file name="protractor.js" type="protractor">
27421 var friends = element.all(by.repeater('friend in friends'));
27423 it('should render initial data set', function() {
27424 expect(friends.count()).toBe(10);
27425 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.');
27426 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.');
27427 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.');
27428 expect(element(by.binding('friends.length')).getText())
27429 .toMatch("I have 10 friends. They are:");
27432 it('should update repeater when filter predicate changes', function() {
27433 expect(friends.count()).toBe(10);
27435 element(by.model('q')).sendKeys('ma');
27437 expect(friends.count()).toBe(2);
27438 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.');
27439 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.');
27444 var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
27445 var NG_REMOVED = '$$NG_REMOVED';
27446 var ngRepeatMinErr = minErr('ngRepeat');
27448 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) {
27449 // TODO(perf): generate setters to shave off ~40ms or 1-1.5%
27450 scope[valueIdentifier] = value;
27451 if (keyIdentifier) scope[keyIdentifier] = key;
27452 scope.$index = index;
27453 scope.$first = (index === 0);
27454 scope.$last = (index === (arrayLength - 1));
27455 scope.$middle = !(scope.$first || scope.$last);
27456 // jshint bitwise: false
27457 scope.$odd = !(scope.$even = (index&1) === 0);
27458 // jshint bitwise: true
27461 var getBlockStart = function(block) {
27462 return block.clone[0];
27465 var getBlockEnd = function(block) {
27466 return block.clone[block.clone.length - 1];
27472 multiElement: true,
27473 transclude: 'element',
27477 compile: function ngRepeatCompile($element, $attr) {
27478 var expression = $attr.ngRepeat;
27479 var ngRepeatEndComment = document.createComment(' end ngRepeat: ' + expression + ' ');
27481 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*$/);
27484 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.",
27488 var lhs = match[1];
27489 var rhs = match[2];
27490 var aliasAs = match[3];
27491 var trackByExp = match[4];
27493 match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);
27496 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.",
27499 var valueIdentifier = match[3] || match[1];
27500 var keyIdentifier = match[2];
27502 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) ||
27503 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) {
27504 throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.",
27508 var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn;
27509 var hashFnLocals = {$id: hashKey};
27512 trackByExpGetter = $parse(trackByExp);
27514 trackByIdArrayFn = function(key, value) {
27515 return hashKey(value);
27517 trackByIdObjFn = function(key) {
27522 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) {
27524 if (trackByExpGetter) {
27525 trackByIdExpFn = function(key, value, index) {
27526 // assign key, value, and $index to the locals so that they can be used in hash functions
27527 if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
27528 hashFnLocals[valueIdentifier] = value;
27529 hashFnLocals.$index = index;
27530 return trackByExpGetter($scope, hashFnLocals);
27534 // Store a list of elements from previous run. This is a hash where key is the item from the
27535 // iterator, and the value is objects with following properties.
27536 // - scope: bound scope
27537 // - element: previous element.
27538 // - index: position
27540 // We are using no-proto object so that we don't need to guard against inherited props via
27542 var lastBlockMap = createMap();
27545 $scope.$watchCollection(rhs, function ngRepeatAction(collection) {
27547 previousNode = $element[0], // node that cloned nodes should be inserted after
27548 // initialized to the comment node anchor
27550 // Same as lastBlockMap but it has the current state. It will become the
27551 // lastBlockMap on the next iteration.
27552 nextBlockMap = createMap(),
27554 key, value, // key/value of iteration
27558 block, // last object information {scope, element, id}
27563 $scope[aliasAs] = collection;
27566 if (isArrayLike(collection)) {
27567 collectionKeys = collection;
27568 trackByIdFn = trackByIdExpFn || trackByIdArrayFn;
27570 trackByIdFn = trackByIdExpFn || trackByIdObjFn;
27571 // if object, extract keys, in enumeration order, unsorted
27572 collectionKeys = [];
27573 for (var itemKey in collection) {
27574 if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') {
27575 collectionKeys.push(itemKey);
27580 collectionLength = collectionKeys.length;
27581 nextBlockOrder = new Array(collectionLength);
27583 // locate existing items
27584 for (index = 0; index < collectionLength; index++) {
27585 key = (collection === collectionKeys) ? index : collectionKeys[index];
27586 value = collection[key];
27587 trackById = trackByIdFn(key, value, index);
27588 if (lastBlockMap[trackById]) {
27589 // found previously seen block
27590 block = lastBlockMap[trackById];
27591 delete lastBlockMap[trackById];
27592 nextBlockMap[trackById] = block;
27593 nextBlockOrder[index] = block;
27594 } else if (nextBlockMap[trackById]) {
27595 // if collision detected. restore lastBlockMap and throw an error
27596 forEach(nextBlockOrder, function(block) {
27597 if (block && block.scope) lastBlockMap[block.id] = block;
27599 throw ngRepeatMinErr('dupes',
27600 "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}",
27601 expression, trackById, value);
27603 // new never before seen block
27604 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined};
27605 nextBlockMap[trackById] = true;
27609 // remove leftover items
27610 for (var blockKey in lastBlockMap) {
27611 block = lastBlockMap[blockKey];
27612 elementsToRemove = getBlockNodes(block.clone);
27613 $animate.leave(elementsToRemove);
27614 if (elementsToRemove[0].parentNode) {
27615 // if the element was not removed yet because of pending animation, mark it as deleted
27616 // so that we can ignore it later
27617 for (index = 0, length = elementsToRemove.length; index < length; index++) {
27618 elementsToRemove[index][NG_REMOVED] = true;
27621 block.scope.$destroy();
27624 // we are not using forEach for perf reasons (trying to avoid #call)
27625 for (index = 0; index < collectionLength; index++) {
27626 key = (collection === collectionKeys) ? index : collectionKeys[index];
27627 value = collection[key];
27628 block = nextBlockOrder[index];
27631 // if we have already seen this object, then we need to reuse the
27632 // associated scope/element
27634 nextNode = previousNode;
27636 // skip nodes that are already pending removal via leave animation
27638 nextNode = nextNode.nextSibling;
27639 } while (nextNode && nextNode[NG_REMOVED]);
27641 if (getBlockStart(block) != nextNode) {
27642 // existing item which got moved
27643 $animate.move(getBlockNodes(block.clone), null, jqLite(previousNode));
27645 previousNode = getBlockEnd(block);
27646 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
27648 // new item which we don't know about
27649 $transclude(function ngRepeatTransclude(clone, scope) {
27650 block.scope = scope;
27651 // http://jsperf.com/clone-vs-createcomment
27652 var endNode = ngRepeatEndComment.cloneNode(false);
27653 clone[clone.length++] = endNode;
27655 // TODO(perf): support naked previousNode in `enter` to avoid creation of jqLite wrapper?
27656 $animate.enter(clone, null, jqLite(previousNode));
27657 previousNode = endNode;
27658 // Note: We only need the first/last node of the cloned nodes.
27659 // However, we need to keep the reference to the jqlite wrapper as it might be changed later
27660 // by a directive with templateUrl when its template arrives.
27661 block.clone = clone;
27662 nextBlockMap[block.id] = block;
27663 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
27667 lastBlockMap = nextBlockMap;
27674 var NG_HIDE_CLASS = 'ng-hide';
27675 var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
27682 * The `ngShow` directive shows or hides the given HTML element based on the expression
27683 * provided to the `ngShow` attribute. The element is shown or hidden by removing or adding
27684 * the `.ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined
27685 * in AngularJS and sets the display style to none (using an !important flag).
27686 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
27689 * <!-- when $scope.myValue is truthy (element is visible) -->
27690 * <div ng-show="myValue"></div>
27692 * <!-- when $scope.myValue is falsy (element is hidden) -->
27693 * <div ng-show="myValue" class="ng-hide"></div>
27696 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added to the class
27697 * attribute on the element causing it to become hidden. When truthy, the `.ng-hide` CSS class is removed
27698 * from the element causing the element not to appear hidden.
27700 * ## Why is !important used?
27702 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector
27703 * can be easily overridden by heavier selectors. For example, something as simple
27704 * as changing the display style on a HTML list item would make hidden elements appear visible.
27705 * This also becomes a bigger issue when dealing with CSS frameworks.
27707 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector
27708 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the
27709 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code.
27711 * ### Overriding `.ng-hide`
27713 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
27714 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
27715 * class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope
27716 * with extra animation classes that can be added.
27719 * .ng-hide:not(.ng-hide-animate) {
27720 * /* this is just another form of hiding an element */
27721 * display: block!important;
27722 * position: absolute;
27728 * By default you don't need to override in CSS anything and the animations will work around the display style.
27730 * ## A note about animations with `ngShow`
27732 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
27733 * is true and false. This system works like the animation system present with ngClass except that
27734 * you must also include the !important flag to override the display property
27735 * so that you can perform an animation when the element is hidden during the time of the animation.
27739 * //a working example can be found at the bottom of this page
27741 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
27742 * /* this is required as of 1.3x to properly
27743 * apply all styling in a show/hide animation */
27744 * transition: 0s linear all;
27747 * .my-element.ng-hide-add-active,
27748 * .my-element.ng-hide-remove-active {
27749 * /* the transition is defined in the active class */
27750 * transition: 1s linear all;
27753 * .my-element.ng-hide-add { ... }
27754 * .my-element.ng-hide-add.ng-hide-add-active { ... }
27755 * .my-element.ng-hide-remove { ... }
27756 * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
27759 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display
27760 * property to block during animation states--ngAnimate will handle the style toggling automatically for you.
27763 * addClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a truthy value and the just before contents are set to visible
27764 * removeClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden
27767 * @param {expression} ngShow If the {@link guide/expression expression} is truthy
27768 * then the element is shown or hidden respectively.
27771 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27772 <file name="index.html">
27773 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br/>
27776 <div class="check-element animate-show" ng-show="checked">
27777 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked.
27782 <div class="check-element animate-show" ng-hide="checked">
27783 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked.
27787 <file name="glyphicons.css">
27788 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
27790 <file name="animations.css">
27795 border: 1px solid black;
27799 .animate-show.ng-hide-add, .animate-show.ng-hide-remove {
27800 transition: all linear 0.5s;
27803 .animate-show.ng-hide {
27811 border: 1px solid black;
27815 <file name="protractor.js" type="protractor">
27816 var thumbsUp = element(by.css('span.glyphicon-thumbs-up'));
27817 var thumbsDown = element(by.css('span.glyphicon-thumbs-down'));
27819 it('should check ng-show / ng-hide', function() {
27820 expect(thumbsUp.isDisplayed()).toBeFalsy();
27821 expect(thumbsDown.isDisplayed()).toBeTruthy();
27823 element(by.model('checked')).click();
27825 expect(thumbsUp.isDisplayed()).toBeTruthy();
27826 expect(thumbsDown.isDisplayed()).toBeFalsy();
27831 var ngShowDirective = ['$animate', function($animate) {
27834 multiElement: true,
27835 link: function(scope, element, attr) {
27836 scope.$watch(attr.ngShow, function ngShowWatchAction(value) {
27837 // we're adding a temporary, animation-specific class for ng-hide since this way
27838 // we can control when the element is actually displayed on screen without having
27839 // to have a global/greedy CSS selector that breaks when other animations are run.
27840 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845
27841 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, {
27842 tempClasses: NG_HIDE_IN_PROGRESS_CLASS
27856 * The `ngHide` directive shows or hides the given HTML element based on the expression
27857 * provided to the `ngHide` attribute. The element is shown or hidden by removing or adding
27858 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined
27859 * in AngularJS and sets the display style to none (using an !important flag).
27860 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
27863 * <!-- when $scope.myValue is truthy (element is hidden) -->
27864 * <div ng-hide="myValue" class="ng-hide"></div>
27866 * <!-- when $scope.myValue is falsy (element is visible) -->
27867 * <div ng-hide="myValue"></div>
27870 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added to the class
27871 * attribute on the element causing it to become hidden. When falsy, the `.ng-hide` CSS class is removed
27872 * from the element causing the element not to appear hidden.
27874 * ## Why is !important used?
27876 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector
27877 * can be easily overridden by heavier selectors. For example, something as simple
27878 * as changing the display style on a HTML list item would make hidden elements appear visible.
27879 * This also becomes a bigger issue when dealing with CSS frameworks.
27881 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector
27882 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the
27883 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code.
27885 * ### Overriding `.ng-hide`
27887 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
27888 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
27893 * /* this is just another form of hiding an element */
27894 * display: block!important;
27895 * position: absolute;
27901 * By default you don't need to override in CSS anything and the animations will work around the display style.
27903 * ## A note about animations with `ngHide`
27905 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
27906 * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide`
27907 * CSS class is added and removed for you instead of your own CSS class.
27911 * //a working example can be found at the bottom of this page
27913 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
27914 * transition: 0.5s linear all;
27917 * .my-element.ng-hide-add { ... }
27918 * .my-element.ng-hide-add.ng-hide-add-active { ... }
27919 * .my-element.ng-hide-remove { ... }
27920 * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
27923 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display
27924 * property to block during animation states--ngAnimate will handle the style toggling automatically for you.
27927 * removeClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden
27928 * addClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a non truthy value and just before the contents are set to visible
27931 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then
27932 * the element is shown or hidden respectively.
27935 <example module="ngAnimate" deps="angular-animate.js" animations="true">
27936 <file name="index.html">
27937 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br/>
27940 <div class="check-element animate-hide" ng-show="checked">
27941 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked.
27946 <div class="check-element animate-hide" ng-hide="checked">
27947 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked.
27951 <file name="glyphicons.css">
27952 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
27954 <file name="animations.css">
27956 transition: all linear 0.5s;
27960 border: 1px solid black;
27964 .animate-hide.ng-hide {
27972 border: 1px solid black;
27976 <file name="protractor.js" type="protractor">
27977 var thumbsUp = element(by.css('span.glyphicon-thumbs-up'));
27978 var thumbsDown = element(by.css('span.glyphicon-thumbs-down'));
27980 it('should check ng-show / ng-hide', function() {
27981 expect(thumbsUp.isDisplayed()).toBeFalsy();
27982 expect(thumbsDown.isDisplayed()).toBeTruthy();
27984 element(by.model('checked')).click();
27986 expect(thumbsUp.isDisplayed()).toBeTruthy();
27987 expect(thumbsDown.isDisplayed()).toBeFalsy();
27992 var ngHideDirective = ['$animate', function($animate) {
27995 multiElement: true,
27996 link: function(scope, element, attr) {
27997 scope.$watch(attr.ngHide, function ngHideWatchAction(value) {
27998 // The comment inside of the ngShowDirective explains why we add and
27999 // remove a temporary class for the show/hide animation
28000 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, {
28001 tempClasses: NG_HIDE_IN_PROGRESS_CLASS
28014 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally.
28017 * @param {expression} ngStyle
28019 * {@link guide/expression Expression} which evals to an
28020 * object whose keys are CSS style names and values are corresponding values for those CSS
28023 * Since some CSS style names are not valid keys for an object, they must be quoted.
28024 * See the 'background-color' style in the example below.
28028 <file name="index.html">
28029 <input type="button" value="set color" ng-click="myStyle={color:'red'}">
28030 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}">
28031 <input type="button" value="clear" ng-click="myStyle={}">
28033 <span ng-style="myStyle">Sample Text</span>
28034 <pre>myStyle={{myStyle}}</pre>
28036 <file name="style.css">
28041 <file name="protractor.js" type="protractor">
28042 var colorSpan = element(by.css('span'));
28044 it('should check ng-style', function() {
28045 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
28046 element(by.css('input[value=\'set color\']')).click();
28047 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)');
28048 element(by.css('input[value=clear]')).click();
28049 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
28054 var ngStyleDirective = ngDirective(function(scope, element, attr) {
28055 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
28056 if (oldStyles && (newStyles !== oldStyles)) {
28057 forEach(oldStyles, function(val, style) { element.css(style, '');});
28059 if (newStyles) element.css(newStyles);
28069 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression.
28070 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location
28071 * as specified in the template.
28073 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it
28074 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element
28075 * matches the value obtained from the evaluated expression. In other words, you define a container element
28076 * (where you place the directive), place an expression on the **`on="..."` attribute**
28077 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place
28078 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on
28079 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default
28080 * attribute is displayed.
28082 * <div class="alert alert-info">
28083 * Be aware that the attribute values to match against cannot be expressions. They are interpreted
28084 * as literal string values to match against.
28085 * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the
28086 * value of the expression `$scope.someVal`.
28090 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container
28091 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM
28096 * <ANY ng-switch="expression">
28097 * <ANY ng-switch-when="matchValue1">...</ANY>
28098 * <ANY ng-switch-when="matchValue2">...</ANY>
28099 * <ANY ng-switch-default>...</ANY>
28106 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>.
28107 * On child elements add:
28109 * * `ngSwitchWhen`: the case statement to match against. If match then this
28110 * case will be displayed. If the same match appears multiple times, all the
28111 * elements will be displayed.
28112 * * `ngSwitchDefault`: the default case when no other case match. If there
28113 * are multiple default cases, all of them will be displayed when no other
28118 <example module="switchExample" deps="angular-animate.js" animations="true">
28119 <file name="index.html">
28120 <div ng-controller="ExampleController">
28121 <select ng-model="selection" ng-options="item for item in items">
28123 <code>selection={{selection}}</code>
28125 <div class="animate-switch-container"
28126 ng-switch on="selection">
28127 <div class="animate-switch" ng-switch-when="settings">Settings Div</div>
28128 <div class="animate-switch" ng-switch-when="home">Home Span</div>
28129 <div class="animate-switch" ng-switch-default>default</div>
28133 <file name="script.js">
28134 angular.module('switchExample', ['ngAnimate'])
28135 .controller('ExampleController', ['$scope', function($scope) {
28136 $scope.items = ['settings', 'home', 'other'];
28137 $scope.selection = $scope.items[0];
28140 <file name="animations.css">
28141 .animate-switch-container {
28144 border:1px solid black;
28153 .animate-switch.ng-animate {
28154 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
28163 .animate-switch.ng-leave.ng-leave-active,
28164 .animate-switch.ng-enter {
28167 .animate-switch.ng-leave,
28168 .animate-switch.ng-enter.ng-enter-active {
28172 <file name="protractor.js" type="protractor">
28173 var switchElem = element(by.css('[ng-switch]'));
28174 var select = element(by.model('selection'));
28176 it('should start in settings', function() {
28177 expect(switchElem.getText()).toMatch(/Settings Div/);
28179 it('should change to home', function() {
28180 select.all(by.css('option')).get(1).click();
28181 expect(switchElem.getText()).toMatch(/Home Span/);
28183 it('should select default', function() {
28184 select.all(by.css('option')).get(2).click();
28185 expect(switchElem.getText()).toMatch(/default/);
28190 var ngSwitchDirective = ['$animate', function($animate) {
28192 require: 'ngSwitch',
28194 // asks for $scope to fool the BC controller module
28195 controller: ['$scope', function ngSwitchController() {
28198 link: function(scope, element, attr, ngSwitchController) {
28199 var watchExpr = attr.ngSwitch || attr.on,
28200 selectedTranscludes = [],
28201 selectedElements = [],
28202 previousLeaveAnimations = [],
28203 selectedScopes = [];
28205 var spliceFactory = function(array, index) {
28206 return function() { array.splice(index, 1); };
28209 scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
28211 for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) {
28212 $animate.cancel(previousLeaveAnimations[i]);
28214 previousLeaveAnimations.length = 0;
28216 for (i = 0, ii = selectedScopes.length; i < ii; ++i) {
28217 var selected = getBlockNodes(selectedElements[i].clone);
28218 selectedScopes[i].$destroy();
28219 var promise = previousLeaveAnimations[i] = $animate.leave(selected);
28220 promise.then(spliceFactory(previousLeaveAnimations, i));
28223 selectedElements.length = 0;
28224 selectedScopes.length = 0;
28226 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) {
28227 forEach(selectedTranscludes, function(selectedTransclude) {
28228 selectedTransclude.transclude(function(caseElement, selectedScope) {
28229 selectedScopes.push(selectedScope);
28230 var anchor = selectedTransclude.element;
28231 caseElement[caseElement.length++] = document.createComment(' end ngSwitchWhen: ');
28232 var block = { clone: caseElement };
28234 selectedElements.push(block);
28235 $animate.enter(caseElement, anchor.parent(), anchor);
28244 var ngSwitchWhenDirective = ngDirective({
28245 transclude: 'element',
28247 require: '^ngSwitch',
28248 multiElement: true,
28249 link: function(scope, element, attrs, ctrl, $transclude) {
28250 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
28251 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element });
28255 var ngSwitchDefaultDirective = ngDirective({
28256 transclude: 'element',
28258 require: '^ngSwitch',
28259 multiElement: true,
28260 link: function(scope, element, attr, ctrl, $transclude) {
28261 ctrl.cases['?'] = (ctrl.cases['?'] || []);
28262 ctrl.cases['?'].push({ transclude: $transclude, element: element });
28268 * @name ngTransclude
28272 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.
28274 * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted.
28279 <example module="transcludeExample">
28280 <file name="index.html">
28282 angular.module('transcludeExample', [])
28283 .directive('pane', function(){
28287 scope: { title:'@' },
28288 template: '<div style="border: 1px solid black;">' +
28289 '<div style="background-color: gray">{{title}}</div>' +
28290 '<ng-transclude></ng-transclude>' +
28294 .controller('ExampleController', ['$scope', function($scope) {
28295 $scope.title = 'Lorem Ipsum';
28296 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
28299 <div ng-controller="ExampleController">
28300 <input ng-model="title" aria-label="title"> <br/>
28301 <textarea ng-model="text" aria-label="text"></textarea> <br/>
28302 <pane title="{{title}}">{{text}}</pane>
28305 <file name="protractor.js" type="protractor">
28306 it('should have transcluded', function() {
28307 var titleElement = element(by.model('title'));
28308 titleElement.clear();
28309 titleElement.sendKeys('TITLE');
28310 var textElement = element(by.model('text'));
28311 textElement.clear();
28312 textElement.sendKeys('TEXT');
28313 expect(element(by.binding('title')).getText()).toEqual('TITLE');
28314 expect(element(by.binding('text')).getText()).toEqual('TEXT');
28320 var ngTranscludeDirective = ngDirective({
28322 link: function($scope, $element, $attrs, controller, $transclude) {
28323 if (!$transclude) {
28324 throw minErr('ngTransclude')('orphan',
28325 'Illegal use of ngTransclude directive in the template! ' +
28326 'No parent directive that requires a transclusion found. ' +
28328 startingTag($element));
28331 $transclude(function(clone) {
28333 $element.append(clone);
28344 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the
28345 * template can be used by {@link ng.directive:ngInclude `ngInclude`},
28346 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the
28347 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be
28348 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`.
28350 * @param {string} type Must be set to `'text/ng-template'`.
28351 * @param {string} id Cache name of the template.
28355 <file name="index.html">
28356 <script type="text/ng-template" id="/tpl.html">
28357 Content of the template.
28360 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a>
28361 <div id="tpl-content" ng-include src="currentTpl"></div>
28363 <file name="protractor.js" type="protractor">
28364 it('should load template defined inside script tag', function() {
28365 element(by.css('#tpl-link')).click();
28366 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/);
28371 var scriptDirective = ['$templateCache', function($templateCache) {
28375 compile: function(element, attr) {
28376 if (attr.type == 'text/ng-template') {
28377 var templateUrl = attr.id,
28378 text = element[0].text;
28380 $templateCache.put(templateUrl, text);
28386 var noopNgModelController = { $setViewValue: noop, $render: noop };
28388 function chromeHack(optionElement) {
28389 // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459
28390 // Adding an <option selected="selected"> element to a <select required="required"> should
28391 // automatically select the new element
28392 if (optionElement[0].hasAttribute('selected')) {
28393 optionElement[0].selected = true;
28399 * @name select.SelectController
28401 * The controller for the `<select>` directive. This provides support for reading
28402 * and writing the selected value(s) of the control and also coordinates dynamically
28403 * added `<option>` elements, perhaps by an `ngRepeat` directive.
28405 var SelectController =
28406 ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
28409 optionsMap = new HashMap();
28411 // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors
28412 self.ngModelCtrl = noopNgModelController;
28414 // The "unknown" option is one that is prepended to the list if the viewValue
28415 // does not match any of the options. When it is rendered the value of the unknown
28416 // option is '? XXX ?' where XXX is the hashKey of the value that is not known.
28418 // We can't just jqLite('<option>') since jqLite is not smart enough
28419 // to create it in <select> and IE barfs otherwise.
28420 self.unknownOption = jqLite(document.createElement('option'));
28421 self.renderUnknownOption = function(val) {
28422 var unknownVal = '? ' + hashKey(val) + ' ?';
28423 self.unknownOption.val(unknownVal);
28424 $element.prepend(self.unknownOption);
28425 $element.val(unknownVal);
28428 $scope.$on('$destroy', function() {
28429 // disable unknown option so that we don't do work when the whole select is being destroyed
28430 self.renderUnknownOption = noop;
28433 self.removeUnknownOption = function() {
28434 if (self.unknownOption.parent()) self.unknownOption.remove();
28438 // Read the value of the select control, the implementation of this changes depending
28439 // upon whether the select can have multiple values and whether ngOptions is at work.
28440 self.readValue = function readSingleValue() {
28441 self.removeUnknownOption();
28442 return $element.val();
28446 // Write the value to the select control, the implementation of this changes depending
28447 // upon whether the select can have multiple values and whether ngOptions is at work.
28448 self.writeValue = function writeSingleValue(value) {
28449 if (self.hasOption(value)) {
28450 self.removeUnknownOption();
28451 $element.val(value);
28452 if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy
28454 if (value == null && self.emptyOption) {
28455 self.removeUnknownOption();
28458 self.renderUnknownOption(value);
28464 // Tell the select control that an option, with the given value, has been added
28465 self.addOption = function(value, element) {
28466 assertNotHasOwnProperty(value, '"option value"');
28467 if (value === '') {
28468 self.emptyOption = element;
28470 var count = optionsMap.get(value) || 0;
28471 optionsMap.put(value, count + 1);
28472 self.ngModelCtrl.$render();
28473 chromeHack(element);
28476 // Tell the select control that an option, with the given value, has been removed
28477 self.removeOption = function(value) {
28478 var count = optionsMap.get(value);
28481 optionsMap.remove(value);
28482 if (value === '') {
28483 self.emptyOption = undefined;
28486 optionsMap.put(value, count - 1);
28491 // Check whether the select control has an option matching the given value
28492 self.hasOption = function(value) {
28493 return !!optionsMap.get(value);
28497 self.registerOption = function(optionScope, optionElement, optionAttrs, interpolateValueFn, interpolateTextFn) {
28499 if (interpolateValueFn) {
28500 // The value attribute is interpolated
28502 optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {
28503 if (isDefined(oldVal)) {
28504 self.removeOption(oldVal);
28507 self.addOption(newVal, optionElement);
28509 } else if (interpolateTextFn) {
28510 // The text content is interpolated
28511 optionScope.$watch(interpolateTextFn, function interpolateWatchAction(newVal, oldVal) {
28512 optionAttrs.$set('value', newVal);
28513 if (oldVal !== newVal) {
28514 self.removeOption(oldVal);
28516 self.addOption(newVal, optionElement);
28519 // The value attribute is static
28520 self.addOption(optionAttrs.value, optionElement);
28523 optionElement.on('$destroy', function() {
28524 self.removeOption(optionAttrs.value);
28525 self.ngModelCtrl.$render();
28536 * HTML `SELECT` element with angular data-binding.
28538 * The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding
28539 * between the scope and the `<select>` control (including setting default values).
28540 * Ìt also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or
28541 * {@link ngOptions `ngOptions`} directives.
28543 * When an item in the `<select>` menu is selected, the value of the selected option will be bound
28544 * to the model identified by the `ngModel` directive. With static or repeated options, this is
28545 * the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing.
28546 * If you want dynamic value attributes, you can use interpolation inside the value attribute.
28548 * <div class="alert alert-warning">
28549 * Note that the value of a `select` directive used without `ngOptions` is always a string.
28550 * When the model needs to be bound to a non-string value, you must either explictly convert it
28551 * using a directive (see example below) or use `ngOptions` to specify the set of options.
28552 * This is because an option element can only be bound to string values at present.
28555 * If the viewValue of `ngModel` does not match any of the options, then the control
28556 * will automatically add an "unknown" option, which it then removes when the mismatch is resolved.
28558 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
28559 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
28560 * option. See example below for demonstration.
28562 * <div class="alert alert-info">
28563 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions
28564 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits, such as
28565 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
28566 * comprehension expression, and additionally in reducing memory and increasing speed by not creating
28567 * a new scope for each repeated instance.
28571 * @param {string} ngModel Assignable angular expression to data-bind to.
28572 * @param {string=} name Property name of the form under which the control is published.
28573 * @param {string=} multiple Allows multiple options to be selected. The selected values will be
28574 * bound to the model as an array.
28575 * @param {string=} required Sets `required` validation error key if the value is not entered.
28576 * @param {string=} ngRequired Adds required attribute and required validation constraint to
28577 * the element when the ngRequired expression evaluates to true. Use ngRequired instead of required
28578 * when you want to data-bind to the required attribute.
28579 * @param {string=} ngChange Angular expression to be executed when selected option(s) changes due to user
28580 * interaction with the select element.
28581 * @param {string=} ngOptions sets the options that the select is populated with and defines what is
28582 * set on the model on selection. See {@link ngOptions `ngOptions`}.
28585 * ### Simple `select` elements with static options
28587 * <example name="static-select" module="staticSelect">
28588 * <file name="index.html">
28589 * <div ng-controller="ExampleController">
28590 * <form name="myForm">
28591 * <label for="singleSelect"> Single select: </label><br>
28592 * <select name="singleSelect" ng-model="data.singleSelect">
28593 * <option value="option-1">Option 1</option>
28594 * <option value="option-2">Option 2</option>
28597 * <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br>
28598 * <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect">
28599 * <option value="">---Please select---</option> <!-- not selected / blank option -->
28600 * <option value="{{data.option1}}">Option 1</option> <!-- interpolation -->
28601 * <option value="option-2">Option 2</option>
28603 * <button ng-click="forceUnknownOption()">Force unknown option</button><br>
28604 * <tt>singleSelect = {{data.singleSelect}}</tt>
28607 * <label for="multipleSelect"> Multiple select: </label><br>
28608 * <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple>
28609 * <option value="option-1">Option 1</option>
28610 * <option value="option-2">Option 2</option>
28611 * <option value="option-3">Option 3</option>
28613 * <tt>multipleSelect = {{data.multipleSelect}}</tt><br/>
28617 * <file name="app.js">
28618 * angular.module('staticSelect', [])
28619 * .controller('ExampleController', ['$scope', function($scope) {
28621 * singleSelect: null,
28622 * multipleSelect: [],
28623 * option1: 'option-1',
28626 * $scope.forceUnknownOption = function() {
28627 * $scope.data.singleSelect = 'nonsense';
28633 * ### Using `ngRepeat` to generate `select` options
28634 * <example name="ngrepeat-select" module="ngrepeatSelect">
28635 * <file name="index.html">
28636 * <div ng-controller="ExampleController">
28637 * <form name="myForm">
28638 * <label for="repeatSelect"> Repeat select: </label>
28639 * <select name="repeatSelect" id="repeatSelect" ng-model="data.repeatSelect">
28640 * <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option>
28644 * <tt>repeatSelect = {{data.repeatSelect}}</tt><br/>
28647 * <file name="app.js">
28648 * angular.module('ngrepeatSelect', [])
28649 * .controller('ExampleController', ['$scope', function($scope) {
28651 * repeatSelect: null,
28652 * availableOptions: [
28653 * {id: '1', name: 'Option A'},
28654 * {id: '2', name: 'Option B'},
28655 * {id: '3', name: 'Option C'}
28663 * ### Using `select` with `ngOptions` and setting a default value
28664 * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples.
28666 * <example name="select-with-default-values" module="defaultValueSelect">
28667 * <file name="index.html">
28668 * <div ng-controller="ExampleController">
28669 * <form name="myForm">
28670 * <label for="mySelect">Make a choice:</label>
28671 * <select name="mySelect" id="mySelect"
28672 * ng-options="option.name for option in data.availableOptions track by option.id"
28673 * ng-model="data.selectedOption"></select>
28676 * <tt>option = {{data.selectedOption}}</tt><br/>
28679 * <file name="app.js">
28680 * angular.module('defaultValueSelect', [])
28681 * .controller('ExampleController', ['$scope', function($scope) {
28683 * availableOptions: [
28684 * {id: '1', name: 'Option A'},
28685 * {id: '2', name: 'Option B'},
28686 * {id: '3', name: 'Option C'}
28688 * selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui
28695 * ### Binding `select` to a non-string value via `ngModel` parsing / formatting
28697 * <example name="select-with-non-string-options" module="nonStringSelect">
28698 * <file name="index.html">
28699 * <select ng-model="model.id" convert-to-number>
28700 * <option value="0">Zero</option>
28701 * <option value="1">One</option>
28702 * <option value="2">Two</option>
28706 * <file name="app.js">
28707 * angular.module('nonStringSelect', [])
28708 * .run(function($rootScope) {
28709 * $rootScope.model = { id: 2 };
28711 * .directive('convertToNumber', function() {
28713 * require: 'ngModel',
28714 * link: function(scope, element, attrs, ngModel) {
28715 * ngModel.$parsers.push(function(val) {
28716 * return parseInt(val, 10);
28718 * ngModel.$formatters.push(function(val) {
28725 * <file name="protractor.js" type="protractor">
28726 * it('should initialize to model', function() {
28727 * var select = element(by.css('select'));
28728 * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two');
28734 var selectDirective = function() {
28738 require: ['select', '?ngModel'],
28739 controller: SelectController,
28746 function selectPreLink(scope, element, attr, ctrls) {
28748 // if ngModel is not defined, we don't need to do anything
28749 var ngModelCtrl = ctrls[1];
28750 if (!ngModelCtrl) return;
28752 var selectCtrl = ctrls[0];
28754 selectCtrl.ngModelCtrl = ngModelCtrl;
28756 // We delegate rendering to the `writeValue` method, which can be changed
28757 // if the select can have multiple selected values or if the options are being
28758 // generated by `ngOptions`
28759 ngModelCtrl.$render = function() {
28760 selectCtrl.writeValue(ngModelCtrl.$viewValue);
28763 // When the selected item(s) changes we delegate getting the value of the select control
28764 // to the `readValue` method, which can be changed if the select can have multiple
28765 // selected values or if the options are being generated by `ngOptions`
28766 element.on('change', function() {
28767 scope.$apply(function() {
28768 ngModelCtrl.$setViewValue(selectCtrl.readValue());
28772 // If the select allows multiple values then we need to modify how we read and write
28773 // values from and to the control; also what it means for the value to be empty and
28774 // we have to add an extra watch since ngModel doesn't work well with arrays - it
28775 // doesn't trigger rendering if only an item in the array changes.
28776 if (attr.multiple) {
28778 // Read value now needs to check each option to see if it is selected
28779 selectCtrl.readValue = function readMultipleValue() {
28781 forEach(element.find('option'), function(option) {
28782 if (option.selected) {
28783 array.push(option.value);
28789 // Write value now needs to set the selected property of each matching option
28790 selectCtrl.writeValue = function writeMultipleValue(value) {
28791 var items = new HashMap(value);
28792 forEach(element.find('option'), function(option) {
28793 option.selected = isDefined(items.get(option.value));
28797 // we have to do it on each watch since ngModel watches reference, but
28798 // we need to work of an array, so we need to see if anything was inserted/removed
28799 var lastView, lastViewRef = NaN;
28800 scope.$watch(function selectMultipleWatch() {
28801 if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) {
28802 lastView = shallowCopy(ngModelCtrl.$viewValue);
28803 ngModelCtrl.$render();
28805 lastViewRef = ngModelCtrl.$viewValue;
28808 // If we are a multiple select then value is now a collection
28809 // so the meaning of $isEmpty changes
28810 ngModelCtrl.$isEmpty = function(value) {
28811 return !value || value.length === 0;
28819 // The option directive is purely designed to communicate the existence (or lack of)
28820 // of dynamically created (and destroyed) option elements to their containing select
28821 // directive via its controller.
28822 var optionDirective = ['$interpolate', function($interpolate) {
28826 compile: function(element, attr) {
28828 if (isDefined(attr.value)) {
28829 // If the value attribute is defined, check if it contains an interpolation
28830 var interpolateValueFn = $interpolate(attr.value, true);
28832 // If the value attribute is not defined then we fall back to the
28833 // text content of the option element, which may be interpolated
28834 var interpolateTextFn = $interpolate(element.text(), true);
28835 if (!interpolateTextFn) {
28836 attr.$set('value', element.text());
28840 return function(scope, element, attr) {
28842 // This is an optimization over using ^^ since we don't want to have to search
28843 // all the way to the root of the DOM for every single option element
28844 var selectCtrlName = '$selectController',
28845 parent = element.parent(),
28846 selectCtrl = parent.data(selectCtrlName) ||
28847 parent.parent().data(selectCtrlName); // in case we are in optgroup
28850 selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn);
28857 var styleDirective = valueFn({
28862 var requiredDirective = function() {
28865 require: '?ngModel',
28866 link: function(scope, elm, attr, ctrl) {
28868 attr.required = true; // force truthy in case we are on non input element
28870 ctrl.$validators.required = function(modelValue, viewValue) {
28871 return !attr.required || !ctrl.$isEmpty(viewValue);
28874 attr.$observe('required', function() {
28882 var patternDirective = function() {
28885 require: '?ngModel',
28886 link: function(scope, elm, attr, ctrl) {
28889 var regexp, patternExp = attr.ngPattern || attr.pattern;
28890 attr.$observe('pattern', function(regex) {
28891 if (isString(regex) && regex.length > 0) {
28892 regex = new RegExp('^' + regex + '$');
28895 if (regex && !regex.test) {
28896 throw minErr('ngPattern')('noregexp',
28897 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
28898 regex, startingTag(elm));
28901 regexp = regex || undefined;
28905 ctrl.$validators.pattern = function(modelValue, viewValue) {
28906 // HTML5 pattern constraint validates the input value, so we validate the viewValue
28907 return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue);
28914 var maxlengthDirective = function() {
28917 require: '?ngModel',
28918 link: function(scope, elm, attr, ctrl) {
28921 var maxlength = -1;
28922 attr.$observe('maxlength', function(value) {
28923 var intVal = toInt(value);
28924 maxlength = isNaN(intVal) ? -1 : intVal;
28927 ctrl.$validators.maxlength = function(modelValue, viewValue) {
28928 return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength);
28934 var minlengthDirective = function() {
28937 require: '?ngModel',
28938 link: function(scope, elm, attr, ctrl) {
28942 attr.$observe('minlength', function(value) {
28943 minlength = toInt(value) || 0;
28946 ctrl.$validators.minlength = function(modelValue, viewValue) {
28947 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
28953 if (window.angular.bootstrap) {
28954 //AngularJS is already loaded, so we can return here...
28955 console.log('WARNING: Tried to load angular more than once.');
28959 //try to bind to jquery now so that one can write jqLite(document).ready()
28960 //but we will rebind on bootstrap again.
28963 publishExternalAPI(angular);
28965 angular.module("ngLocale", [], ["$provide", function($provide) {
28966 var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"};
28967 function getDecimals(n) {
28969 var i = n.indexOf('.');
28970 return (i == -1) ? 0 : n.length - i - 1;
28973 function getVF(n, opt_precision) {
28974 var v = opt_precision;
28976 if (undefined === v) {
28977 v = Math.min(getDecimals(n), 3);
28980 var base = Math.pow(10, v);
28981 var f = ((n * base) | 0) % base;
28982 return {v: v, f: f};
28985 $provide.value("$locale", {
28986 "DATETIME_FORMATS": {
29008 "FIRSTDAYOFWEEK": 6,
29050 "fullDate": "EEEE, MMMM d, y",
29051 "longDate": "MMMM d, y",
29052 "medium": "MMM d, y h:mm:ss a",
29053 "mediumDate": "MMM d, y",
29054 "mediumTime": "h:mm:ss a",
29055 "short": "M/d/yy h:mm a",
29056 "shortDate": "M/d/yy",
29057 "shortTime": "h:mm a"
29059 "NUMBER_FORMATS": {
29060 "CURRENCY_SYM": "$",
29061 "DECIMAL_SEP": ".",
29081 "negPre": "-\u00a4",
29083 "posPre": "\u00a4",
29089 "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;}
29093 jqLite(document).ready(function() {
29094 angularInit(document, bootstrap);
29097 })(window, document);
29099 !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>');
29103 /***/ function(module, exports) {
29106 * State-based routing for AngularJS
29108 * @link http://angular-ui.github.com/
29109 * @license MIT License, http://www.opensource.org/licenses/MIT
29112 /* commonjs package manager support (eg componentjs) */
29113 if (typeof module !== "undefined" && typeof exports !== "undefined" && module.exports === exports){
29114 module.exports = 'ui.router';
29117 (function (window, angular, undefined) {
29118 /*jshint globalstrict:true*/
29119 /*global angular:false*/
29122 var isDefined = angular.isDefined,
29123 isFunction = angular.isFunction,
29124 isString = angular.isString,
29125 isObject = angular.isObject,
29126 isArray = angular.isArray,
29127 forEach = angular.forEach,
29128 extend = angular.extend,
29129 copy = angular.copy,
29130 toJson = angular.toJson;
29132 function inherit(parent, extra) {
29133 return extend(new (extend(function() {}, { prototype: parent }))(), extra);
29136 function merge(dst) {
29137 forEach(arguments, function(obj) {
29139 forEach(obj, function(value, key) {
29140 if (!dst.hasOwnProperty(key)) dst[key] = value;
29148 * Finds the common ancestor path between two states.
29150 * @param {Object} first The first state.
29151 * @param {Object} second The second state.
29152 * @return {Array} Returns an array of state names in descending order, not including the root.
29154 function ancestors(first, second) {
29157 for (var n in first.path) {
29158 if (first.path[n] !== second.path[n]) break;
29159 path.push(first.path[n]);
29165 * IE8-safe wrapper for `Object.keys()`.
29167 * @param {Object} object A JavaScript object.
29168 * @return {Array} Returns the keys of the object as an array.
29170 function objectKeys(object) {
29172 return Object.keys(object);
29176 forEach(object, function(val, key) {
29183 * IE8-safe wrapper for `Array.prototype.indexOf()`.
29185 * @param {Array} array A JavaScript array.
29186 * @param {*} value A value to search the array for.
29187 * @return {Number} Returns the array index value of `value`, or `-1` if not present.
29189 function indexOf(array, value) {
29190 if (Array.prototype.indexOf) {
29191 return array.indexOf(value, Number(arguments[2]) || 0);
29193 var len = array.length >>> 0, from = Number(arguments[2]) || 0;
29194 from = (from < 0) ? Math.ceil(from) : Math.floor(from);
29196 if (from < 0) from += len;
29198 for (; from < len; from++) {
29199 if (from in array && array[from] === value) return from;
29205 * Merges a set of parameters with all parameters inherited between the common parents of the
29206 * current state and a given destination state.
29208 * @param {Object} currentParams The value of the current state parameters ($stateParams).
29209 * @param {Object} newParams The set of parameters which will be composited with inherited params.
29210 * @param {Object} $current Internal definition of object representing the current state.
29211 * @param {Object} $to Internal definition of object representing state to transition to.
29213 function inheritParams(currentParams, newParams, $current, $to) {
29214 var parents = ancestors($current, $to), parentParams, inherited = {}, inheritList = [];
29216 for (var i in parents) {
29217 if (!parents[i] || !parents[i].params) continue;
29218 parentParams = objectKeys(parents[i].params);
29219 if (!parentParams.length) continue;
29221 for (var j in parentParams) {
29222 if (indexOf(inheritList, parentParams[j]) >= 0) continue;
29223 inheritList.push(parentParams[j]);
29224 inherited[parentParams[j]] = currentParams[parentParams[j]];
29227 return extend({}, inherited, newParams);
29231 * Performs a non-strict comparison of the subset of two objects, defined by a list of keys.
29233 * @param {Object} a The first object.
29234 * @param {Object} b The second object.
29235 * @param {Array} keys The list of keys within each object to compare. If the list is empty or not specified,
29236 * it defaults to the list of keys in `a`.
29237 * @return {Boolean} Returns `true` if the keys match, otherwise `false`.
29239 function equalForKeys(a, b, keys) {
29242 for (var n in a) keys.push(n); // Used instead of Object.keys() for IE8 compatibility
29245 for (var i=0; i<keys.length; i++) {
29247 if (a[k] != b[k]) return false; // Not '===', values aren't necessarily normalized
29253 * Returns the subset of an object, based on a list of keys.
29255 * @param {Array} keys
29256 * @param {Object} values
29257 * @return {Boolean} Returns a subset of `values`.
29259 function filterByKeys(keys, values) {
29262 forEach(keys, function (name) {
29263 filtered[name] = values[name];
29269 // when you know that your index values will be unique, or you want last-one-in to win
29270 function indexBy(array, propName) {
29272 forEach(array, function(item) {
29273 result[item[propName]] = item;
29278 // extracted from underscore.js
29279 // Return a copy of the object only containing the whitelisted properties.
29280 function pick(obj) {
29282 var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1));
29283 forEach(keys, function(key) {
29284 if (key in obj) copy[key] = obj[key];
29289 // extracted from underscore.js
29290 // Return a copy of the object omitting the blacklisted properties.
29291 function omit(obj) {
29293 var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1));
29294 for (var key in obj) {
29295 if (indexOf(keys, key) == -1) copy[key] = obj[key];
29300 function pluck(collection, key) {
29301 var result = isArray(collection) ? [] : {};
29303 forEach(collection, function(val, i) {
29304 result[i] = isFunction(key) ? key(val) : val[key];
29309 function filter(collection, callback) {
29310 var array = isArray(collection);
29311 var result = array ? [] : {};
29312 forEach(collection, function(val, i) {
29313 if (callback(val, i)) {
29314 result[array ? result.length : i] = val;
29320 function map(collection, callback) {
29321 var result = isArray(collection) ? [] : {};
29323 forEach(collection, function(val, i) {
29324 result[i] = callback(val, i);
29331 * @name ui.router.util
29334 * # ui.router.util sub-module
29336 * This module is a dependency of other sub-modules. Do not include this module as a dependency
29337 * in your angular app (use {@link ui.router} module instead).
29340 angular.module('ui.router.util', ['ng']);
29344 * @name ui.router.router
29346 * @requires ui.router.util
29349 * # ui.router.router sub-module
29351 * This module is a dependency of other sub-modules. Do not include this module as a dependency
29352 * in your angular app (use {@link ui.router} module instead).
29354 angular.module('ui.router.router', ['ui.router.util']);
29358 * @name ui.router.state
29360 * @requires ui.router.router
29361 * @requires ui.router.util
29364 * # ui.router.state sub-module
29366 * This module is a dependency of the main ui.router module. Do not include this module as a dependency
29367 * in your angular app (use {@link ui.router} module instead).
29370 angular.module('ui.router.state', ['ui.router.router', 'ui.router.util']);
29376 * @requires ui.router.state
29381 * ## The main module for ui.router
29382 * There are several sub-modules included with the ui.router module, however only this module is needed
29383 * as a dependency within your angular app. The other modules are for organization purposes.
29386 * * ui.router - the main "umbrella" module
29387 * * ui.router.router -
29389 * *You'll need to include **only** this module as the dependency within your angular app.*
29393 * <html ng-app="myApp">
29395 * <script src="js/angular.js"></script>
29396 * <!-- Include the ui-router script -->
29397 * <script src="js/angular-ui-router.min.js"></script>
29399 * // ...and add 'ui.router' as a dependency
29400 * var myApp = angular.module('myApp', ['ui.router']);
29408 angular.module('ui.router', ['ui.router.state']);
29410 angular.module('ui.router.compat', ['ui.router']);
29414 * @name ui.router.util.$resolve
29417 * @requires $injector
29420 * Manages resolution of (acyclic) graphs of promises.
29422 $Resolve.$inject = ['$q', '$injector'];
29423 function $Resolve( $q, $injector) {
29425 var VISIT_IN_PROGRESS = 1,
29428 NO_DEPENDENCIES = [],
29429 NO_LOCALS = NOTHING,
29430 NO_PARENT = extend($q.when(NOTHING), { $$promises: NOTHING, $$values: NOTHING });
29435 * @name ui.router.util.$resolve#study
29436 * @methodOf ui.router.util.$resolve
29439 * Studies a set of invocables that are likely to be used multiple times.
29441 * $resolve.study(invocables)(locals, parent, self)
29445 * $resolve.resolve(invocables, locals, parent, self)
29447 * but the former is more efficient (in fact `resolve` just calls `study`
29450 * @param {object} invocables Invocable objects
29451 * @return {function} a function to pass in locals, parent and self
29453 this.study = function (invocables) {
29454 if (!isObject(invocables)) throw new Error("'invocables' must be an object");
29455 var invocableKeys = objectKeys(invocables || {});
29457 // Perform a topological sort of invocables to build an ordered plan
29458 var plan = [], cycle = [], visited = {};
29459 function visit(value, key) {
29460 if (visited[key] === VISIT_DONE) return;
29463 if (visited[key] === VISIT_IN_PROGRESS) {
29464 cycle.splice(0, indexOf(cycle, key));
29465 throw new Error("Cyclic dependency: " + cycle.join(" -> "));
29467 visited[key] = VISIT_IN_PROGRESS;
29469 if (isString(value)) {
29470 plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES);
29472 var params = $injector.annotate(value);
29473 forEach(params, function (param) {
29474 if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param);
29476 plan.push(key, value, params);
29480 visited[key] = VISIT_DONE;
29482 forEach(invocables, visit);
29483 invocables = cycle = visited = null; // plan is all that's required
29485 function isResolve(value) {
29486 return isObject(value) && value.then && value.$$promises;
29489 return function (locals, parent, self) {
29490 if (isResolve(locals) && self === undefined) {
29491 self = parent; parent = locals; locals = null;
29493 if (!locals) locals = NO_LOCALS;
29494 else if (!isObject(locals)) {
29495 throw new Error("'locals' must be an object");
29497 if (!parent) parent = NO_PARENT;
29498 else if (!isResolve(parent)) {
29499 throw new Error("'parent' must be a promise returned by $resolve.resolve()");
29502 // To complete the overall resolution, we have to wait for the parent
29503 // promise and for the promise for each invokable in our plan.
29504 var resolution = $q.defer(),
29505 result = resolution.promise,
29506 promises = result.$$promises = {},
29507 values = extend({}, locals),
29508 wait = 1 + plan.length/3,
29512 // Merge parent values we haven't got yet and publish our own $$values
29514 if (!merged) merge(values, parent.$$values);
29515 result.$$values = values;
29516 result.$$promises = result.$$promises || true; // keep for isResolve()
29517 delete result.$$inheritedValues;
29518 resolution.resolve(values);
29522 function fail(reason) {
29523 result.$$failure = reason;
29524 resolution.reject(reason);
29527 // Short-circuit if parent has already failed
29528 if (isDefined(parent.$$failure)) {
29529 fail(parent.$$failure);
29533 if (parent.$$inheritedValues) {
29534 merge(values, omit(parent.$$inheritedValues, invocableKeys));
29537 // Merge parent values if the parent has already resolved, or merge
29538 // parent promises and wait if the parent resolve is still in progress.
29539 extend(promises, parent.$$promises);
29540 if (parent.$$values) {
29541 merged = merge(values, omit(parent.$$values, invocableKeys));
29542 result.$$inheritedValues = omit(parent.$$values, invocableKeys);
29545 if (parent.$$inheritedValues) {
29546 result.$$inheritedValues = omit(parent.$$inheritedValues, invocableKeys);
29548 parent.then(done, fail);
29551 // Process each invocable in the plan, but ignore any where a local of the same name exists.
29552 for (var i=0, ii=plan.length; i<ii; i+=3) {
29553 if (locals.hasOwnProperty(plan[i])) done();
29554 else invoke(plan[i], plan[i+1], plan[i+2]);
29557 function invoke(key, invocable, params) {
29558 // Create a deferred for this invocation. Failures will propagate to the resolution as well.
29559 var invocation = $q.defer(), waitParams = 0;
29560 function onfailure(reason) {
29561 invocation.reject(reason);
29564 // Wait for any parameter that we have a promise for (either from parent or from this
29565 // resolve; in that case study() will have made sure it's ordered before us in the plan).
29566 forEach(params, function (dep) {
29567 if (promises.hasOwnProperty(dep) && !locals.hasOwnProperty(dep)) {
29569 promises[dep].then(function (result) {
29570 values[dep] = result;
29571 if (!(--waitParams)) proceed();
29575 if (!waitParams) proceed();
29576 function proceed() {
29577 if (isDefined(result.$$failure)) return;
29579 invocation.resolve($injector.invoke(invocable, self, values));
29580 invocation.promise.then(function (result) {
29581 values[key] = result;
29588 // Publish promise synchronously; invocations further down in the plan may depend on it.
29589 promises[key] = invocation.promise;
29598 * @name ui.router.util.$resolve#resolve
29599 * @methodOf ui.router.util.$resolve
29602 * Resolves a set of invocables. An invocable is a function to be invoked via
29603 * `$injector.invoke()`, and can have an arbitrary number of dependencies.
29604 * An invocable can either return a value directly,
29605 * or a `$q` promise. If a promise is returned it will be resolved and the
29606 * resulting value will be used instead. Dependencies of invocables are resolved
29607 * (in this order of precedence)
29609 * - from the specified `locals`
29610 * - from another invocable that is part of this `$resolve` call
29611 * - from an invocable that is inherited from a `parent` call to `$resolve`
29613 * - from any ancestor `$resolve` of that parent).
29615 * The return value of `$resolve` is a promise for an object that contains
29616 * (in this order of precedence)
29618 * - any `locals` (if specified)
29619 * - the resolved return values of all injectables
29620 * - any values inherited from a `parent` call to `$resolve` (if specified)
29622 * The promise will resolve after the `parent` promise (if any) and all promises
29623 * returned by injectables have been resolved. If any invocable
29624 * (or `$injector.invoke`) throws an exception, or if a promise returned by an
29625 * invocable is rejected, the `$resolve` promise is immediately rejected with the
29626 * same error. A rejection of a `parent` promise (if specified) will likewise be
29627 * propagated immediately. Once the `$resolve` promise has been rejected, no
29628 * further invocables will be called.
29630 * Cyclic dependencies between invocables are not permitted and will cause `$resolve`
29631 * to throw an error. As a special case, an injectable can depend on a parameter
29632 * with the same name as the injectable, which will be fulfilled from the `parent`
29633 * injectable of the same name. This allows inherited values to be decorated.
29634 * Note that in this case any other injectable in the same `$resolve` with the same
29635 * dependency would see the decorated value, not the inherited value.
29637 * Note that missing dependencies -- unlike cyclic dependencies -- will cause an
29638 * (asynchronous) rejection of the `$resolve` promise rather than a (synchronous)
29641 * Invocables are invoked eagerly as soon as all dependencies are available.
29642 * This is true even for dependencies inherited from a `parent` call to `$resolve`.
29644 * As a special case, an invocable can be a string, in which case it is taken to
29645 * be a service name to be passed to `$injector.get()`. This is supported primarily
29646 * for backwards-compatibility with the `resolve` property of `$routeProvider`
29649 * @param {object} invocables functions to invoke or
29650 * `$injector` services to fetch.
29651 * @param {object} locals values to make available to the injectables
29652 * @param {object} parent a promise returned by another call to `$resolve`.
29653 * @param {object} self the `this` for the invoked methods
29654 * @return {object} Promise for an object that contains the resolved return value
29655 * of all invocables, as well as any inherited and local values.
29657 this.resolve = function (invocables, locals, parent, self) {
29658 return this.study(invocables)(locals, parent, self);
29662 angular.module('ui.router.util').service('$resolve', $Resolve);
29667 * @name ui.router.util.$templateFactory
29670 * @requires $templateCache
29671 * @requires $injector
29674 * Service. Manages loading of templates.
29676 $TemplateFactory.$inject = ['$http', '$templateCache', '$injector'];
29677 function $TemplateFactory( $http, $templateCache, $injector) {
29681 * @name ui.router.util.$templateFactory#fromConfig
29682 * @methodOf ui.router.util.$templateFactory
29685 * Creates a template from a configuration object.
29687 * @param {object} config Configuration object for which to load a template.
29688 * The following properties are search in the specified order, and the first one
29689 * that is defined is used to create the template:
29691 * @param {string|object} config.template html string template or function to
29692 * load via {@link ui.router.util.$templateFactory#fromString fromString}.
29693 * @param {string|object} config.templateUrl url to load or a function returning
29694 * the url to load via {@link ui.router.util.$templateFactory#fromUrl fromUrl}.
29695 * @param {Function} config.templateProvider function to invoke via
29696 * {@link ui.router.util.$templateFactory#fromProvider fromProvider}.
29697 * @param {object} params Parameters to pass to the template function.
29698 * @param {object} locals Locals to pass to `invoke` if the template is loaded
29699 * via a `templateProvider`. Defaults to `{ params: params }`.
29701 * @return {string|object} The template html as a string, or a promise for
29702 * that string,or `null` if no template is configured.
29704 this.fromConfig = function (config, params, locals) {
29706 isDefined(config.template) ? this.fromString(config.template, params) :
29707 isDefined(config.templateUrl) ? this.fromUrl(config.templateUrl, params) :
29708 isDefined(config.templateProvider) ? this.fromProvider(config.templateProvider, params, locals) :
29715 * @name ui.router.util.$templateFactory#fromString
29716 * @methodOf ui.router.util.$templateFactory
29719 * Creates a template from a string or a function returning a string.
29721 * @param {string|object} template html template as a string or function that
29722 * returns an html template as a string.
29723 * @param {object} params Parameters to pass to the template function.
29725 * @return {string|object} The template html as a string, or a promise for that
29728 this.fromString = function (template, params) {
29729 return isFunction(template) ? template(params) : template;
29734 * @name ui.router.util.$templateFactory#fromUrl
29735 * @methodOf ui.router.util.$templateFactory
29738 * Loads a template from the a URL via `$http` and `$templateCache`.
29740 * @param {string|Function} url url of the template to load, or a function
29741 * that returns a url.
29742 * @param {Object} params Parameters to pass to the url function.
29743 * @return {string|Promise.<string>} The template html as a string, or a promise
29746 this.fromUrl = function (url, params) {
29747 if (isFunction(url)) url = url(params);
29748 if (url == null) return null;
29750 .get(url, { cache: $templateCache, headers: { Accept: 'text/html' }})
29751 .then(function(response) { return response.data; });
29756 * @name ui.router.util.$templateFactory#fromProvider
29757 * @methodOf ui.router.util.$templateFactory
29760 * Creates a template by invoking an injectable provider function.
29762 * @param {Function} provider Function to invoke via `$injector.invoke`
29763 * @param {Object} params Parameters for the template.
29764 * @param {Object} locals Locals to pass to `invoke`. Defaults to
29765 * `{ params: params }`.
29766 * @return {string|Promise.<string>} The template html as a string, or a promise
29769 this.fromProvider = function (provider, params, locals) {
29770 return $injector.invoke(provider, null, locals || { params: params });
29774 angular.module('ui.router.util').service('$templateFactory', $TemplateFactory);
29776 var $$UMFP; // reference to $UrlMatcherFactoryProvider
29780 * @name ui.router.util.type:UrlMatcher
29783 * Matches URLs against patterns and extracts named parameters from the path or the search
29784 * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list
29785 * of search parameters. Multiple search parameter names are separated by '&'. Search parameters
29786 * do not influence whether or not a URL is matched, but their values are passed through into
29787 * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}.
29789 * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace
29790 * syntax, which optionally allows a regular expression for the parameter to be specified:
29792 * * `':'` name - colon placeholder
29793 * * `'*'` name - catch-all placeholder
29794 * * `'{' name '}'` - curly placeholder
29795 * * `'{' name ':' regexp|type '}'` - curly placeholder with regexp or type name. Should the
29796 * regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash.
29798 * Parameter names may contain only word characters (latin letters, digits, and underscore) and
29799 * must be unique within the pattern (across both path and search parameters). For colon
29800 * placeholders or curly placeholders without an explicit regexp, a path parameter matches any
29801 * number of characters other than '/'. For catch-all placeholders the path parameter matches
29802 * any number of characters.
29806 * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for
29807 * trailing slashes, and patterns have to match the entire path, not just a prefix.
29808 * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or
29809 * '/user/bob/details'. The second path segment will be captured as the parameter 'id'.
29810 * * `'/user/{id}'` - Same as the previous example, but using curly brace syntax.
29811 * * `'/user/{id:[^/]*}'` - Same as the previous example.
29812 * * `'/user/{id:[0-9a-fA-F]{1,8}}'` - Similar to the previous example, but only matches if the id
29813 * parameter consists of 1 to 8 hex digits.
29814 * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the
29815 * path into the parameter 'path'.
29816 * * `'/files/*path'` - ditto.
29817 * * `'/calendar/{start:date}'` - Matches "/calendar/2014-11-12" (because the pattern defined
29818 * in the built-in `date` Type matches `2014-11-12`) and provides a Date object in $stateParams.start
29820 * @param {string} pattern The pattern to compile into a matcher.
29821 * @param {Object} config A configuration object hash:
29822 * @param {Object=} parentMatcher Used to concatenate the pattern/config onto
29823 * an existing UrlMatcher
29825 * * `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`.
29826 * * `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`.
29828 * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any
29829 * URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns
29830 * non-null) will start with this prefix.
29832 * @property {string} source The pattern that was passed into the constructor
29834 * @property {string} sourcePath The path portion of the source property
29836 * @property {string} sourceSearch The search portion of the source property
29838 * @property {string} regex The constructed regex that will be used to match against the url when
29839 * it is time to determine which url will match.
29841 * @returns {Object} New `UrlMatcher` object
29843 function UrlMatcher(pattern, config, parentMatcher) {
29844 config = extend({ params: {} }, isObject(config) ? config : {});
29846 // Find all placeholders and create a compiled pattern, using either classic or curly syntax:
29850 // '{' name ':' regexp '}'
29851 // The regular expression is somewhat complicated due to the need to allow curly braces
29852 // inside the regular expression. The placeholder regexp breaks down as follows:
29853 // ([:*])([\w\[\]]+) - classic placeholder ($1 / $2) (search version has - for snake-case)
29854 // \{([\w\[\]]+)(?:\:\s*( ... ))?\} - curly brace placeholder ($3) with optional regexp/type ... ($4) (search version has - for snake-case
29855 // (?: ... | ... | ... )+ - the regexp consists of any number of atoms, an atom being either
29856 // [^{}\\]+ - anything other than curly braces or backslash
29857 // \\. - a backslash escape
29858 // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms
29859 var placeholder = /([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
29860 searchPlaceholder = /([:]?)([\w\[\].-]+)|\{([\w\[\].-]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
29861 compiled = '^', last = 0, m,
29862 segments = this.segments = [],
29863 parentParams = parentMatcher ? parentMatcher.params : {},
29864 params = this.params = parentMatcher ? parentMatcher.params.$$new() : new $$UMFP.ParamSet(),
29867 function addParameter(id, type, config, location) {
29868 paramNames.push(id);
29869 if (parentParams[id]) return parentParams[id];
29870 if (!/^\w+([-.]+\w+)*(?:\[\])?$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'");
29871 if (params[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'");
29872 params[id] = new $$UMFP.Param(id, type, config, location);
29876 function quoteRegExp(string, pattern, squash, optional) {
29877 var surroundPattern = ['',''], result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&");
29878 if (!pattern) return result;
29880 case false: surroundPattern = ['(', ')' + (optional ? "?" : "")]; break;
29882 result = result.replace(/\/$/, '');
29883 surroundPattern = ['(?:\/(', ')|\/)?'];
29885 default: surroundPattern = ['(' + squash + "|", ')?']; break;
29887 return result + surroundPattern[0] + pattern + surroundPattern[1];
29890 this.source = pattern;
29892 // Split into static segments separated by path parameter placeholders.
29893 // The number of segments is always 1 more than the number of parameters.
29894 function matchDetails(m, isSearch) {
29895 var id, regexp, segment, type, cfg, arrayMode;
29896 id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null
29897 cfg = config.params[id];
29898 segment = pattern.substring(last, m.index);
29899 regexp = isSearch ? m[4] : m[4] || (m[1] == '*' ? '.*' : null);
29902 type = $$UMFP.type(regexp) || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp, config.caseInsensitive ? 'i' : undefined) });
29906 id: id, regexp: regexp, segment: segment, type: type, cfg: cfg
29910 var p, param, segment;
29911 while ((m = placeholder.exec(pattern))) {
29912 p = matchDetails(m, false);
29913 if (p.segment.indexOf('?') >= 0) break; // we're into the search part
29915 param = addParameter(p.id, p.type, p.cfg, "path");
29916 compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash, param.isOptional);
29917 segments.push(p.segment);
29918 last = placeholder.lastIndex;
29920 segment = pattern.substring(last);
29922 // Find any search parameter names and remove them from the last segment
29923 var i = segment.indexOf('?');
29926 var search = this.sourceSearch = segment.substring(i);
29927 segment = segment.substring(0, i);
29928 this.sourcePath = pattern.substring(0, last + i);
29930 if (search.length > 0) {
29932 while ((m = searchPlaceholder.exec(search))) {
29933 p = matchDetails(m, true);
29934 param = addParameter(p.id, p.type, p.cfg, "search");
29935 last = placeholder.lastIndex;
29940 this.sourcePath = pattern;
29941 this.sourceSearch = '';
29944 compiled += quoteRegExp(segment) + (config.strict === false ? '\/?' : '') + '$';
29945 segments.push(segment);
29947 this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined);
29948 this.prefix = segments[0];
29949 this.$$paramNames = paramNames;
29954 * @name ui.router.util.type:UrlMatcher#concat
29955 * @methodOf ui.router.util.type:UrlMatcher
29958 * Returns a new matcher for a pattern constructed by appending the path part and adding the
29959 * search parameters of the specified pattern to this pattern. The current pattern is not
29960 * modified. This can be understood as creating a pattern for URLs that are relative to (or
29961 * suffixes of) the current pattern.
29964 * The following two matchers are equivalent:
29966 * new UrlMatcher('/user/{id}?q').concat('/details?date');
29967 * new UrlMatcher('/user/{id}/details?q&date');
29970 * @param {string} pattern The pattern to append.
29971 * @param {Object} config An object hash of the configuration for the matcher.
29972 * @returns {UrlMatcher} A matcher for the concatenated pattern.
29974 UrlMatcher.prototype.concat = function (pattern, config) {
29975 // Because order of search parameters is irrelevant, we can add our own search
29976 // parameters to the end of the new pattern. Parse the new pattern by itself
29977 // and then join the bits together, but it's much easier to do this on a string level.
29978 var defaultConfig = {
29979 caseInsensitive: $$UMFP.caseInsensitive(),
29980 strict: $$UMFP.strictMode(),
29981 squash: $$UMFP.defaultSquashPolicy()
29983 return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch, extend(defaultConfig, config), this);
29986 UrlMatcher.prototype.toString = function () {
29987 return this.source;
29992 * @name ui.router.util.type:UrlMatcher#exec
29993 * @methodOf ui.router.util.type:UrlMatcher
29996 * Tests the specified path against this matcher, and returns an object containing the captured
29997 * parameter values, or null if the path does not match. The returned object contains the values
29998 * of any search parameters that are mentioned in the pattern, but their value may be null if
29999 * they are not present in `searchParams`. This means that search parameters are always treated
30004 * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', {
30005 * x: '1', q: 'hello'
30007 * // returns { id: 'bob', q: 'hello', r: null }
30010 * @param {string} path The URL path to match, e.g. `$location.path()`.
30011 * @param {Object} searchParams URL search parameters, e.g. `$location.search()`.
30012 * @returns {Object} The captured parameter values.
30014 UrlMatcher.prototype.exec = function (path, searchParams) {
30015 var m = this.regexp.exec(path);
30016 if (!m) return null;
30017 searchParams = searchParams || {};
30019 var paramNames = this.parameters(), nTotal = paramNames.length,
30020 nPath = this.segments.length - 1,
30021 values = {}, i, j, cfg, paramName;
30023 if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'");
30025 function decodePathArray(string) {
30026 function reverseString(str) { return str.split("").reverse().join(""); }
30027 function unquoteDashes(str) { return str.replace(/\\-/g, "-"); }
30029 var split = reverseString(string).split(/-(?!\\)/);
30030 var allReversed = map(split, reverseString);
30031 return map(allReversed, unquoteDashes).reverse();
30034 var param, paramVal;
30035 for (i = 0; i < nPath; i++) {
30036 paramName = paramNames[i];
30037 param = this.params[paramName];
30039 // if the param value matches a pre-replace pair, replace the value before decoding.
30040 for (j = 0; j < param.replace.length; j++) {
30041 if (param.replace[j].from === paramVal) paramVal = param.replace[j].to;
30043 if (paramVal && param.array === true) paramVal = decodePathArray(paramVal);
30044 if (isDefined(paramVal)) paramVal = param.type.decode(paramVal);
30045 values[paramName] = param.value(paramVal);
30047 for (/**/; i < nTotal; i++) {
30048 paramName = paramNames[i];
30049 values[paramName] = this.params[paramName].value(searchParams[paramName]);
30050 param = this.params[paramName];
30051 paramVal = searchParams[paramName];
30052 for (j = 0; j < param.replace.length; j++) {
30053 if (param.replace[j].from === paramVal) paramVal = param.replace[j].to;
30055 if (isDefined(paramVal)) paramVal = param.type.decode(paramVal);
30056 values[paramName] = param.value(paramVal);
30064 * @name ui.router.util.type:UrlMatcher#parameters
30065 * @methodOf ui.router.util.type:UrlMatcher
30068 * Returns the names of all path and search parameters of this pattern in an unspecified order.
30070 * @returns {Array.<string>} An array of parameter names. Must be treated as read-only. If the
30071 * pattern has no parameters, an empty array is returned.
30073 UrlMatcher.prototype.parameters = function (param) {
30074 if (!isDefined(param)) return this.$$paramNames;
30075 return this.params[param] || null;
30080 * @name ui.router.util.type:UrlMatcher#validates
30081 * @methodOf ui.router.util.type:UrlMatcher
30084 * Checks an object hash of parameters to validate their correctness according to the parameter
30085 * types of this `UrlMatcher`.
30087 * @param {Object} params The object hash of parameters to validate.
30088 * @returns {boolean} Returns `true` if `params` validates, otherwise `false`.
30090 UrlMatcher.prototype.validates = function (params) {
30091 return this.params.$$validates(params);
30096 * @name ui.router.util.type:UrlMatcher#format
30097 * @methodOf ui.router.util.type:UrlMatcher
30100 * Creates a URL that matches this pattern by substituting the specified values
30101 * for the path and search parameters. Null values for path parameters are
30102 * treated as empty strings.
30106 * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' });
30107 * // returns '/user/bob?q=yes'
30110 * @param {Object} values the values to substitute for the parameters in this pattern.
30111 * @returns {string} the formatted URL (path and optionally search part).
30113 UrlMatcher.prototype.format = function (values) {
30114 values = values || {};
30115 var segments = this.segments, params = this.parameters(), paramset = this.params;
30116 if (!this.validates(values)) return null;
30118 var i, search = false, nPath = segments.length - 1, nTotal = params.length, result = segments[0];
30120 function encodeDashes(str) { // Replace dashes with encoded "\-"
30121 return encodeURIComponent(str).replace(/-/g, function(c) { return '%5C%' + c.charCodeAt(0).toString(16).toUpperCase(); });
30124 for (i = 0; i < nTotal; i++) {
30125 var isPathParam = i < nPath;
30126 var name = params[i], param = paramset[name], value = param.value(values[name]);
30127 var isDefaultValue = param.isOptional && param.type.equals(param.value(), value);
30128 var squash = isDefaultValue ? param.squash : false;
30129 var encoded = param.type.encode(value);
30132 var nextSegment = segments[i + 1];
30133 var isFinalPathParam = i + 1 === nPath;
30135 if (squash === false) {
30136 if (encoded != null) {
30137 if (isArray(encoded)) {
30138 result += map(encoded, encodeDashes).join("-");
30140 result += encodeURIComponent(encoded);
30143 result += nextSegment;
30144 } else if (squash === true) {
30145 var capture = result.match(/\/$/) ? /\/?(.*)/ : /(.*)/;
30146 result += nextSegment.match(capture)[1];
30147 } else if (isString(squash)) {
30148 result += squash + nextSegment;
30151 if (isFinalPathParam && param.squash === true && result.slice(-1) === '/') result = result.slice(0, -1);
30153 if (encoded == null || (isDefaultValue && squash !== false)) continue;
30154 if (!isArray(encoded)) encoded = [ encoded ];
30155 if (encoded.length === 0) continue;
30156 encoded = map(encoded, encodeURIComponent).join('&' + name + '=');
30157 result += (search ? '&' : '?') + (name + '=' + encoded);
30167 * @name ui.router.util.type:Type
30170 * Implements an interface to define custom parameter types that can be decoded from and encoded to
30171 * string parameters matched in a URL. Used by {@link ui.router.util.type:UrlMatcher `UrlMatcher`}
30172 * objects when matching or formatting URLs, or comparing or validating parameter values.
30174 * See {@link ui.router.util.$urlMatcherFactory#methods_type `$urlMatcherFactory#type()`} for more
30175 * information on registering custom types.
30177 * @param {Object} config A configuration object which contains the custom type definition. The object's
30178 * properties will override the default methods and/or pattern in `Type`'s public interface.
30182 * decode: function(val) { return parseInt(val, 10); },
30183 * encode: function(val) { return val && val.toString(); },
30184 * equals: function(a, b) { return this.is(a) && a === b; },
30185 * is: function(val) { return angular.isNumber(val) isFinite(val) && val % 1 === 0; },
30190 * @property {RegExp} pattern The regular expression pattern used to match values of this type when
30191 * coming from a substring of a URL.
30193 * @returns {Object} Returns a new `Type` object.
30195 function Type(config) {
30196 extend(this, config);
30201 * @name ui.router.util.type:Type#is
30202 * @methodOf ui.router.util.type:Type
30205 * Detects whether a value is of a particular type. Accepts a native (decoded) value
30206 * and determines whether it matches the current `Type` object.
30208 * @param {*} val The value to check.
30209 * @param {string} key Optional. If the type check is happening in the context of a specific
30210 * {@link ui.router.util.type:UrlMatcher `UrlMatcher`} object, this is the name of the
30211 * parameter in which `val` is stored. Can be used for meta-programming of `Type` objects.
30212 * @returns {Boolean} Returns `true` if the value matches the type, otherwise `false`.
30214 Type.prototype.is = function(val, key) {
30220 * @name ui.router.util.type:Type#encode
30221 * @methodOf ui.router.util.type:Type
30224 * Encodes a custom/native type value to a string that can be embedded in a URL. Note that the
30225 * return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it
30226 * only needs to be a representation of `val` that has been coerced to a string.
30228 * @param {*} val The value to encode.
30229 * @param {string} key The name of the parameter in which `val` is stored. Can be used for
30230 * meta-programming of `Type` objects.
30231 * @returns {string} Returns a string representation of `val` that can be encoded in a URL.
30233 Type.prototype.encode = function(val, key) {
30239 * @name ui.router.util.type:Type#decode
30240 * @methodOf ui.router.util.type:Type
30243 * Converts a parameter value (from URL string or transition param) to a custom/native value.
30245 * @param {string} val The URL parameter value to decode.
30246 * @param {string} key The name of the parameter in which `val` is stored. Can be used for
30247 * meta-programming of `Type` objects.
30248 * @returns {*} Returns a custom representation of the URL parameter value.
30250 Type.prototype.decode = function(val, key) {
30256 * @name ui.router.util.type:Type#equals
30257 * @methodOf ui.router.util.type:Type
30260 * Determines whether two decoded values are equivalent.
30262 * @param {*} a A value to compare against.
30263 * @param {*} b A value to compare against.
30264 * @returns {Boolean} Returns `true` if the values are equivalent/equal, otherwise `false`.
30266 Type.prototype.equals = function(a, b) {
30270 Type.prototype.$subPattern = function() {
30271 var sub = this.pattern.toString();
30272 return sub.substr(1, sub.length - 2);
30275 Type.prototype.pattern = /.*/;
30277 Type.prototype.toString = function() { return "{Type:" + this.name + "}"; };
30279 /** Given an encoded string, or a decoded object, returns a decoded object */
30280 Type.prototype.$normalize = function(val) {
30281 return this.is(val) ? val : this.decode(val);
30285 * Wraps an existing custom Type as an array of Type, depending on 'mode'.
30287 * - urlmatcher pattern "/path?{queryParam[]:int}"
30288 * - url: "/path?queryParam=1&queryParam=2
30289 * - $stateParams.queryParam will be [1, 2]
30290 * if `mode` is "auto", then
30291 * - url: "/path?queryParam=1 will create $stateParams.queryParam: 1
30292 * - url: "/path?queryParam=1&queryParam=2 will create $stateParams.queryParam: [1, 2]
30294 Type.prototype.$asArray = function(mode, isSearch) {
30295 if (!mode) return this;
30296 if (mode === "auto" && !isSearch) throw new Error("'auto' array mode is for query parameters only");
30298 function ArrayType(type, mode) {
30299 function bindTo(type, callbackName) {
30300 return function() {
30301 return type[callbackName].apply(type, arguments);
30305 // Wrap non-array value as array
30306 function arrayWrap(val) { return isArray(val) ? val : (isDefined(val) ? [ val ] : []); }
30307 // Unwrap array value for "auto" mode. Return undefined for empty array.
30308 function arrayUnwrap(val) {
30309 switch(val.length) {
30310 case 0: return undefined;
30311 case 1: return mode === "auto" ? val[0] : val;
30312 default: return val;
30315 function falsey(val) { return !val; }
30317 // Wraps type (.is/.encode/.decode) functions to operate on each value of an array
30318 function arrayHandler(callback, allTruthyMode) {
30319 return function handleArray(val) {
30320 if (isArray(val) && val.length === 0) return val;
30321 val = arrayWrap(val);
30322 var result = map(val, callback);
30323 if (allTruthyMode === true)
30324 return filter(result, falsey).length === 0;
30325 return arrayUnwrap(result);
30329 // Wraps type (.equals) functions to operate on each value of an array
30330 function arrayEqualsHandler(callback) {
30331 return function handleArray(val1, val2) {
30332 var left = arrayWrap(val1), right = arrayWrap(val2);
30333 if (left.length !== right.length) return false;
30334 for (var i = 0; i < left.length; i++) {
30335 if (!callback(left[i], right[i])) return false;
30341 this.encode = arrayHandler(bindTo(type, 'encode'));
30342 this.decode = arrayHandler(bindTo(type, 'decode'));
30343 this.is = arrayHandler(bindTo(type, 'is'), true);
30344 this.equals = arrayEqualsHandler(bindTo(type, 'equals'));
30345 this.pattern = type.pattern;
30346 this.$normalize = arrayHandler(bindTo(type, '$normalize'));
30347 this.name = type.name;
30348 this.$arrayMode = mode;
30351 return new ArrayType(this, mode);
30358 * @name ui.router.util.$urlMatcherFactory
30361 * Factory for {@link ui.router.util.type:UrlMatcher `UrlMatcher`} instances. The factory
30362 * is also available to providers under the name `$urlMatcherFactoryProvider`.
30364 function $UrlMatcherFactory() {
30367 var isCaseInsensitive = false, isStrictMode = true, defaultSquashPolicy = false;
30369 // Use tildes to pre-encode slashes.
30370 // If the slashes are simply URLEncoded, the browser can choose to pre-decode them,
30371 // and bidirectional encoding/decoding fails.
30372 // Tilde was chosen because it's not a RFC 3986 section 2.2 Reserved Character
30373 function valToString(val) { return val != null ? val.toString().replace(/~/g, "~~").replace(/\//g, "~2F") : val; }
30374 function valFromString(val) { return val != null ? val.toString().replace(/~2F/g, "/").replace(/~~/g, "~") : val; }
30376 var $types = {}, enqueue = true, typeQueue = [], injector, defaultTypes = {
30378 encode: valToString,
30379 decode: valFromString,
30380 // TODO: in 1.0, make string .is() return false if value is undefined/null by default.
30381 // In 0.2.x, string params are optional by default for backwards compat
30382 is: function(val) { return val == null || !isDefined(val) || typeof val === "string"; },
30386 encode: valToString,
30387 decode: function(val) { return parseInt(val, 10); },
30388 is: function(val) { return isDefined(val) && this.decode(val.toString()) === val; },
30392 encode: function(val) { return val ? 1 : 0; },
30393 decode: function(val) { return parseInt(val, 10) !== 0; },
30394 is: function(val) { return val === true || val === false; },
30398 encode: function (val) {
30401 return [ val.getFullYear(),
30402 ('0' + (val.getMonth() + 1)).slice(-2),
30403 ('0' + val.getDate()).slice(-2)
30406 decode: function (val) {
30407 if (this.is(val)) return val;
30408 var match = this.capture.exec(val);
30409 return match ? new Date(match[1], match[2] - 1, match[3]) : undefined;
30411 is: function(val) { return val instanceof Date && !isNaN(val.valueOf()); },
30412 equals: function (a, b) { return this.is(a) && this.is(b) && a.toISOString() === b.toISOString(); },
30413 pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/,
30414 capture: /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/
30417 encode: angular.toJson,
30418 decode: angular.fromJson,
30419 is: angular.isObject,
30420 equals: angular.equals,
30423 "any": { // does not encode/decode
30424 encode: angular.identity,
30425 decode: angular.identity,
30426 equals: angular.equals,
30431 function getDefaultConfig() {
30433 strict: isStrictMode,
30434 caseInsensitive: isCaseInsensitive
30438 function isInjectable(value) {
30439 return (isFunction(value) || (isArray(value) && isFunction(value[value.length - 1])));
30443 * [Internal] Get the default value of a parameter, which may be an injectable function.
30445 $UrlMatcherFactory.$$getDefaultValue = function(config) {
30446 if (!isInjectable(config.value)) return config.value;
30447 if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
30448 return injector.invoke(config.value);
30453 * @name ui.router.util.$urlMatcherFactory#caseInsensitive
30454 * @methodOf ui.router.util.$urlMatcherFactory
30457 * Defines whether URL matching should be case sensitive (the default behavior), or not.
30459 * @param {boolean} value `false` to match URL in a case sensitive manner; otherwise `true`;
30460 * @returns {boolean} the current value of caseInsensitive
30462 this.caseInsensitive = function(value) {
30463 if (isDefined(value))
30464 isCaseInsensitive = value;
30465 return isCaseInsensitive;
30470 * @name ui.router.util.$urlMatcherFactory#strictMode
30471 * @methodOf ui.router.util.$urlMatcherFactory
30474 * Defines whether URLs should match trailing slashes, or not (the default behavior).
30476 * @param {boolean=} value `false` to match trailing slashes in URLs, otherwise `true`.
30477 * @returns {boolean} the current value of strictMode
30479 this.strictMode = function(value) {
30480 if (isDefined(value))
30481 isStrictMode = value;
30482 return isStrictMode;
30487 * @name ui.router.util.$urlMatcherFactory#defaultSquashPolicy
30488 * @methodOf ui.router.util.$urlMatcherFactory
30491 * Sets the default behavior when generating or matching URLs with default parameter values.
30493 * @param {string} value A string that defines the default parameter URL squashing behavior.
30494 * `nosquash`: When generating an href with a default parameter value, do not squash the parameter value from the URL
30495 * `slash`: When generating an href with a default parameter value, squash (remove) the parameter value, and, if the
30496 * parameter is surrounded by slashes, squash (remove) one slash from the URL
30497 * any other string, e.g. "~": When generating an href with a default parameter value, squash (remove)
30498 * the parameter value from the URL and replace it with this string.
30500 this.defaultSquashPolicy = function(value) {
30501 if (!isDefined(value)) return defaultSquashPolicy;
30502 if (value !== true && value !== false && !isString(value))
30503 throw new Error("Invalid squash policy: " + value + ". Valid policies: false, true, arbitrary-string");
30504 defaultSquashPolicy = value;
30510 * @name ui.router.util.$urlMatcherFactory#compile
30511 * @methodOf ui.router.util.$urlMatcherFactory
30514 * Creates a {@link ui.router.util.type:UrlMatcher `UrlMatcher`} for the specified pattern.
30516 * @param {string} pattern The URL pattern.
30517 * @param {Object} config The config object hash.
30518 * @returns {UrlMatcher} The UrlMatcher.
30520 this.compile = function (pattern, config) {
30521 return new UrlMatcher(pattern, extend(getDefaultConfig(), config));
30526 * @name ui.router.util.$urlMatcherFactory#isMatcher
30527 * @methodOf ui.router.util.$urlMatcherFactory
30530 * Returns true if the specified object is a `UrlMatcher`, or false otherwise.
30532 * @param {Object} object The object to perform the type check against.
30533 * @returns {Boolean} Returns `true` if the object matches the `UrlMatcher` interface, by
30534 * implementing all the same methods.
30536 this.isMatcher = function (o) {
30537 if (!isObject(o)) return false;
30540 forEach(UrlMatcher.prototype, function(val, name) {
30541 if (isFunction(val)) {
30542 result = result && (isDefined(o[name]) && isFunction(o[name]));
30550 * @name ui.router.util.$urlMatcherFactory#type
30551 * @methodOf ui.router.util.$urlMatcherFactory
30554 * Registers a custom {@link ui.router.util.type:Type `Type`} object that can be used to
30555 * generate URLs with typed parameters.
30557 * @param {string} name The type name.
30558 * @param {Object|Function} definition The type definition. See
30559 * {@link ui.router.util.type:Type `Type`} for information on the values accepted.
30560 * @param {Object|Function} definitionFn (optional) A function that is injected before the app
30561 * runtime starts. The result of this function is merged into the existing `definition`.
30562 * See {@link ui.router.util.type:Type `Type`} for information on the values accepted.
30564 * @returns {Object} Returns `$urlMatcherFactoryProvider`.
30567 * This is a simple example of a custom type that encodes and decodes items from an
30568 * array, using the array index as the URL-encoded value:
30571 * var list = ['John', 'Paul', 'George', 'Ringo'];
30573 * $urlMatcherFactoryProvider.type('listItem', {
30574 * encode: function(item) {
30575 * // Represent the list item in the URL using its corresponding index
30576 * return list.indexOf(item);
30578 * decode: function(item) {
30579 * // Look up the list item by index
30580 * return list[parseInt(item, 10)];
30582 * is: function(item) {
30583 * // Ensure the item is valid by checking to see that it appears
30585 * return list.indexOf(item) > -1;
30589 * $stateProvider.state('list', {
30590 * url: "/list/{item:listItem}",
30591 * controller: function($scope, $stateParams) {
30592 * console.log($stateParams.item);
30598 * // Changes URL to '/list/3', logs "Ringo" to the console
30599 * $state.go('list', { item: "Ringo" });
30602 * This is a more complex example of a type that relies on dependency injection to
30603 * interact with services, and uses the parameter name from the URL to infer how to
30604 * handle encoding and decoding parameter values:
30607 * // Defines a custom type that gets a value from a service,
30608 * // where each service gets different types of values from
30609 * // a backend API:
30610 * $urlMatcherFactoryProvider.type('dbObject', {}, function(Users, Posts) {
30612 * // Matches up services to URL parameter names
30619 * encode: function(object) {
30620 * // Represent the object in the URL using its unique ID
30621 * return object.id;
30623 * decode: function(value, key) {
30624 * // Look up the object by ID, using the parameter
30625 * // name (key) to call the correct service
30626 * return services[key].findById(value);
30628 * is: function(object, key) {
30629 * // Check that object is a valid dbObject
30630 * return angular.isObject(object) && object.id && services[key];
30632 * equals: function(a, b) {
30633 * // Check the equality of decoded objects by comparing
30634 * // their unique IDs
30635 * return a.id === b.id;
30640 * // In a config() block, you can then attach URLs with
30641 * // type-annotated parameters:
30642 * $stateProvider.state('users', {
30645 * }).state('users.item', {
30646 * url: "/{user:dbObject}",
30647 * controller: function($scope, $stateParams) {
30648 * // $stateParams.user will now be an object returned from
30649 * // the Users service
30655 this.type = function (name, definition, definitionFn) {
30656 if (!isDefined(definition)) return $types[name];
30657 if ($types.hasOwnProperty(name)) throw new Error("A type named '" + name + "' has already been defined.");
30659 $types[name] = new Type(extend({ name: name }, definition));
30660 if (definitionFn) {
30661 typeQueue.push({ name: name, def: definitionFn });
30662 if (!enqueue) flushTypeQueue();
30667 // `flushTypeQueue()` waits until `$urlMatcherFactory` is injected before invoking the queued `definitionFn`s
30668 function flushTypeQueue() {
30669 while(typeQueue.length) {
30670 var type = typeQueue.shift();
30671 if (type.pattern) throw new Error("You cannot override a type's .pattern at runtime.");
30672 angular.extend($types[type.name], injector.invoke(type.def));
30676 // Register default types. Store them in the prototype of $types.
30677 forEach(defaultTypes, function(type, name) { $types[name] = new Type(extend({name: name}, type)); });
30678 $types = inherit($types, {});
30680 /* No need to document $get, since it returns this */
30681 this.$get = ['$injector', function ($injector) {
30682 injector = $injector;
30686 forEach(defaultTypes, function(type, name) {
30687 if (!$types[name]) $types[name] = new Type(type);
30692 this.Param = function Param(id, type, config, location) {
30694 config = unwrapShorthand(config);
30695 type = getType(config, type, location);
30696 var arrayMode = getArrayMode();
30697 type = arrayMode ? type.$asArray(arrayMode, location === "search") : type;
30698 if (type.name === "string" && !arrayMode && location === "path" && config.value === undefined)
30699 config.value = ""; // for 0.2.x; in 0.3.0+ do not automatically default to ""
30700 var isOptional = config.value !== undefined;
30701 var squash = getSquashPolicy(config, isOptional);
30702 var replace = getReplace(config, arrayMode, isOptional, squash);
30704 function unwrapShorthand(config) {
30705 var keys = isObject(config) ? objectKeys(config) : [];
30706 var isShorthand = indexOf(keys, "value") === -1 && indexOf(keys, "type") === -1 &&
30707 indexOf(keys, "squash") === -1 && indexOf(keys, "array") === -1;
30708 if (isShorthand) config = { value: config };
30709 config.$$fn = isInjectable(config.value) ? config.value : function () { return config.value; };
30713 function getType(config, urlType, location) {
30714 if (config.type && urlType) throw new Error("Param '"+id+"' has two type configurations.");
30715 if (urlType) return urlType;
30716 if (!config.type) return (location === "config" ? $types.any : $types.string);
30718 if (angular.isString(config.type))
30719 return $types[config.type];
30720 if (config.type instanceof Type)
30721 return config.type;
30722 return new Type(config.type);
30725 // array config: param name (param[]) overrides default settings. explicit config overrides param name.
30726 function getArrayMode() {
30727 var arrayDefaults = { array: (location === "search" ? "auto" : false) };
30728 var arrayParamNomenclature = id.match(/\[\]$/) ? { array: true } : {};
30729 return extend(arrayDefaults, arrayParamNomenclature, config).array;
30733 * returns false, true, or the squash value to indicate the "default parameter url squash policy".
30735 function getSquashPolicy(config, isOptional) {
30736 var squash = config.squash;
30737 if (!isOptional || squash === false) return false;
30738 if (!isDefined(squash) || squash == null) return defaultSquashPolicy;
30739 if (squash === true || isString(squash)) return squash;
30740 throw new Error("Invalid squash policy: '" + squash + "'. Valid policies: false, true, or arbitrary string");
30743 function getReplace(config, arrayMode, isOptional, squash) {
30744 var replace, configuredKeys, defaultPolicy = [
30745 { from: "", to: (isOptional || arrayMode ? undefined : "") },
30746 { from: null, to: (isOptional || arrayMode ? undefined : "") }
30748 replace = isArray(config.replace) ? config.replace : [];
30749 if (isString(squash))
30750 replace.push({ from: squash, to: undefined });
30751 configuredKeys = map(replace, function(item) { return item.from; } );
30752 return filter(defaultPolicy, function(item) { return indexOf(configuredKeys, item.from) === -1; }).concat(replace);
30756 * [Internal] Get the default value of a parameter, which may be an injectable function.
30758 function $$getDefaultValue() {
30759 if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
30760 var defaultValue = injector.invoke(config.$$fn);
30761 if (defaultValue !== null && defaultValue !== undefined && !self.type.is(defaultValue))
30762 throw new Error("Default value (" + defaultValue + ") for parameter '" + self.id + "' is not an instance of Type (" + self.type.name + ")");
30763 return defaultValue;
30767 * [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the
30768 * default value, which may be the result of an injectable function.
30770 function $value(value) {
30771 function hasReplaceVal(val) { return function(obj) { return obj.from === val; }; }
30772 function $replace(value) {
30773 var replacement = map(filter(self.replace, hasReplaceVal(value)), function(obj) { return obj.to; });
30774 return replacement.length ? replacement[0] : value;
30776 value = $replace(value);
30777 return !isDefined(value) ? $$getDefaultValue() : self.type.$normalize(value);
30780 function toString() { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}"; }
30785 location: location,
30789 isOptional: isOptional,
30791 dynamic: undefined,
30797 function ParamSet(params) {
30798 extend(this, params || {});
30801 ParamSet.prototype = {
30802 $$new: function() {
30803 return inherit(this, extend(new ParamSet(), { $$parent: this}));
30805 $$keys: function () {
30806 var keys = [], chain = [], parent = this,
30807 ignore = objectKeys(ParamSet.prototype);
30808 while (parent) { chain.push(parent); parent = parent.$$parent; }
30810 forEach(chain, function(paramset) {
30811 forEach(objectKeys(paramset), function(key) {
30812 if (indexOf(keys, key) === -1 && indexOf(ignore, key) === -1) keys.push(key);
30817 $$values: function(paramValues) {
30818 var values = {}, self = this;
30819 forEach(self.$$keys(), function(key) {
30820 values[key] = self[key].value(paramValues && paramValues[key]);
30824 $$equals: function(paramValues1, paramValues2) {
30825 var equal = true, self = this;
30826 forEach(self.$$keys(), function(key) {
30827 var left = paramValues1 && paramValues1[key], right = paramValues2 && paramValues2[key];
30828 if (!self[key].type.equals(left, right)) equal = false;
30832 $$validates: function $$validate(paramValues) {
30833 var keys = this.$$keys(), i, param, rawVal, normalized, encoded;
30834 for (i = 0; i < keys.length; i++) {
30835 param = this[keys[i]];
30836 rawVal = paramValues[keys[i]];
30837 if ((rawVal === undefined || rawVal === null) && param.isOptional)
30838 break; // There was no parameter value, but the param is optional
30839 normalized = param.type.$normalize(rawVal);
30840 if (!param.type.is(normalized))
30841 return false; // The value was not of the correct Type, and could not be decoded to the correct Type
30842 encoded = param.type.encode(normalized);
30843 if (angular.isString(encoded) && !param.type.pattern.exec(encoded))
30844 return false; // The value was of the correct type, but when encoded, did not match the Type's regexp
30848 $$parent: undefined
30851 this.ParamSet = ParamSet;
30854 // Register as a provider so it's available to other providers
30855 angular.module('ui.router.util').provider('$urlMatcherFactory', $UrlMatcherFactory);
30856 angular.module('ui.router.util').run(['$urlMatcherFactory', function($urlMatcherFactory) { }]);
30860 * @name ui.router.router.$urlRouterProvider
30862 * @requires ui.router.util.$urlMatcherFactoryProvider
30863 * @requires $locationProvider
30866 * `$urlRouterProvider` has the responsibility of watching `$location`.
30867 * When `$location` changes it runs through a list of rules one by one until a
30868 * match is found. `$urlRouterProvider` is used behind the scenes anytime you specify
30869 * a url in a state configuration. All urls are compiled into a UrlMatcher object.
30871 * There are several methods on `$urlRouterProvider` that make it useful to use directly
30872 * in your module config.
30874 $UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider'];
30875 function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
30876 var rules = [], otherwise = null, interceptDeferred = false, listener;
30878 // Returns a string that is a prefix of all strings matching the RegExp
30879 function regExpPrefix(re) {
30880 var prefix = /^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(re.source);
30881 return (prefix != null) ? prefix[1].replace(/\\(.)/g, "$1") : '';
30884 // Interpolates matched values into a String.replace()-style pattern
30885 function interpolate(pattern, match) {
30886 return pattern.replace(/\$(\$|\d{1,2})/, function (m, what) {
30887 return match[what === '$' ? 0 : Number(what)];
30893 * @name ui.router.router.$urlRouterProvider#rule
30894 * @methodOf ui.router.router.$urlRouterProvider
30897 * Defines rules that are used by `$urlRouterProvider` to find matches for
30902 * var app = angular.module('app', ['ui.router.router']);
30904 * app.config(function ($urlRouterProvider) {
30905 * // Here's an example of how you might allow case insensitive urls
30906 * $urlRouterProvider.rule(function ($injector, $location) {
30907 * var path = $location.path(),
30908 * normalized = path.toLowerCase();
30910 * if (path !== normalized) {
30911 * return normalized;
30917 * @param {function} rule Handler function that takes `$injector` and `$location`
30918 * services as arguments. You can use them to return a valid path as a string.
30920 * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
30922 this.rule = function (rule) {
30923 if (!isFunction(rule)) throw new Error("'rule' must be a function");
30930 * @name ui.router.router.$urlRouterProvider#otherwise
30931 * @methodOf ui.router.router.$urlRouterProvider
30934 * Defines a path that is used when an invalid route is requested.
30938 * var app = angular.module('app', ['ui.router.router']);
30940 * app.config(function ($urlRouterProvider) {
30941 * // if the path doesn't match any of the urls you configured
30942 * // otherwise will take care of routing the user to the
30944 * $urlRouterProvider.otherwise('/index');
30946 * // Example of using function rule as param
30947 * $urlRouterProvider.otherwise(function ($injector, $location) {
30948 * return '/a/valid/url';
30953 * @param {string|function} rule The url path you want to redirect to or a function
30954 * rule that returns the url path. The function version is passed two params:
30955 * `$injector` and `$location` services, and must return a url string.
30957 * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
30959 this.otherwise = function (rule) {
30960 if (isString(rule)) {
30961 var redirect = rule;
30962 rule = function () { return redirect; };
30964 else if (!isFunction(rule)) throw new Error("'rule' must be a function");
30970 function handleIfMatch($injector, handler, match) {
30971 if (!match) return false;
30972 var result = $injector.invoke(handler, handler, { $match: match });
30973 return isDefined(result) ? result : true;
30978 * @name ui.router.router.$urlRouterProvider#when
30979 * @methodOf ui.router.router.$urlRouterProvider
30982 * Registers a handler for a given url matching.
30984 * If the handler is a string, it is
30985 * treated as a redirect, and is interpolated according to the syntax of match
30986 * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise).
30988 * If the handler is a function, it is injectable. It gets invoked if `$location`
30989 * matches. You have the option of inject the match object as `$match`.
30991 * The handler can return
30993 * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter`
30994 * will continue trying to find another one that matches.
30995 * - **string** which is treated as a redirect and passed to `$location.url()`
30996 * - **void** or any **truthy** value tells `$urlRouter` that the url was handled.
31000 * var app = angular.module('app', ['ui.router.router']);
31002 * app.config(function ($urlRouterProvider) {
31003 * $urlRouterProvider.when($state.url, function ($match, $stateParams) {
31004 * if ($state.$current.navigable !== state ||
31005 * !equalForKeys($match, $stateParams) {
31006 * $state.transitionTo(state, $match, false);
31012 * @param {string|object} what The incoming path that you want to redirect.
31013 * @param {string|function} handler The path you want to redirect your user to.
31015 this.when = function (what, handler) {
31016 var redirect, handlerIsString = isString(handler);
31017 if (isString(what)) what = $urlMatcherFactory.compile(what);
31019 if (!handlerIsString && !isFunction(handler) && !isArray(handler))
31020 throw new Error("invalid 'handler' in when()");
31023 matcher: function (what, handler) {
31024 if (handlerIsString) {
31025 redirect = $urlMatcherFactory.compile(handler);
31026 handler = ['$match', function ($match) { return redirect.format($match); }];
31028 return extend(function ($injector, $location) {
31029 return handleIfMatch($injector, handler, what.exec($location.path(), $location.search()));
31031 prefix: isString(what.prefix) ? what.prefix : ''
31034 regex: function (what, handler) {
31035 if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky");
31037 if (handlerIsString) {
31038 redirect = handler;
31039 handler = ['$match', function ($match) { return interpolate(redirect, $match); }];
31041 return extend(function ($injector, $location) {
31042 return handleIfMatch($injector, handler, what.exec($location.path()));
31044 prefix: regExpPrefix(what)
31049 var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp };
31051 for (var n in check) {
31052 if (check[n]) return this.rule(strategies[n](what, handler));
31055 throw new Error("invalid 'what' in when()");
31060 * @name ui.router.router.$urlRouterProvider#deferIntercept
31061 * @methodOf ui.router.router.$urlRouterProvider
31064 * Disables (or enables) deferring location change interception.
31066 * If you wish to customize the behavior of syncing the URL (for example, if you wish to
31067 * defer a transition but maintain the current URL), call this method at configuration time.
31068 * Then, at run time, call `$urlRouter.listen()` after you have configured your own
31069 * `$locationChangeSuccess` event handler.
31073 * var app = angular.module('app', ['ui.router.router']);
31075 * app.config(function ($urlRouterProvider) {
31077 * // Prevent $urlRouter from automatically intercepting URL changes;
31078 * // this allows you to configure custom behavior in between
31079 * // location changes and route synchronization:
31080 * $urlRouterProvider.deferIntercept();
31082 * }).run(function ($rootScope, $urlRouter, UserService) {
31084 * $rootScope.$on('$locationChangeSuccess', function(e) {
31085 * // UserService is an example service for managing user state
31086 * if (UserService.isLoggedIn()) return;
31088 * // Prevent $urlRouter's default handler from firing
31089 * e.preventDefault();
31091 * UserService.handleLogin().then(function() {
31092 * // Once the user has logged in, sync the current URL
31093 * // to the router:
31094 * $urlRouter.sync();
31098 * // Configures $urlRouter's listener *after* your custom listener
31099 * $urlRouter.listen();
31103 * @param {boolean} defer Indicates whether to defer location change interception. Passing
31104 no parameter is equivalent to `true`.
31106 this.deferIntercept = function (defer) {
31107 if (defer === undefined) defer = true;
31108 interceptDeferred = defer;
31113 * @name ui.router.router.$urlRouter
31115 * @requires $location
31116 * @requires $rootScope
31117 * @requires $injector
31118 * @requires $browser
31124 $get.$inject = ['$location', '$rootScope', '$injector', '$browser', '$sniffer'];
31125 function $get( $location, $rootScope, $injector, $browser, $sniffer) {
31127 var baseHref = $browser.baseHref(), location = $location.url(), lastPushedUrl;
31129 function appendBasePath(url, isHtml5, absolute) {
31130 if (baseHref === '/') return url;
31131 if (isHtml5) return baseHref.slice(0, -1) + url;
31132 if (absolute) return baseHref.slice(1) + url;
31136 // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree
31137 function update(evt) {
31138 if (evt && evt.defaultPrevented) return;
31139 var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl;
31140 lastPushedUrl = undefined;
31141 // TODO: Re-implement this in 1.0 for https://github.com/angular-ui/ui-router/issues/1573
31142 //if (ignoreUpdate) return true;
31144 function check(rule) {
31145 var handled = rule($injector, $location);
31147 if (!handled) return false;
31148 if (isString(handled)) $location.replace().url(handled);
31151 var n = rules.length, i;
31153 for (i = 0; i < n; i++) {
31154 if (check(rules[i])) return;
31156 // always check otherwise last to allow dynamic updates to the set of rules
31157 if (otherwise) check(otherwise);
31160 function listen() {
31161 listener = listener || $rootScope.$on('$locationChangeSuccess', update);
31165 if (!interceptDeferred) listen();
31170 * @name ui.router.router.$urlRouter#sync
31171 * @methodOf ui.router.router.$urlRouter
31174 * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`.
31175 * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event,
31176 * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed
31177 * with the transition by calling `$urlRouter.sync()`.
31181 * angular.module('app', ['ui.router'])
31182 * .run(function($rootScope, $urlRouter) {
31183 * $rootScope.$on('$locationChangeSuccess', function(evt) {
31184 * // Halt state change from even starting
31185 * evt.preventDefault();
31186 * // Perform custom logic
31187 * var meetsRequirement = ...
31188 * // Continue with the update and state transition if logic allows
31189 * if (meetsRequirement) $urlRouter.sync();
31198 listen: function() {
31202 update: function(read) {
31204 location = $location.url();
31207 if ($location.url() === location) return;
31209 $location.url(location);
31210 $location.replace();
31213 push: function(urlMatcher, params, options) {
31214 var url = urlMatcher.format(params || {});
31216 // Handle the special hash param, if needed
31217 if (url !== null && params && params['#']) {
31218 url += '#' + params['#'];
31221 $location.url(url);
31222 lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined;
31223 if (options && options.replace) $location.replace();
31228 * @name ui.router.router.$urlRouter#href
31229 * @methodOf ui.router.router.$urlRouter
31232 * A URL generation method that returns the compiled URL for a given
31233 * {@link ui.router.util.type:UrlMatcher `UrlMatcher`}, populated with the provided parameters.
31237 * $bob = $urlRouter.href(new UrlMatcher("/about/:person"), {
31240 * // $bob == "/about/bob";
31243 * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate.
31244 * @param {object=} params An object of parameter values to fill the matcher's required parameters.
31245 * @param {object=} options Options object. The options are:
31247 * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
31249 * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher`
31251 href: function(urlMatcher, params, options) {
31252 if (!urlMatcher.validates(params)) return null;
31254 var isHtml5 = $locationProvider.html5Mode();
31255 if (angular.isObject(isHtml5)) {
31256 isHtml5 = isHtml5.enabled;
31259 isHtml5 = isHtml5 && $sniffer.history;
31261 var url = urlMatcher.format(params);
31262 options = options || {};
31264 if (!isHtml5 && url !== null) {
31265 url = "#" + $locationProvider.hashPrefix() + url;
31268 // Handle special hash param, if needed
31269 if (url !== null && params && params['#']) {
31270 url += '#' + params['#'];
31273 url = appendBasePath(url, isHtml5, options.absolute);
31275 if (!options.absolute || !url) {
31279 var slash = (!isHtml5 && url ? '/' : ''), port = $location.port();
31280 port = (port === 80 || port === 443 ? '' : ':' + port);
31282 return [$location.protocol(), '://', $location.host(), port, slash, url].join('');
31288 angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);
31292 * @name ui.router.state.$stateProvider
31294 * @requires ui.router.router.$urlRouterProvider
31295 * @requires ui.router.util.$urlMatcherFactoryProvider
31298 * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely
31301 * A state corresponds to a "place" in the application in terms of the overall UI and
31302 * navigation. A state describes (via the controller / template / view properties) what
31303 * the UI looks like and does at that place.
31305 * States often have things in common, and the primary way of factoring out these
31306 * commonalities in this model is via the state hierarchy, i.e. parent/child states aka
31309 * The `$stateProvider` provides interfaces to declare these states for your app.
31311 $StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider'];
31312 function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
31314 var root, states = {}, $state, queue = {}, abstractKey = 'abstract';
31316 // Builds state properties from definition passed to registerState()
31317 var stateBuilder = {
31319 // Derive parent state from a hierarchical name only if 'parent' is not explicitly defined.
31320 // state.children = [];
31321 // if (parent) parent.children.push(state);
31322 parent: function(state) {
31323 if (isDefined(state.parent) && state.parent) return findState(state.parent);
31324 // regex matches any valid composite state name
31325 // would match "contact.list" but not "contacts"
31326 var compositeName = /^(.+)\.[^.]+$/.exec(state.name);
31327 return compositeName ? findState(compositeName[1]) : root;
31330 // inherit 'data' from parent and override by own values (if any)
31331 data: function(state) {
31332 if (state.parent && state.parent.data) {
31333 state.data = state.self.data = inherit(state.parent.data, state.data);
31338 // Build a URLMatcher if necessary, either via a relative or absolute URL
31339 url: function(state) {
31340 var url = state.url, config = { params: state.params || {} };
31342 if (isString(url)) {
31343 if (url.charAt(0) == '^') return $urlMatcherFactory.compile(url.substring(1), config);
31344 return (state.parent.navigable || root).url.concat(url, config);
31347 if (!url || $urlMatcherFactory.isMatcher(url)) return url;
31348 throw new Error("Invalid url '" + url + "' in state '" + state + "'");
31351 // Keep track of the closest ancestor state that has a URL (i.e. is navigable)
31352 navigable: function(state) {
31353 return state.url ? state : (state.parent ? state.parent.navigable : null);
31356 // Own parameters for this state. state.url.params is already built at this point. Create and add non-url params
31357 ownParams: function(state) {
31358 var params = state.url && state.url.params || new $$UMFP.ParamSet();
31359 forEach(state.params || {}, function(config, id) {
31360 if (!params[id]) params[id] = new $$UMFP.Param(id, null, config, "config");
31365 // Derive parameters for this state and ensure they're a super-set of parent's parameters
31366 params: function(state) {
31367 var ownParams = pick(state.ownParams, state.ownParams.$$keys());
31368 return state.parent && state.parent.params ? extend(state.parent.params.$$new(), ownParams) : new $$UMFP.ParamSet();
31371 // If there is no explicit multi-view configuration, make one up so we don't have
31372 // to handle both cases in the view directive later. Note that having an explicit
31373 // 'views' property will mean the default unnamed view properties are ignored. This
31374 // is also a good time to resolve view names to absolute names, so everything is a
31375 // straight lookup at link time.
31376 views: function(state) {
31379 forEach(isDefined(state.views) ? state.views : { '': state }, function (view, name) {
31380 if (name.indexOf('@') < 0) name += '@' + state.parent.name;
31381 views[name] = view;
31386 // Keep a full path from the root down to this state as this is needed for state activation.
31387 path: function(state) {
31388 return state.parent ? state.parent.path.concat(state) : []; // exclude root from path
31391 // Speed up $state.contains() as it's used a lot
31392 includes: function(state) {
31393 var includes = state.parent ? extend({}, state.parent.includes) : {};
31394 includes[state.name] = true;
31401 function isRelative(stateName) {
31402 return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0;
31405 function findState(stateOrName, base) {
31406 if (!stateOrName) return undefined;
31408 var isStr = isString(stateOrName),
31409 name = isStr ? stateOrName : stateOrName.name,
31410 path = isRelative(name);
31413 if (!base) throw new Error("No reference point given for path '" + name + "'");
31414 base = findState(base);
31416 var rel = name.split("."), i = 0, pathLength = rel.length, current = base;
31418 for (; i < pathLength; i++) {
31419 if (rel[i] === "" && i === 0) {
31423 if (rel[i] === "^") {
31424 if (!current.parent) throw new Error("Path '" + name + "' not valid for state '" + base.name + "'");
31425 current = current.parent;
31430 rel = rel.slice(i).join(".");
31431 name = current.name + (current.name && rel ? "." : "") + rel;
31433 var state = states[name];
31435 if (state && (isStr || (!isStr && (state === stateOrName || state.self === stateOrName)))) {
31441 function queueState(parentName, state) {
31442 if (!queue[parentName]) {
31443 queue[parentName] = [];
31445 queue[parentName].push(state);
31448 function flushQueuedChildren(parentName) {
31449 var queued = queue[parentName] || [];
31450 while(queued.length) {
31451 registerState(queued.shift());
31455 function registerState(state) {
31456 // Wrap a new object around the state so we can store our private details easily.
31457 state = inherit(state, {
31459 resolve: state.resolve || {},
31460 toString: function() { return this.name; }
31463 var name = state.name;
31464 if (!isString(name) || name.indexOf('@') >= 0) throw new Error("State must have a valid name");
31465 if (states.hasOwnProperty(name)) throw new Error("State '" + name + "' is already defined");
31468 var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.'))
31469 : (isString(state.parent)) ? state.parent
31470 : (isObject(state.parent) && isString(state.parent.name)) ? state.parent.name
31473 // If parent is not registered yet, add state to queue and register later
31474 if (parentName && !states[parentName]) {
31475 return queueState(parentName, state.self);
31478 for (var key in stateBuilder) {
31479 if (isFunction(stateBuilder[key])) state[key] = stateBuilder[key](state, stateBuilder.$delegates[key]);
31481 states[name] = state;
31483 // Register the state in the global state list and with $urlRouter if necessary.
31484 if (!state[abstractKey] && state.url) {
31485 $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) {
31486 if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) {
31487 $state.transitionTo(state, $match, { inherit: true, location: false });
31492 // Register any queued children
31493 flushQueuedChildren(name);
31498 // Checks text to see if it looks like a glob.
31499 function isGlob (text) {
31500 return text.indexOf('*') > -1;
31503 // Returns true if glob matches current $state name.
31504 function doesStateMatchGlob (glob) {
31505 var globSegments = glob.split('.'),
31506 segments = $state.$current.name.split('.');
31508 //match single stars
31509 for (var i = 0, l = globSegments.length; i < l; i++) {
31510 if (globSegments[i] === '*') {
31515 //match greedy starts
31516 if (globSegments[0] === '**') {
31517 segments = segments.slice(indexOf(segments, globSegments[1]));
31518 segments.unshift('**');
31520 //match greedy ends
31521 if (globSegments[globSegments.length - 1] === '**') {
31522 segments.splice(indexOf(segments, globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE);
31523 segments.push('**');
31526 if (globSegments.length != segments.length) {
31530 return segments.join('') === globSegments.join('');
31534 // Implicit root state that is always active
31535 root = registerState({
31541 root.navigable = null;
31546 * @name ui.router.state.$stateProvider#decorator
31547 * @methodOf ui.router.state.$stateProvider
31550 * Allows you to extend (carefully) or override (at your own peril) the
31551 * `stateBuilder` object used internally by `$stateProvider`. This can be used
31552 * to add custom functionality to ui-router, for example inferring templateUrl
31553 * based on the state name.
31555 * When passing only a name, it returns the current (original or decorated) builder
31556 * function that matches `name`.
31558 * The builder functions that can be decorated are listed below. Though not all
31559 * necessarily have a good use case for decoration, that is up to you to decide.
31561 * In addition, users can attach custom decorators, which will generate new
31562 * properties within the state's internal definition. There is currently no clear
31563 * use-case for this beyond accessing internal states (i.e. $state.$current),
31564 * however, expect this to become increasingly relevant as we introduce additional
31565 * meta-programming features.
31567 * **Warning**: Decorators should not be interdependent because the order of
31568 * execution of the builder functions in non-deterministic. Builder functions
31569 * should only be dependent on the state definition object and super function.
31572 * Existing builder functions and current return values:
31574 * - **parent** `{object}` - returns the parent state object.
31575 * - **data** `{object}` - returns state data, including any inherited data that is not
31576 * overridden by own values (if any).
31577 * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher}
31579 * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is
31581 * - **params** `{object}` - returns an array of state params that are ensured to
31582 * be a super-set of parent's params.
31583 * - **views** `{object}` - returns a views object where each key is an absolute view
31584 * name (i.e. "viewName@stateName") and each value is the config object
31585 * (template, controller) for the view. Even when you don't use the views object
31586 * explicitly on a state config, one is still created for you internally.
31587 * So by decorating this builder function you have access to decorating template
31588 * and controller properties.
31589 * - **ownParams** `{object}` - returns an array of params that belong to the state,
31590 * not including any params defined by ancestor states.
31591 * - **path** `{string}` - returns the full path from the root down to this state.
31592 * Needed for state activation.
31593 * - **includes** `{object}` - returns an object that includes every state that
31594 * would pass a `$state.includes()` test.
31598 * // Override the internal 'views' builder with a function that takes the state
31599 * // definition, and a reference to the internal function being overridden:
31600 * $stateProvider.decorator('views', function (state, parent) {
31602 * views = parent(state);
31604 * angular.forEach(views, function (config, name) {
31605 * var autoName = (state.name + '.' + name).replace('.', '/');
31606 * config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';
31607 * result[name] = config;
31612 * $stateProvider.state('home', {
31614 * 'contact.list': { controller: 'ListController' },
31615 * 'contact.item': { controller: 'ItemController' }
31621 * $state.go('home');
31622 * // Auto-populates list and item views with /partials/home/contact/list.html,
31623 * // and /partials/home/contact/item.html, respectively.
31626 * @param {string} name The name of the builder function to decorate.
31627 * @param {object} func A function that is responsible for decorating the original
31628 * builder function. The function receives two parameters:
31630 * - `{object}` - state - The state config object.
31631 * - `{object}` - super - The original builder function.
31633 * @return {object} $stateProvider - $stateProvider instance
31635 this.decorator = decorator;
31636 function decorator(name, func) {
31637 /*jshint validthis: true */
31638 if (isString(name) && !isDefined(func)) {
31639 return stateBuilder[name];
31641 if (!isFunction(func) || !isString(name)) {
31644 if (stateBuilder[name] && !stateBuilder.$delegates[name]) {
31645 stateBuilder.$delegates[name] = stateBuilder[name];
31647 stateBuilder[name] = func;
31653 * @name ui.router.state.$stateProvider#state
31654 * @methodOf ui.router.state.$stateProvider
31657 * Registers a state configuration under a given state name. The stateConfig object
31658 * has the following acceptable properties.
31660 * @param {string} name A unique state name, e.g. "home", "about", "contacts".
31661 * To create a parent/child state use a dot, e.g. "about.sales", "home.newest".
31662 * @param {object} stateConfig State configuration object.
31663 * @param {string|function=} stateConfig.template
31664 * <a id='template'></a>
31665 * html template as a string or a function that returns
31666 * an html template as a string which should be used by the uiView directives. This property
31667 * takes precedence over templateUrl.
31669 * If `template` is a function, it will be called with the following parameters:
31671 * - {array.<object>} - state parameters extracted from the current $location.path() by
31672 * applying the current state
31675 * "<h1>inline template definition</h1>" +
31676 * "<div ui-view></div>"</pre>
31677 * <pre>template: function(params) {
31678 * return "<h1>generated template</h1>"; }</pre>
31681 * @param {string|function=} stateConfig.templateUrl
31682 * <a id='templateUrl'></a>
31684 * path or function that returns a path to an html
31685 * template that should be used by uiView.
31687 * If `templateUrl` is a function, it will be called with the following parameters:
31689 * - {array.<object>} - state parameters extracted from the current $location.path() by
31690 * applying the current state
31692 * <pre>templateUrl: "home.html"</pre>
31693 * <pre>templateUrl: function(params) {
31694 * return myTemplates[params.pageId]; }</pre>
31696 * @param {function=} stateConfig.templateProvider
31697 * <a id='templateProvider'></a>
31698 * Provider function that returns HTML content string.
31699 * <pre> templateProvider:
31700 * function(MyTemplateService, params) {
31701 * return MyTemplateService.getTemplate(params.pageId);
31704 * @param {string|function=} stateConfig.controller
31705 * <a id='controller'></a>
31707 * Controller fn that should be associated with newly
31708 * related scope or the name of a registered controller if passed as a string.
31709 * Optionally, the ControllerAs may be declared here.
31710 * <pre>controller: "MyRegisteredController"</pre>
31712 * "MyRegisteredController as fooCtrl"}</pre>
31713 * <pre>controller: function($scope, MyService) {
31714 * $scope.data = MyService.getData(); }</pre>
31716 * @param {function=} stateConfig.controllerProvider
31717 * <a id='controllerProvider'></a>
31719 * Injectable provider function that returns the actual controller or string.
31720 * <pre>controllerProvider:
31721 * function(MyResolveData) {
31722 * if (MyResolveData.foo)
31724 * else if (MyResolveData.bar)
31725 * return "BarCtrl";
31726 * else return function($scope) {
31727 * $scope.baz = "Qux";
31731 * @param {string=} stateConfig.controllerAs
31732 * <a id='controllerAs'></a>
31734 * A controller alias name. If present the controller will be
31735 * published to scope under the controllerAs name.
31736 * <pre>controllerAs: "myCtrl"</pre>
31738 * @param {string|object=} stateConfig.parent
31739 * <a id='parent'></a>
31740 * Optionally specifies the parent state of this state.
31742 * <pre>parent: 'parentState'</pre>
31743 * <pre>parent: parentState // JS variable</pre>
31745 * @param {object=} stateConfig.resolve
31746 * <a id='resolve'></a>
31748 * An optional map<string, function> of dependencies which
31749 * should be injected into the controller. If any of these dependencies are promises,
31750 * the router will wait for them all to be resolved before the controller is instantiated.
31751 * If all the promises are resolved successfully, the $stateChangeSuccess event is fired
31752 * and the values of the resolved promises are injected into any controllers that reference them.
31753 * If any of the promises are rejected the $stateChangeError event is fired.
31755 * The map object is:
31757 * - key - {string}: name of dependency to be injected into controller
31758 * - factory - {string|function}: If string then it is alias for service. Otherwise if function,
31759 * it is injected and return value it treated as dependency. If result is a promise, it is
31760 * resolved before its value is injected into controller.
31764 * function($http, $stateParams) {
31765 * return $http.get("/api/foos/"+stateParams.fooID);
31769 * @param {string=} stateConfig.url
31772 * A url fragment with optional parameters. When a state is navigated or
31773 * transitioned to, the `$stateParams` service will be populated with any
31774 * parameters that were passed.
31776 * (See {@link ui.router.util.type:UrlMatcher UrlMatcher} `UrlMatcher`} for
31777 * more details on acceptable patterns )
31780 * <pre>url: "/home"
31781 * url: "/users/:userid"
31782 * url: "/books/{bookid:[a-zA-Z_-]}"
31783 * url: "/books/{categoryid:int}"
31784 * url: "/books/{publishername:string}/{categoryid:int}"
31785 * url: "/messages?before&after"
31786 * url: "/messages?{before:date}&{after:date}"
31787 * url: "/messages/:mailboxid?{before:date}&{after:date}"
31790 * @param {object=} stateConfig.views
31791 * <a id='views'></a>
31792 * an optional map<string, object> which defined multiple views, or targets views
31793 * manually/explicitly.
31797 * Targets three named `ui-view`s in the parent state's template
31800 * controller: "headerCtrl",
31801 * templateUrl: "header.html"
31803 * controller: "bodyCtrl",
31804 * templateUrl: "body.html"
31806 * controller: "footCtrl",
31807 * templateUrl: "footer.html"
31811 * Targets named `ui-view="header"` from grandparent state 'top''s template, and named `ui-view="body" from parent state's template.
31814 * controller: "msgHeaderCtrl",
31815 * templateUrl: "msgHeader.html"
31817 * controller: "messagesCtrl",
31818 * templateUrl: "messages.html"
31822 * @param {boolean=} [stateConfig.abstract=false]
31823 * <a id='abstract'></a>
31824 * An abstract state will never be directly activated,
31825 * but can provide inherited properties to its common children states.
31826 * <pre>abstract: true</pre>
31828 * @param {function=} stateConfig.onEnter
31829 * <a id='onEnter'></a>
31831 * Callback function for when a state is entered. Good way
31832 * to trigger an action or dispatch an event, such as opening a dialog.
31833 * If minifying your scripts, make sure to explicitly annotate this function,
31834 * because it won't be automatically annotated by your build tools.
31836 * <pre>onEnter: function(MyService, $stateParams) {
31837 * MyService.foo($stateParams.myParam);
31840 * @param {function=} stateConfig.onExit
31841 * <a id='onExit'></a>
31843 * Callback function for when a state is exited. Good way to
31844 * trigger an action or dispatch an event, such as opening a dialog.
31845 * If minifying your scripts, make sure to explicitly annotate this function,
31846 * because it won't be automatically annotated by your build tools.
31848 * <pre>onExit: function(MyService, $stateParams) {
31849 * MyService.cleanup($stateParams.myParam);
31852 * @param {boolean=} [stateConfig.reloadOnSearch=true]
31853 * <a id='reloadOnSearch'></a>
31855 * If `false`, will not retrigger the same state
31856 * just because a search/query parameter has changed (via $location.search() or $location.hash()).
31857 * Useful for when you'd like to modify $location.search() without triggering a reload.
31858 * <pre>reloadOnSearch: false</pre>
31860 * @param {object=} stateConfig.data
31861 * <a id='data'></a>
31863 * Arbitrary data object, useful for custom configuration. The parent state's `data` is
31864 * prototypally inherited. In other words, adding a data property to a state adds it to
31865 * the entire subtree via prototypal inheritance.
31868 * requiredRole: 'foo'
31871 * @param {object=} stateConfig.params
31872 * <a id='params'></a>
31874 * A map which optionally configures parameters declared in the `url`, or
31875 * defines additional non-url parameters. For each parameter being
31876 * configured, add a configuration object keyed to the name of the parameter.
31878 * Each parameter configuration object may contain the following properties:
31880 * - ** value ** - {object|function=}: specifies the default value for this
31881 * parameter. This implicitly sets this parameter as optional.
31883 * When UI-Router routes to a state and no value is
31884 * specified for this parameter in the URL or transition, the
31885 * default value will be used instead. If `value` is a function,
31886 * it will be injected and invoked, and the return value used.
31888 * *Note*: `undefined` is treated as "no default value" while `null`
31889 * is treated as "the default value is `null`".
31891 * *Shorthand*: If you only need to configure the default value of the
31892 * parameter, you may use a shorthand syntax. In the **`params`**
31893 * map, instead mapping the param name to a full parameter configuration
31894 * object, simply set map it to the default parameter value, e.g.:
31896 * <pre>// define a parameter's default value
31898 * param1: { value: "defaultValue" }
31900 * // shorthand default values
31902 * param1: "defaultValue",
31903 * param2: "param2Default"
31906 * - ** array ** - {boolean=}: *(default: false)* If true, the param value will be
31907 * treated as an array of values. If you specified a Type, the value will be
31908 * treated as an array of the specified Type. Note: query parameter values
31909 * default to a special `"auto"` mode.
31911 * For query parameters in `"auto"` mode, if multiple values for a single parameter
31912 * are present in the URL (e.g.: `/foo?bar=1&bar=2&bar=3`) then the values
31913 * are mapped to an array (e.g.: `{ foo: [ '1', '2', '3' ] }`). However, if
31914 * only one value is present (e.g.: `/foo?bar=1`) then the value is treated as single
31915 * value (e.g.: `{ foo: '1' }`).
31918 * param1: { array: true }
31921 * - ** squash ** - {bool|string=}: `squash` configures how a default parameter value is represented in the URL when
31922 * the current parameter value is the same as the default value. If `squash` is not set, it uses the
31923 * configured default squash policy.
31924 * (See {@link ui.router.util.$urlMatcherFactory#methods_defaultSquashPolicy `defaultSquashPolicy()`})
31926 * There are three squash settings:
31928 * - false: The parameter's default value is not squashed. It is encoded and included in the URL
31929 * - true: The parameter's default value is omitted from the URL. If the parameter is preceeded and followed
31930 * by slashes in the state's `url` declaration, then one of those slashes are omitted.
31931 * This can allow for cleaner looking URLs.
31932 * - `"<arbitrary string>"`: The parameter's default value is replaced with an arbitrary placeholder of your choice.
31936 * value: "defaultId",
31939 * // squash "defaultValue" to "~"
31942 * value: "defaultValue",
31950 * // Some state name examples
31952 * // stateName can be a single top-level name (must be unique).
31953 * $stateProvider.state("home", {});
31955 * // Or it can be a nested state name. This state is a child of the
31956 * // above "home" state.
31957 * $stateProvider.state("home.newest", {});
31959 * // Nest states as deeply as needed.
31960 * $stateProvider.state("home.newest.abc.xyz.inception", {});
31962 * // state() returns $stateProvider, so you can chain state declarations.
31964 * .state("home", {})
31965 * .state("about", {})
31966 * .state("contacts", {});
31970 this.state = state;
31971 function state(name, definition) {
31972 /*jshint validthis: true */
31973 if (isObject(name)) definition = name;
31974 else definition.name = name;
31975 registerState(definition);
31981 * @name ui.router.state.$state
31983 * @requires $rootScope
31985 * @requires ui.router.state.$view
31986 * @requires $injector
31987 * @requires ui.router.util.$resolve
31988 * @requires ui.router.state.$stateParams
31989 * @requires ui.router.router.$urlRouter
31991 * @property {object} params A param object, e.g. {sectionId: section.id)}, that
31992 * you'd like to test against the current active state.
31993 * @property {object} current A reference to the state's config object. However
31994 * you passed it in. Useful for accessing custom data.
31995 * @property {object} transition Currently pending transition. A promise that'll
31996 * resolve or reject.
31999 * `$state` service is responsible for representing states as well as transitioning
32000 * between them. It also provides interfaces to ask for current state or even states
32001 * you're coming from.
32004 $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$urlRouter', '$location', '$urlMatcherFactory'];
32005 function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $urlRouter, $location, $urlMatcherFactory) {
32007 var TransitionSuperseded = $q.reject(new Error('transition superseded'));
32008 var TransitionPrevented = $q.reject(new Error('transition prevented'));
32009 var TransitionAborted = $q.reject(new Error('transition aborted'));
32010 var TransitionFailed = $q.reject(new Error('transition failed'));
32012 // Handles the case where a state which is the target of a transition is not found, and the user
32013 // can optionally retry or defer the transition
32014 function handleRedirect(redirect, state, params, options) {
32017 * @name ui.router.state.$state#$stateNotFound
32018 * @eventOf ui.router.state.$state
32019 * @eventType broadcast on root scope
32021 * Fired when a requested state **cannot be found** using the provided state name during transition.
32022 * The event is broadcast allowing any handlers a single chance to deal with the error (usually by
32023 * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler,
32024 * you can see its three properties in the example. You can use `event.preventDefault()` to abort the
32025 * transition and the promise returned from `go` will be rejected with a `'transition aborted'` value.
32027 * @param {Object} event Event object.
32028 * @param {Object} unfoundState Unfound State information. Contains: `to, toParams, options` properties.
32029 * @param {State} fromState Current state object.
32030 * @param {Object} fromParams Current state params.
32035 * // somewhere, assume lazy.state has not been defined
32036 * $state.go("lazy.state", {a:1, b:2}, {inherit:false});
32038 * // somewhere else
32039 * $scope.$on('$stateNotFound',
32040 * function(event, unfoundState, fromState, fromParams){
32041 * console.log(unfoundState.to); // "lazy.state"
32042 * console.log(unfoundState.toParams); // {a:1, b:2}
32043 * console.log(unfoundState.options); // {inherit:false} + default options
32047 var evt = $rootScope.$broadcast('$stateNotFound', redirect, state, params);
32049 if (evt.defaultPrevented) {
32050 $urlRouter.update();
32051 return TransitionAborted;
32058 // Allow the handler to return a promise to defer state lookup retry
32059 if (options.$retry) {
32060 $urlRouter.update();
32061 return TransitionFailed;
32063 var retryTransition = $state.transition = $q.when(evt.retry);
32065 retryTransition.then(function() {
32066 if (retryTransition !== $state.transition) return TransitionSuperseded;
32067 redirect.options.$retry = true;
32068 return $state.transitionTo(redirect.to, redirect.toParams, redirect.options);
32070 return TransitionAborted;
32072 $urlRouter.update();
32074 return retryTransition;
32077 root.locals = { resolve: null, globals: { $stateParams: {} } };
32081 current: root.self,
32088 * @name ui.router.state.$state#reload
32089 * @methodOf ui.router.state.$state
32092 * A method that force reloads the current state. All resolves are re-resolved,
32093 * controllers reinstantiated, and events re-fired.
32097 * var app angular.module('app', ['ui.router']);
32099 * app.controller('ctrl', function ($scope, $state) {
32100 * $scope.reload = function(){
32106 * `reload()` is just an alias for:
32108 * $state.transitionTo($state.current, $stateParams, {
32109 * reload: true, inherit: false, notify: true
32113 * @param {string=|object=} state - A state name or a state object, which is the root of the resolves to be re-resolved.
32116 * //assuming app application consists of 3 states: 'contacts', 'contacts.detail', 'contacts.detail.item'
32117 * //and current state is 'contacts.detail.item'
32118 * var app angular.module('app', ['ui.router']);
32120 * app.controller('ctrl', function ($scope, $state) {
32121 * $scope.reload = function(){
32122 * //will reload 'contact.detail' and 'contact.detail.item' states
32123 * $state.reload('contact.detail');
32128 * `reload()` is just an alias for:
32130 * $state.transitionTo($state.current, $stateParams, {
32131 * reload: true, inherit: false, notify: true
32135 * @returns {promise} A promise representing the state of the new transition. See
32136 * {@link ui.router.state.$state#methods_go $state.go}.
32138 $state.reload = function reload(state) {
32139 return $state.transitionTo($state.current, $stateParams, { reload: state || true, inherit: false, notify: true});
32144 * @name ui.router.state.$state#go
32145 * @methodOf ui.router.state.$state
32148 * Convenience method for transitioning to a new state. `$state.go` calls
32149 * `$state.transitionTo` internally but automatically sets options to
32150 * `{ location: true, inherit: true, relative: $state.$current, notify: true }`.
32151 * This allows you to easily use an absolute or relative to path and specify
32152 * only the parameters you'd like to update (while letting unspecified parameters
32153 * inherit from the currently active ancestor states).
32157 * var app = angular.module('app', ['ui.router']);
32159 * app.controller('ctrl', function ($scope, $state) {
32160 * $scope.changeState = function () {
32161 * $state.go('contact.detail');
32165 * <img src='../ngdoc_assets/StateGoExamples.png'/>
32167 * @param {string} to Absolute state name or relative state path. Some examples:
32169 * - `$state.go('contact.detail')` - will go to the `contact.detail` state
32170 * - `$state.go('^')` - will go to a parent state
32171 * - `$state.go('^.sibling')` - will go to a sibling state
32172 * - `$state.go('.child.grandchild')` - will go to grandchild state
32174 * @param {object=} params A map of the parameters that will be sent to the state,
32175 * will populate $stateParams. Any parameters that are not specified will be inherited from currently
32176 * defined parameters. Only parameters specified in the state definition can be overridden, new
32177 * parameters will be ignored. This allows, for example, going to a sibling state that shares parameters
32178 * specified in a parent state. Parameter inheritance only works between common ancestor states, I.e.
32179 * transitioning to a sibling will get you the parameters for all parents, transitioning to a child
32180 * will get you all current parameters, etc.
32181 * @param {object=} options Options object. The options are:
32183 * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
32184 * will not. If string, must be `"replace"`, which will update url and also replace last history record.
32185 * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
32186 * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
32187 * defines which state to be relative from.
32188 * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
32189 * - **`reload`** (v0.2.5) - {boolean=false|string|object}, If `true` will force transition even if no state or params
32190 * have changed. It will reload the resolves and views of the current state and parent states.
32191 * If `reload` is a string (or state object), the state object is fetched (by name, or object reference); and \
32192 * the transition reloads the resolves and views for that matched state, and all its children states.
32194 * @returns {promise} A promise representing the state of the new transition.
32196 * Possible success values:
32200 * <br/>Possible rejection values:
32202 * - 'transition superseded' - when a newer transition has been started after this one
32203 * - 'transition prevented' - when `event.preventDefault()` has been called in a `$stateChangeStart` listener
32204 * - 'transition aborted' - when `event.preventDefault()` has been called in a `$stateNotFound` listener or
32205 * when a `$stateNotFound` `event.retry` promise errors.
32206 * - 'transition failed' - when a state has been unsuccessfully found after 2 tries.
32207 * - *resolve error* - when an error has occurred with a `resolve`
32210 $state.go = function go(to, params, options) {
32211 return $state.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options));
32216 * @name ui.router.state.$state#transitionTo
32217 * @methodOf ui.router.state.$state
32220 * Low-level method for transitioning to a new state. {@link ui.router.state.$state#methods_go $state.go}
32221 * uses `transitionTo` internally. `$state.go` is recommended in most situations.
32225 * var app = angular.module('app', ['ui.router']);
32227 * app.controller('ctrl', function ($scope, $state) {
32228 * $scope.changeState = function () {
32229 * $state.transitionTo('contact.detail');
32234 * @param {string} to State name.
32235 * @param {object=} toParams A map of the parameters that will be sent to the state,
32236 * will populate $stateParams.
32237 * @param {object=} options Options object. The options are:
32239 * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
32240 * will not. If string, must be `"replace"`, which will update url and also replace last history record.
32241 * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url.
32242 * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'),
32243 * defines which state to be relative from.
32244 * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
32245 * - **`reload`** (v0.2.5) - {boolean=false|string=|object=}, If `true` will force transition even if the state or params
32246 * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
32247 * use this when you want to force a reload when *everything* is the same, including search params.
32248 * if String, then will reload the state with the name given in reload, and any children.
32249 * if Object, then a stateObj is expected, will reload the state found in stateObj, and any children.
32251 * @returns {promise} A promise representing the state of the new transition. See
32252 * {@link ui.router.state.$state#methods_go $state.go}.
32254 $state.transitionTo = function transitionTo(to, toParams, options) {
32255 toParams = toParams || {};
32257 location: true, inherit: false, relative: null, notify: true, reload: false, $retry: false
32260 var from = $state.$current, fromParams = $state.params, fromPath = from.path;
32261 var evt, toState = findState(to, options.relative);
32263 // Store the hash param for later (since it will be stripped out by various methods)
32264 var hash = toParams['#'];
32266 if (!isDefined(toState)) {
32267 var redirect = { to: to, toParams: toParams, options: options };
32268 var redirectResult = handleRedirect(redirect, from.self, fromParams, options);
32270 if (redirectResult) {
32271 return redirectResult;
32274 // Always retry once if the $stateNotFound was not prevented
32275 // (handles either redirect changed or state lazy-definition)
32277 toParams = redirect.toParams;
32278 options = redirect.options;
32279 toState = findState(to, options.relative);
32281 if (!isDefined(toState)) {
32282 if (!options.relative) throw new Error("No such state '" + to + "'");
32283 throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'");
32286 if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'");
32287 if (options.inherit) toParams = inheritParams($stateParams, toParams || {}, $state.$current, toState);
32288 if (!toState.params.$$validates(toParams)) return TransitionFailed;
32290 toParams = toState.params.$$values(toParams);
32293 var toPath = to.path;
32295 // Starting from the root of the path, keep all levels that haven't changed
32296 var keep = 0, state = toPath[keep], locals = root.locals, toLocals = [];
32298 if (!options.reload) {
32299 while (state && state === fromPath[keep] && state.ownParams.$$equals(toParams, fromParams)) {
32300 locals = toLocals[keep] = state.locals;
32302 state = toPath[keep];
32304 } else if (isString(options.reload) || isObject(options.reload)) {
32305 if (isObject(options.reload) && !options.reload.name) {
32306 throw new Error('Invalid reload state object');
32309 var reloadState = options.reload === true ? fromPath[0] : findState(options.reload);
32310 if (options.reload && !reloadState) {
32311 throw new Error("No such reload state '" + (isString(options.reload) ? options.reload : options.reload.name) + "'");
32314 while (state && state === fromPath[keep] && state !== reloadState) {
32315 locals = toLocals[keep] = state.locals;
32317 state = toPath[keep];
32321 // If we're going to the same state and all locals are kept, we've got nothing to do.
32322 // But clear 'transition', as we still want to cancel any other pending transitions.
32323 // TODO: We may not want to bump 'transition' if we're called from a location change
32324 // that we've initiated ourselves, because we might accidentally abort a legitimate
32325 // transition initiated from code?
32326 if (shouldSkipReload(to, toParams, from, fromParams, locals, options)) {
32327 if (hash) toParams['#'] = hash;
32328 $state.params = toParams;
32329 copy($state.params, $stateParams);
32330 copy(filterByKeys(to.params.$$keys(), $stateParams), to.locals.globals.$stateParams);
32331 if (options.location && to.navigable && to.navigable.url) {
32332 $urlRouter.push(to.navigable.url, toParams, {
32333 $$avoidResync: true, replace: options.location === 'replace'
32335 $urlRouter.update(true);
32337 $state.transition = null;
32338 return $q.when($state.current);
32341 // Filter parameters before we pass them to event handlers etc.
32342 toParams = filterByKeys(to.params.$$keys(), toParams || {});
32344 // Re-add the saved hash before we start returning things or broadcasting $stateChangeStart
32345 if (hash) toParams['#'] = hash;
32347 // Broadcast start event and cancel the transition if requested
32348 if (options.notify) {
32351 * @name ui.router.state.$state#$stateChangeStart
32352 * @eventOf ui.router.state.$state
32353 * @eventType broadcast on root scope
32355 * Fired when the state transition **begins**. You can use `event.preventDefault()`
32356 * to prevent the transition from happening and then the transition promise will be
32357 * rejected with a `'transition prevented'` value.
32359 * @param {Object} event Event object.
32360 * @param {State} toState The state being transitioned to.
32361 * @param {Object} toParams The params supplied to the `toState`.
32362 * @param {State} fromState The current state, pre-transition.
32363 * @param {Object} fromParams The params supplied to the `fromState`.
32368 * $rootScope.$on('$stateChangeStart',
32369 * function(event, toState, toParams, fromState, fromParams){
32370 * event.preventDefault();
32371 * // transitionTo() promise will be rejected with
32372 * // a 'transition prevented' error
32376 if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams, options).defaultPrevented) {
32377 $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams);
32378 //Don't update and resync url if there's been a new transition started. see issue #2238, #600
32379 if ($state.transition == null) $urlRouter.update();
32380 return TransitionPrevented;
32384 // Resolve locals for the remaining states, but don't update any global state just
32385 // yet -- if anything fails to resolve the current state needs to remain untouched.
32386 // We also set up an inheritance chain for the locals here. This allows the view directive
32387 // to quickly look up the correct definition for each view in the current state. Even
32388 // though we create the locals object itself outside resolveState(), it is initially
32389 // empty and gets filled asynchronously. We need to keep track of the promise for the
32390 // (fully resolved) current locals, and pass this down the chain.
32391 var resolved = $q.when(locals);
32393 for (var l = keep; l < toPath.length; l++, state = toPath[l]) {
32394 locals = toLocals[l] = inherit(locals);
32395 resolved = resolveState(state, toParams, state === to, resolved, locals, options);
32398 // Once everything is resolved, we are ready to perform the actual transition
32399 // and return a promise for the new state. We also keep track of what the
32400 // current promise is, so that we can detect overlapping transitions and
32401 // keep only the outcome of the last transition.
32402 var transition = $state.transition = resolved.then(function () {
32403 var l, entering, exiting;
32405 if ($state.transition !== transition) return TransitionSuperseded;
32407 // Exit 'from' states not kept
32408 for (l = fromPath.length - 1; l >= keep; l--) {
32409 exiting = fromPath[l];
32410 if (exiting.self.onExit) {
32411 $injector.invoke(exiting.self.onExit, exiting.self, exiting.locals.globals);
32413 exiting.locals = null;
32416 // Enter 'to' states not kept
32417 for (l = keep; l < toPath.length; l++) {
32418 entering = toPath[l];
32419 entering.locals = toLocals[l];
32420 if (entering.self.onEnter) {
32421 $injector.invoke(entering.self.onEnter, entering.self, entering.locals.globals);
32425 // Run it again, to catch any transitions in callbacks
32426 if ($state.transition !== transition) return TransitionSuperseded;
32428 // Update globals in $state
32429 $state.$current = to;
32430 $state.current = to.self;
32431 $state.params = toParams;
32432 copy($state.params, $stateParams);
32433 $state.transition = null;
32435 if (options.location && to.navigable) {
32436 $urlRouter.push(to.navigable.url, to.navigable.locals.globals.$stateParams, {
32437 $$avoidResync: true, replace: options.location === 'replace'
32441 if (options.notify) {
32444 * @name ui.router.state.$state#$stateChangeSuccess
32445 * @eventOf ui.router.state.$state
32446 * @eventType broadcast on root scope
32448 * Fired once the state transition is **complete**.
32450 * @param {Object} event Event object.
32451 * @param {State} toState The state being transitioned to.
32452 * @param {Object} toParams The params supplied to the `toState`.
32453 * @param {State} fromState The current state, pre-transition.
32454 * @param {Object} fromParams The params supplied to the `fromState`.
32456 $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams);
32458 $urlRouter.update(true);
32460 return $state.current;
32461 }, function (error) {
32462 if ($state.transition !== transition) return TransitionSuperseded;
32464 $state.transition = null;
32467 * @name ui.router.state.$state#$stateChangeError
32468 * @eventOf ui.router.state.$state
32469 * @eventType broadcast on root scope
32471 * Fired when an **error occurs** during transition. It's important to note that if you
32472 * have any errors in your resolve functions (javascript errors, non-existent services, etc)
32473 * they will not throw traditionally. You must listen for this $stateChangeError event to
32474 * catch **ALL** errors.
32476 * @param {Object} event Event object.
32477 * @param {State} toState The state being transitioned to.
32478 * @param {Object} toParams The params supplied to the `toState`.
32479 * @param {State} fromState The current state, pre-transition.
32480 * @param {Object} fromParams The params supplied to the `fromState`.
32481 * @param {Error} error The resolve error object.
32483 evt = $rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error);
32485 if (!evt.defaultPrevented) {
32486 $urlRouter.update();
32489 return $q.reject(error);
32497 * @name ui.router.state.$state#is
32498 * @methodOf ui.router.state.$state
32501 * Similar to {@link ui.router.state.$state#methods_includes $state.includes},
32502 * but only checks for the full state name. If params is supplied then it will be
32503 * tested for strict equality against the current active params object, so all params
32504 * must match with none missing and no extras.
32508 * $state.$current.name = 'contacts.details.item';
32511 * $state.is('contact.details.item'); // returns true
32512 * $state.is(contactDetailItemStateObject); // returns true
32514 * // relative name (. and ^), typically from a template
32515 * // E.g. from the 'contacts.details' template
32516 * <div ng-class="{highlighted: $state.is('.item')}">Item</div>
32519 * @param {string|object} stateOrName The state name (absolute or relative) or state object you'd like to check.
32520 * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like
32521 * to test against the current active state.
32522 * @param {object=} options An options object. The options are:
32524 * - **`relative`** - {string|object} - If `stateOrName` is a relative state name and `options.relative` is set, .is will
32525 * test relative to `options.relative` state (or name).
32527 * @returns {boolean} Returns true if it is the state.
32529 $state.is = function is(stateOrName, params, options) {
32530 options = extend({ relative: $state.$current }, options || {});
32531 var state = findState(stateOrName, options.relative);
32533 if (!isDefined(state)) { return undefined; }
32534 if ($state.$current !== state) { return false; }
32535 return params ? equalForKeys(state.params.$$values(params), $stateParams) : true;
32540 * @name ui.router.state.$state#includes
32541 * @methodOf ui.router.state.$state
32544 * A method to determine if the current active state is equal to or is the child of the
32545 * state stateName. If any params are passed then they will be tested for a match as well.
32546 * Not all the parameters need to be passed, just the ones you'd like to test for equality.
32549 * Partial and relative names
32551 * $state.$current.name = 'contacts.details.item';
32553 * // Using partial names
32554 * $state.includes("contacts"); // returns true
32555 * $state.includes("contacts.details"); // returns true
32556 * $state.includes("contacts.details.item"); // returns true
32557 * $state.includes("contacts.list"); // returns false
32558 * $state.includes("about"); // returns false
32560 * // Using relative names (. and ^), typically from a template
32561 * // E.g. from the 'contacts.details' template
32562 * <div ng-class="{highlighted: $state.includes('.item')}">Item</div>
32565 * Basic globbing patterns
32567 * $state.$current.name = 'contacts.details.item.url';
32569 * $state.includes("*.details.*.*"); // returns true
32570 * $state.includes("*.details.**"); // returns true
32571 * $state.includes("**.item.**"); // returns true
32572 * $state.includes("*.details.item.url"); // returns true
32573 * $state.includes("*.details.*.url"); // returns true
32574 * $state.includes("*.details.*"); // returns false
32575 * $state.includes("item.**"); // returns false
32578 * @param {string} stateOrName A partial name, relative name, or glob pattern
32579 * to be searched for within the current state name.
32580 * @param {object=} params A param object, e.g. `{sectionId: section.id}`,
32581 * that you'd like to test against the current active state.
32582 * @param {object=} options An options object. The options are:
32584 * - **`relative`** - {string|object=} - If `stateOrName` is a relative state reference and `options.relative` is set,
32585 * .includes will test relative to `options.relative` state (or name).
32587 * @returns {boolean} Returns true if it does include the state
32589 $state.includes = function includes(stateOrName, params, options) {
32590 options = extend({ relative: $state.$current }, options || {});
32591 if (isString(stateOrName) && isGlob(stateOrName)) {
32592 if (!doesStateMatchGlob(stateOrName)) {
32595 stateOrName = $state.$current.name;
32598 var state = findState(stateOrName, options.relative);
32599 if (!isDefined(state)) { return undefined; }
32600 if (!isDefined($state.$current.includes[state.name])) { return false; }
32601 return params ? equalForKeys(state.params.$$values(params), $stateParams, objectKeys(params)) : true;
32607 * @name ui.router.state.$state#href
32608 * @methodOf ui.router.state.$state
32611 * A url generation method that returns the compiled url for the given state populated with the given params.
32615 * expect($state.href("about.person", { person: "bob" })).toEqual("/about/bob");
32618 * @param {string|object} stateOrName The state name or state object you'd like to generate a url from.
32619 * @param {object=} params An object of parameter values to fill the state's required parameters.
32620 * @param {object=} options Options object. The options are:
32622 * - **`lossy`** - {boolean=true} - If true, and if there is no url associated with the state provided in the
32623 * first parameter, then the constructed href url will be built from the first navigable ancestor (aka
32624 * ancestor with a valid url).
32625 * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
32626 * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
32627 * defines which state to be relative from.
32628 * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
32630 * @returns {string} compiled state url
32632 $state.href = function href(stateOrName, params, options) {
32637 relative: $state.$current
32640 var state = findState(stateOrName, options.relative);
32642 if (!isDefined(state)) return null;
32643 if (options.inherit) params = inheritParams($stateParams, params || {}, $state.$current, state);
32645 var nav = (state && options.lossy) ? state.navigable : state;
32647 if (!nav || nav.url === undefined || nav.url === null) {
32650 return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys().concat('#'), params || {}), {
32651 absolute: options.absolute
32657 * @name ui.router.state.$state#get
32658 * @methodOf ui.router.state.$state
32661 * Returns the state configuration object for any specific state or all states.
32663 * @param {string|object=} stateOrName (absolute or relative) If provided, will only get the config for
32664 * the requested state. If not provided, returns an array of ALL state configs.
32665 * @param {string|object=} context When stateOrName is a relative state reference, the state will be retrieved relative to context.
32666 * @returns {Object|Array} State configuration object or array of all objects.
32668 $state.get = function (stateOrName, context) {
32669 if (arguments.length === 0) return map(objectKeys(states), function(name) { return states[name].self; });
32670 var state = findState(stateOrName, context || $state.$current);
32671 return (state && state.self) ? state.self : null;
32674 function resolveState(state, params, paramsAreFiltered, inherited, dst, options) {
32675 // Make a restricted $stateParams with only the parameters that apply to this state if
32676 // necessary. In addition to being available to the controller and onEnter/onExit callbacks,
32677 // we also need $stateParams to be available for any $injector calls we make during the
32678 // dependency resolution process.
32679 var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params);
32680 var locals = { $stateParams: $stateParams };
32682 // Resolve 'global' dependencies for the state, i.e. those not specific to a view.
32683 // We're also including $stateParams in this; that way the parameters are restricted
32684 // to the set that should be visible to the state, and are independent of when we update
32685 // the global $state and $stateParams values.
32686 dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state);
32687 var promises = [dst.resolve.then(function (globals) {
32688 dst.globals = globals;
32690 if (inherited) promises.push(inherited);
32692 function resolveViews() {
32693 var viewsPromises = [];
32695 // Resolve template and dependencies for all views.
32696 forEach(state.views, function (view, name) {
32697 var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {});
32698 injectables.$template = [ function () {
32699 return $view.load(name, { view: view, locals: dst.globals, params: $stateParams, notify: options.notify }) || '';
32702 viewsPromises.push($resolve.resolve(injectables, dst.globals, dst.resolve, state).then(function (result) {
32703 // References to the controller (only instantiated at link time)
32704 if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) {
32705 var injectLocals = angular.extend({}, injectables, dst.globals);
32706 result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals);
32708 result.$$controller = view.controller;
32710 // Provide access to the state itself for internal use
32711 result.$$state = state;
32712 result.$$controllerAs = view.controllerAs;
32713 dst[name] = result;
32717 return $q.all(viewsPromises).then(function(){
32718 return dst.globals;
32722 // Wait for all the promises and then return the activation object
32723 return $q.all(promises).then(resolveViews).then(function (values) {
32731 function shouldSkipReload(to, toParams, from, fromParams, locals, options) {
32732 // Return true if there are no differences in non-search (path/object) params, false if there are differences
32733 function nonSearchParamsEqual(fromAndToState, fromParams, toParams) {
32734 // Identify whether all the parameters that differ between `fromParams` and `toParams` were search params.
32735 function notSearchParam(key) {
32736 return fromAndToState.params[key].location != "search";
32738 var nonQueryParamKeys = fromAndToState.params.$$keys().filter(notSearchParam);
32739 var nonQueryParams = pick.apply({}, [fromAndToState.params].concat(nonQueryParamKeys));
32740 var nonQueryParamSet = new $$UMFP.ParamSet(nonQueryParams);
32741 return nonQueryParamSet.$$equals(fromParams, toParams);
32744 // If reload was not explicitly requested
32745 // and we're transitioning to the same state we're already in
32746 // and the locals didn't change
32747 // or they changed in a way that doesn't merit reloading
32748 // (reloadOnParams:false, or reloadOnSearch.false and only search params changed)
32749 // Then return true.
32750 if (!options.reload && to === from &&
32751 (locals === from.locals || (to.self.reloadOnSearch === false && nonSearchParamsEqual(from, fromParams, toParams)))) {
32757 angular.module('ui.router.state')
32758 .factory('$stateParams', function () { return {}; })
32759 .provider('$state', $StateProvider);
32762 $ViewProvider.$inject = [];
32763 function $ViewProvider() {
32768 * @name ui.router.state.$view
32770 * @requires ui.router.util.$templateFactory
32771 * @requires $rootScope
32776 $get.$inject = ['$rootScope', '$templateFactory'];
32777 function $get( $rootScope, $templateFactory) {
32779 // $view.load('full.viewName', { template: ..., controller: ..., resolve: ..., async: false, params: ... })
32782 * @name ui.router.state.$view#load
32783 * @methodOf ui.router.state.$view
32787 * @param {string} name name
32788 * @param {object} options option object.
32790 load: function load(name, options) {
32791 var result, defaults = {
32792 template: null, controller: null, view: null, locals: null, notify: true, async: true, params: {}
32794 options = extend(defaults, options);
32796 if (options.view) {
32797 result = $templateFactory.fromConfig(options.view, options.params, options.locals);
32805 angular.module('ui.router.state').provider('$view', $ViewProvider);
32809 * @name ui.router.state.$uiViewScrollProvider
32812 * Provider that returns the {@link ui.router.state.$uiViewScroll} service function.
32814 function $ViewScrollProvider() {
32816 var useAnchorScroll = false;
32820 * @name ui.router.state.$uiViewScrollProvider#useAnchorScroll
32821 * @methodOf ui.router.state.$uiViewScrollProvider
32824 * Reverts back to using the core [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) service for
32825 * scrolling based on the url anchor.
32827 this.useAnchorScroll = function () {
32828 useAnchorScroll = true;
32833 * @name ui.router.state.$uiViewScroll
32835 * @requires $anchorScroll
32836 * @requires $timeout
32839 * When called with a jqLite element, it scrolls the element into view (after a
32840 * `$timeout` so the DOM has time to refresh).
32842 * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor,
32843 * this can be enabled by calling {@link ui.router.state.$uiViewScrollProvider#methods_useAnchorScroll `$uiViewScrollProvider.useAnchorScroll()`}.
32845 this.$get = ['$anchorScroll', '$timeout', function ($anchorScroll, $timeout) {
32846 if (useAnchorScroll) {
32847 return $anchorScroll;
32850 return function ($element) {
32851 return $timeout(function () {
32852 $element[0].scrollIntoView();
32858 angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider);
32860 var ngMajorVer = angular.version.major;
32861 var ngMinorVer = angular.version.minor;
32864 * @name ui.router.state.directive:ui-view
32866 * @requires ui.router.state.$state
32867 * @requires $compile
32868 * @requires $controller
32869 * @requires $injector
32870 * @requires ui.router.state.$uiViewScroll
32871 * @requires $document
32876 * The ui-view directive tells $state where to place your templates.
32878 * @param {string=} name A view name. The name should be unique amongst the other views in the
32879 * same state. You can have views of the same name that live in different states.
32881 * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window
32882 * when a view is populated. By default, $anchorScroll is overridden by ui-router's custom scroll
32883 * service, {@link ui.router.state.$uiViewScroll}. This custom service let's you
32884 * scroll ui-view elements into view when they are populated during a state activation.
32886 * @param {string=} noanimation If truthy, the non-animated renderer will be selected (no animations
32887 * will be applied to the ui-view)
32889 * *Note: To revert back to old [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll)
32890 * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.*
32892 * @param {string=} onload Expression to evaluate whenever the view updates.
32895 * A view can be unnamed or named.
32898 * <div ui-view></div>
32901 * <div ui-view="viewName"></div>
32904 * You can only have one unnamed view within any template (or root html). If you are only using a
32905 * single view and it is unnamed then you can populate it like so:
32907 * <div ui-view></div>
32908 * $stateProvider.state("home", {
32909 * template: "<h1>HELLO!</h1>"
32913 * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#views `views`}
32914 * config property, by name, in this case an empty name:
32916 * $stateProvider.state("home", {
32919 * template: "<h1>HELLO!</h1>"
32925 * But typically you'll only use the views property if you name your view or have more than one view
32926 * in the same template. There's not really a compelling reason to name a view if its the only one,
32927 * but you could if you wanted, like so:
32929 * <div ui-view="main"></div>
32932 * $stateProvider.state("home", {
32935 * template: "<h1>HELLO!</h1>"
32941 * Really though, you'll use views to set up multiple views:
32943 * <div ui-view></div>
32944 * <div ui-view="chart"></div>
32945 * <div ui-view="data"></div>
32949 * $stateProvider.state("home", {
32952 * template: "<h1>HELLO!</h1>"
32955 * template: "<chart_thing/>"
32958 * template: "<data_thing/>"
32964 * Examples for `autoscroll`:
32967 * <!-- If autoscroll present with no expression,
32968 * then scroll ui-view into view -->
32969 * <ui-view autoscroll/>
32971 * <!-- If autoscroll present with valid expression,
32972 * then scroll ui-view into view if expression evaluates to true -->
32973 * <ui-view autoscroll='true'/>
32974 * <ui-view autoscroll='false'/>
32975 * <ui-view autoscroll='scopeVariable'/>
32978 $ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll', '$interpolate'];
32979 function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate) {
32981 function getService() {
32982 return ($injector.has) ? function(service) {
32983 return $injector.has(service) ? $injector.get(service) : null;
32984 } : function(service) {
32986 return $injector.get(service);
32993 var service = getService(),
32994 $animator = service('$animator'),
32995 $animate = service('$animate');
32997 // Returns a set of DOM manipulation functions based on which Angular version
32999 function getRenderer(attrs, scope) {
33001 enter: function (element, target, cb) { target.after(element); cb(); },
33002 leave: function (element, cb) { element.remove(); cb(); }
33005 if (!!attrs.noanimation) return statics;
33007 function animEnabled(element) {
33008 if (ngMajorVer === 1 && ngMinorVer >= 4) return !!$animate.enabled(element);
33009 if (ngMajorVer === 1 && ngMinorVer >= 2) return !!$animate.enabled();
33010 return (!!$animator);
33016 enter: function(element, target, cb) {
33017 if (!animEnabled(element)) {
33018 statics.enter(element, target, cb);
33019 } else if (angular.version.minor > 2) {
33020 $animate.enter(element, null, target).then(cb);
33022 $animate.enter(element, null, target, cb);
33025 leave: function(element, cb) {
33026 if (!animEnabled(element)) {
33027 statics.leave(element, cb);
33028 } else if (angular.version.minor > 2) {
33029 $animate.leave(element).then(cb);
33031 $animate.leave(element, cb);
33039 var animate = $animator && $animator(scope, attrs);
33042 enter: function(element, target, cb) {animate.enter(element, null, target); cb(); },
33043 leave: function(element, cb) { animate.leave(element); cb(); }
33054 transclude: 'element',
33055 compile: function (tElement, tAttrs, $transclude) {
33056 return function (scope, $element, attrs) {
33057 var previousEl, currentEl, currentScope, latestLocals,
33058 onloadExp = attrs.onload || '',
33059 autoScrollExp = attrs.autoscroll,
33060 renderer = getRenderer(attrs, scope);
33062 scope.$on('$stateChangeSuccess', function() {
33068 function cleanupLastView() {
33069 var _previousEl = previousEl;
33070 var _currentScope = currentScope;
33072 if (_currentScope) {
33073 _currentScope._willBeDestroyed = true;
33076 function cleanOld() {
33078 _previousEl.remove();
33081 if (_currentScope) {
33082 _currentScope.$destroy();
33087 renderer.leave(currentEl, function() {
33092 previousEl = currentEl;
33099 currentScope = null;
33102 function updateView(firstTime) {
33104 name = getUiViewName(scope, attrs, $element, $interpolate),
33105 previousLocals = name && $state.$current && $state.$current.locals[name];
33107 if (!firstTime && previousLocals === latestLocals || scope._willBeDestroyed) return; // nothing to do
33108 newScope = scope.$new();
33109 latestLocals = $state.$current.locals[name];
33113 * @name ui.router.state.directive:ui-view#$viewContentLoading
33114 * @eventOf ui.router.state.directive:ui-view
33115 * @eventType emits on ui-view directive scope
33118 * Fired once the view **begins loading**, *before* the DOM is rendered.
33120 * @param {Object} event Event object.
33121 * @param {string} viewName Name of the view.
33123 newScope.$emit('$viewContentLoading', name);
33125 var clone = $transclude(newScope, function(clone) {
33126 renderer.enter(clone, $element, function onUiViewEnter() {
33128 currentScope.$emit('$viewContentAnimationEnded');
33131 if (angular.isDefined(autoScrollExp) && !autoScrollExp || scope.$eval(autoScrollExp)) {
33132 $uiViewScroll(clone);
33139 currentScope = newScope;
33142 * @name ui.router.state.directive:ui-view#$viewContentLoaded
33143 * @eventOf ui.router.state.directive:ui-view
33144 * @eventType emits on ui-view directive scope
33146 * Fired once the view is **loaded**, *after* the DOM is rendered.
33148 * @param {Object} event Event object.
33149 * @param {string} viewName Name of the view.
33151 currentScope.$emit('$viewContentLoaded', name);
33152 currentScope.$eval(onloadExp);
33161 $ViewDirectiveFill.$inject = ['$compile', '$controller', '$state', '$interpolate'];
33162 function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate) {
33166 compile: function (tElement) {
33167 var initial = tElement.html();
33168 return function (scope, $element, attrs) {
33169 var current = $state.$current,
33170 name = getUiViewName(scope, attrs, $element, $interpolate),
33171 locals = current && current.locals[name];
33177 $element.data('$uiView', { name: name, state: locals.$$state });
33178 $element.html(locals.$template ? locals.$template : initial);
33180 var link = $compile($element.contents());
33182 if (locals.$$controller) {
33183 locals.$scope = scope;
33184 locals.$element = $element;
33185 var controller = $controller(locals.$$controller, locals);
33186 if (locals.$$controllerAs) {
33187 scope[locals.$$controllerAs] = controller;
33189 $element.data('$ngControllerController', controller);
33190 $element.children().data('$ngControllerController', controller);
33200 * Shared ui-view code for both directives:
33201 * Given scope, element, and its attributes, return the view's name
33203 function getUiViewName(scope, attrs, element, $interpolate) {
33204 var name = $interpolate(attrs.uiView || attrs.name || '')(scope);
33205 var inherited = element.inheritedData('$uiView');
33206 return name.indexOf('@') >= 0 ? name : (name + '@' + (inherited ? inherited.state.name : ''));
33209 angular.module('ui.router.state').directive('uiView', $ViewDirective);
33210 angular.module('ui.router.state').directive('uiView', $ViewDirectiveFill);
33212 function parseStateRef(ref, current) {
33213 var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed;
33214 if (preparsed) ref = current + '(' + preparsed[1] + ')';
33215 parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
33216 if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'");
33217 return { state: parsed[1], paramExpr: parsed[3] || null };
33220 function stateContext(el) {
33221 var stateData = el.parent().inheritedData('$uiView');
33223 if (stateData && stateData.state && stateData.state.name) {
33224 return stateData.state;
33228 function getTypeInfo(el) {
33229 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
33230 var isSvg = Object.prototype.toString.call(el.prop('href')) === '[object SVGAnimatedString]';
33231 var isForm = el[0].nodeName === "FORM";
33234 attr: isForm ? "action" : (isSvg ? 'xlink:href' : 'href'),
33235 isAnchor: el.prop("tagName").toUpperCase() === "A",
33240 function clickHook(el, $state, $timeout, type, current) {
33241 return function(e) {
33242 var button = e.which || e.button, target = current();
33244 if (!(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || el.attr('target'))) {
33245 // HACK: This is to allow ng-clicks to be processed before the transition is initiated:
33246 var transition = $timeout(function() {
33247 $state.go(target.state, target.params, target.options);
33249 e.preventDefault();
33251 // if the state has no URL, ignore one preventDefault from the <a> directive.
33252 var ignorePreventDefaultCount = type.isAnchor && !target.href ? 1: 0;
33254 e.preventDefault = function() {
33255 if (ignorePreventDefaultCount-- <= 0) $timeout.cancel(transition);
33261 function defaultOpts(el, $state) {
33262 return { relative: stateContext(el) || $state.$current, inherit: true };
33267 * @name ui.router.state.directive:ui-sref
33269 * @requires ui.router.state.$state
33270 * @requires $timeout
33275 * A directive that binds a link (`<a>` tag) to a state. If the state has an associated
33276 * URL, the directive will automatically generate & update the `href` attribute via
33277 * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking
33278 * the link will trigger a state transition with optional parameters.
33280 * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
33281 * handled natively by the browser.
33283 * You can also use relative state paths within ui-sref, just like the relative
33284 * paths passed to `$state.go()`. You just need to be aware that the path is relative
33285 * to the state that the link lives in, in other words the state that loaded the
33286 * template containing the link.
33288 * You can specify options to pass to {@link ui.router.state.$state#go $state.go()}
33289 * using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`,
33293 * Here's an example of how you'd use ui-sref and how it would compile. If you have the
33294 * following template:
33296 * <a ui-sref="home">Home</a> | <a ui-sref="about">About</a> | <a ui-sref="{page: 2}">Next page</a>
33299 * <li ng-repeat="contact in contacts">
33300 * <a ui-sref="contacts.detail({ id: contact.id })">{{ contact.name }}</a>
33305 * Then the compiled html would be (assuming Html5Mode is off and current state is contacts):
33307 * <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>
33310 * <li ng-repeat="contact in contacts">
33311 * <a href="#/contacts/1" ui-sref="contacts.detail({ id: contact.id })">Joe</a>
33313 * <li ng-repeat="contact in contacts">
33314 * <a href="#/contacts/2" ui-sref="contacts.detail({ id: contact.id })">Alice</a>
33316 * <li ng-repeat="contact in contacts">
33317 * <a href="#/contacts/3" ui-sref="contacts.detail({ id: contact.id })">Bob</a>
33321 * <a ui-sref="home" ui-sref-opts="{reload: true}">Home</a>
33324 * @param {string} ui-sref 'stateName' can be any valid absolute or relative state
33325 * @param {Object} ui-sref-opts options to pass to {@link ui.router.state.$state#go $state.go()}
33327 $StateRefDirective.$inject = ['$state', '$timeout'];
33328 function $StateRefDirective($state, $timeout) {
33331 require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
33332 link: function(scope, element, attrs, uiSrefActive) {
33333 var ref = parseStateRef(attrs.uiSref, $state.current.name);
33334 var def = { state: ref.state, href: null, params: null };
33335 var type = getTypeInfo(element);
33336 var active = uiSrefActive[1] || uiSrefActive[0];
33338 def.options = extend(defaultOpts(element, $state), attrs.uiSrefOpts ? scope.$eval(attrs.uiSrefOpts) : {});
33340 var update = function(val) {
33341 if (val) def.params = angular.copy(val);
33342 def.href = $state.href(ref.state, def.params, def.options);
33344 if (active) active.$$addStateInfo(ref.state, def.params);
33345 if (def.href !== null) attrs.$set(type.attr, def.href);
33348 if (ref.paramExpr) {
33349 scope.$watch(ref.paramExpr, function(val) { if (val !== def.params) update(val); }, true);
33350 def.params = angular.copy(scope.$eval(ref.paramExpr));
33354 if (!type.clickable) return;
33355 element.bind("click", clickHook(element, $state, $timeout, type, function() { return def; }));
33362 * @name ui.router.state.directive:ui-state
33364 * @requires ui.router.state.uiSref
33369 * Much like ui-sref, but will accept named $scope properties to evaluate for a state definition,
33370 * params and override options.
33372 * @param {string} ui-state 'stateName' can be any valid absolute or relative state
33373 * @param {Object} ui-state-params params to pass to {@link ui.router.state.$state#href $state.href()}
33374 * @param {Object} ui-state-opts options to pass to {@link ui.router.state.$state#go $state.go()}
33376 $StateRefDynamicDirective.$inject = ['$state', '$timeout'];
33377 function $StateRefDynamicDirective($state, $timeout) {
33380 require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
33381 link: function(scope, element, attrs, uiSrefActive) {
33382 var type = getTypeInfo(element);
33383 var active = uiSrefActive[1] || uiSrefActive[0];
33384 var group = [attrs.uiState, attrs.uiStateParams || null, attrs.uiStateOpts || null];
33385 var watch = '[' + group.map(function(val) { return val || 'null'; }).join(', ') + ']';
33386 var def = { state: null, params: null, options: null, href: null };
33388 function runStateRefLink (group) {
33389 def.state = group[0]; def.params = group[1]; def.options = group[2];
33390 def.href = $state.href(def.state, def.params, def.options);
33392 if (active) active.$$addStateInfo(def.state, def.params);
33393 if (def.href) attrs.$set(type.attr, def.href);
33396 scope.$watch(watch, runStateRefLink, true);
33397 runStateRefLink(scope.$eval(watch));
33399 if (!type.clickable) return;
33400 element.bind("click", clickHook(element, $state, $timeout, type, function() { return def; }));
33408 * @name ui.router.state.directive:ui-sref-active
33410 * @requires ui.router.state.$state
33411 * @requires ui.router.state.$stateParams
33412 * @requires $interpolate
33417 * A directive working alongside ui-sref to add classes to an element when the
33418 * related ui-sref directive's state is active, and removing them when it is inactive.
33419 * The primary use-case is to simplify the special appearance of navigation menus
33420 * relying on `ui-sref`, by having the "active" state's menu button appear different,
33421 * distinguishing it from the inactive menu items.
33423 * ui-sref-active can live on the same element as ui-sref or on a parent element. The first
33424 * ui-sref-active found at the same level or above the ui-sref will be used.
33426 * Will activate when the ui-sref's target state or any child state is active. If you
33427 * need to activate only when the ui-sref target state is active and *not* any of
33428 * it's children, then you will use
33429 * {@link ui.router.state.directive:ui-sref-active-eq ui-sref-active-eq}
33432 * Given the following template:
33435 * <li ui-sref-active="active" class="item">
33436 * <a href ui-sref="app.user({user: 'bilbobaggins'})">@bilbobaggins</a>
33442 * When the app state is "app.user" (or any children states), and contains the state parameter "user" with value "bilbobaggins",
33443 * the resulting HTML will appear as (note the 'active' class):
33446 * <li ui-sref-active="active" class="item active">
33447 * <a ui-sref="app.user({user: 'bilbobaggins'})" href="/users/bilbobaggins">@bilbobaggins</a>
33452 * The class name is interpolated **once** during the directives link time (any further changes to the
33453 * interpolated value are ignored).
33455 * Multiple classes may be specified in a space-separated format:
33458 * <li ui-sref-active='class1 class2 class3'>
33459 * <a ui-sref="app.user">link</a>
33464 * It is also possible to pass ui-sref-active an expression that evaluates
33465 * to an object hash, whose keys represent active class names and whose
33466 * values represent the respective state names/globs.
33467 * ui-sref-active will match if the current active state **includes** any of
33468 * the specified state names/globs, even the abstract ones.
33471 * Given the following template, with "admin" being an abstract state:
33473 * <div ui-sref-active="{'active': 'admin.*'}">
33474 * <a ui-sref-active="active" ui-sref="admin.roles">Roles</a>
33478 * When the current state is "admin.roles" the "active" class will be applied
33479 * to both the <div> and <a> elements. It is important to note that the state
33480 * names/globs passed to ui-sref-active shadow the state provided by ui-sref.
33485 * @name ui.router.state.directive:ui-sref-active-eq
33487 * @requires ui.router.state.$state
33488 * @requires ui.router.state.$stateParams
33489 * @requires $interpolate
33494 * The same as {@link ui.router.state.directive:ui-sref-active ui-sref-active} but will only activate
33495 * when the exact target state used in the `ui-sref` is active; no child states.
33498 $StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
33499 function $StateRefActiveDirective($state, $stateParams, $interpolate) {
33502 controller: ['$scope', '$element', '$attrs', '$timeout', function ($scope, $element, $attrs, $timeout) {
33503 var states = [], activeClasses = {}, activeEqClass, uiSrefActive;
33505 // There probably isn't much point in $observing this
33506 // uiSrefActive and uiSrefActiveEq share the same directive object with some
33507 // slight difference in logic routing
33508 activeEqClass = $interpolate($attrs.uiSrefActiveEq || '', false)($scope);
33511 uiSrefActive = $scope.$eval($attrs.uiSrefActive);
33513 // Do nothing. uiSrefActive is not a valid expression.
33514 // Fall back to using $interpolate below
33516 uiSrefActive = uiSrefActive || $interpolate($attrs.uiSrefActive || '', false)($scope);
33517 if (isObject(uiSrefActive)) {
33518 forEach(uiSrefActive, function(stateOrName, activeClass) {
33519 if (isString(stateOrName)) {
33520 var ref = parseStateRef(stateOrName, $state.current.name);
33521 addState(ref.state, $scope.$eval(ref.paramExpr), activeClass);
33526 // Allow uiSref to communicate with uiSrefActive[Equals]
33527 this.$$addStateInfo = function (newState, newParams) {
33528 // we already got an explicit state provided by ui-sref-active, so we
33529 // shadow the one that comes from ui-sref
33530 if (isObject(uiSrefActive) && states.length > 0) {
33533 addState(newState, newParams, uiSrefActive);
33537 $scope.$on('$stateChangeSuccess', update);
33539 function addState(stateName, stateParams, activeClass) {
33540 var state = $state.get(stateName, stateContext($element));
33541 var stateHash = createStateHash(stateName, stateParams);
33544 state: state || { name: stateName },
33545 params: stateParams,
33549 activeClasses[stateHash] = activeClass;
33553 * @param {string} state
33554 * @param {Object|string} [params]
33557 function createStateHash(state, params) {
33558 if (!isString(state)) {
33559 throw new Error('state should be a string');
33561 if (isObject(params)) {
33562 return state + toJson(params);
33564 params = $scope.$eval(params);
33565 if (isObject(params)) {
33566 return state + toJson(params);
33571 // Update route state
33572 function update() {
33573 for (var i = 0; i < states.length; i++) {
33574 if (anyMatch(states[i].state, states[i].params)) {
33575 addClass($element, activeClasses[states[i].hash]);
33577 removeClass($element, activeClasses[states[i].hash]);
33580 if (exactMatch(states[i].state, states[i].params)) {
33581 addClass($element, activeEqClass);
33583 removeClass($element, activeEqClass);
33588 function addClass(el, className) { $timeout(function () { el.addClass(className); }); }
33589 function removeClass(el, className) { el.removeClass(className); }
33590 function anyMatch(state, params) { return $state.includes(state.name, params); }
33591 function exactMatch(state, params) { return $state.is(state.name, params); }
33598 angular.module('ui.router.state')
33599 .directive('uiSref', $StateRefDirective)
33600 .directive('uiSrefActive', $StateRefActiveDirective)
33601 .directive('uiSrefActiveEq', $StateRefActiveDirective)
33602 .directive('uiState', $StateRefDynamicDirective);
33606 * @name ui.router.state.filter:isState
33608 * @requires ui.router.state.$state
33611 * Translates to {@link ui.router.state.$state#methods_is $state.is("stateName")}.
33613 $IsStateFilter.$inject = ['$state'];
33614 function $IsStateFilter($state) {
33615 var isFilter = function (state, params) {
33616 return $state.is(state, params);
33618 isFilter.$stateful = true;
33624 * @name ui.router.state.filter:includedByState
33626 * @requires ui.router.state.$state
33629 * Translates to {@link ui.router.state.$state#methods_includes $state.includes('fullOrPartialStateName')}.
33631 $IncludedByStateFilter.$inject = ['$state'];
33632 function $IncludedByStateFilter($state) {
33633 var includesFilter = function (state, params, options) {
33634 return $state.includes(state, params, options);
33636 includesFilter.$stateful = true;
33637 return includesFilter;
33640 angular.module('ui.router.state')
33641 .filter('isState', $IsStateFilter)
33642 .filter('includedByState', $IncludedByStateFilter);
33643 })(window, window.angular);
33647 /***/ function(module, exports, __webpack_require__) {
33649 __webpack_require__(5);
33650 module.exports = 'ngResource';
33655 /***/ function(module, exports) {
33658 * @license AngularJS v1.6.2
33659 * (c) 2010-2017 Google, Inc. http://angularjs.org
33662 (function(window, angular) {'use strict';
33664 var $resourceMinErr = angular.$$minErr('$resource');
33666 // Helper functions and regex to lookup a dotted path on an object
33667 // stopping at undefined/null. The path must be composed of ASCII
33668 // identifiers (just like $parse)
33669 var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/;
33671 function isValidDottedPath(path) {
33672 return (path != null && path !== '' && path !== 'hasOwnProperty' &&
33673 MEMBER_NAME_REGEX.test('.' + path));
33676 function lookupDottedPath(obj, path) {
33677 if (!isValidDottedPath(path)) {
33678 throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path);
33680 var keys = path.split('.');
33681 for (var i = 0, ii = keys.length; i < ii && angular.isDefined(obj); i++) {
33683 obj = (obj !== null) ? obj[key] : undefined;
33689 * Create a shallow copy of an object and clear other fields from the destination
33691 function shallowClearAndCopy(src, dst) {
33694 angular.forEach(dst, function(value, key) {
33698 for (var key in src) {
33699 if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
33700 dst[key] = src[key];
33714 * The `ngResource` module provides interaction support with RESTful services
33715 * via the $resource service.
33718 * <div doc-module-components="ngResource"></div>
33720 * See {@link ngResource.$resourceProvider} and {@link ngResource.$resource} for usage.
33725 * @name $resourceProvider
33729 * Use `$resourceProvider` to change the default behavior of the {@link ngResource.$resource}
33733 * Requires the {@link ngResource } module to be installed.
33741 * @requires ng.$log
33743 * @requires ng.$timeout
33746 * A factory which creates a resource object that lets you interact with
33747 * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
33749 * The returned resource object has action methods which provide high-level behaviors without
33750 * the need to interact with the low level {@link ng.$http $http} service.
33752 * Requires the {@link ngResource `ngResource`} module to be installed.
33754 * By default, trailing slashes will be stripped from the calculated URLs,
33755 * which can pose problems with server backends that do not expect that
33756 * behavior. This can be disabled by configuring the `$resourceProvider` like
33760 app.config(['$resourceProvider', function($resourceProvider) {
33761 // Don't strip trailing slashes from calculated URLs
33762 $resourceProvider.defaults.stripTrailingSlashes = false;
33766 * @param {string} url A parameterized URL template with parameters prefixed by `:` as in
33767 * `/user/:username`. If you are using a URL with a port number (e.g.
33768 * `http://example.com:8080/api`), it will be respected.
33770 * If you are using a url with a suffix, just add the suffix, like this:
33771 * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')`
33772 * or even `$resource('http://example.com/resource/:resource_id.:format')`
33773 * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be
33774 * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you
33775 * can escape it with `/\.`.
33777 * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
33778 * `actions` methods. If a parameter value is a function, it will be called every time
33779 * a param value needs to be obtained for a request (unless the param was overridden). The function
33780 * will be passed the current data value as an argument.
33782 * Each key value in the parameter object is first bound to url template if present and then any
33783 * excess keys are appended to the url search query after the `?`.
33785 * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
33786 * URL `/path/greet?salutation=Hello`.
33788 * If the parameter value is prefixed with `@`, then the value for that parameter will be
33789 * extracted from the corresponding property on the `data` object (provided when calling a
33790 * "non-GET" action method).
33791 * For example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of
33792 * `someParam` will be `data.someProp`.
33793 * Note that the parameter will be ignored, when calling a "GET" action method (i.e. an action
33794 * method that does not accept a request body)
33796 * @param {Object.<Object>=} actions Hash with declaration of custom actions that will be available
33797 * in addition to the default set of resource actions (see below). If a custom action has the same
33798 * key as a default action (e.g. `save`), then the default action will be *overwritten*, and not
33801 * The declaration should be created in the format of {@link ng.$http#usage $http.config}:
33803 * {action1: {method:?, params:?, isArray:?, headers:?, ...},
33804 * action2: {method:?, params:?, isArray:?, headers:?, ...},
33809 * - **`action`** – {string} – The name of action. This name becomes the name of the method on
33810 * your resource object.
33811 * - **`method`** – {string} – Case insensitive HTTP method (e.g. `GET`, `POST`, `PUT`,
33812 * `DELETE`, `JSONP`, etc).
33813 * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of
33814 * the parameter value is a function, it will be called every time when a param value needs to
33815 * be obtained for a request (unless the param was overridden). The function will be passed the
33816 * current data value as an argument.
33817 * - **`url`** – {string} – action specific `url` override. The url templating is supported just
33818 * like for the resource-level urls.
33819 * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array,
33820 * see `returns` section.
33821 * - **`transformRequest`** –
33822 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
33823 * transform function or an array of such functions. The transform function takes the http
33824 * request body and headers and returns its transformed (typically serialized) version.
33825 * By default, transformRequest will contain one function that checks if the request data is
33826 * an object and serializes it using `angular.toJson`. To prevent this behavior, set
33827 * `transformRequest` to an empty array: `transformRequest: []`
33828 * - **`transformResponse`** –
33829 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` –
33830 * transform function or an array of such functions. The transform function takes the http
33831 * response body, headers and status and returns its transformed (typically deserialized)
33833 * By default, transformResponse will contain one function that checks if the response looks
33834 * like a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior,
33835 * set `transformResponse` to an empty array: `transformResponse: []`
33836 * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
33837 * GET request, otherwise if a cache instance built with
33838 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
33840 * - **`timeout`** – `{number}` – timeout in milliseconds.<br />
33841 * **Note:** In contrast to {@link ng.$http#usage $http.config}, {@link ng.$q promises} are
33842 * **not** supported in $resource, because the same value would be used for multiple requests.
33843 * If you are looking for a way to cancel requests, you should use the `cancellable` option.
33844 * - **`cancellable`** – `{boolean}` – if set to true, the request made by a "non-instance" call
33845 * will be cancelled (if not already completed) by calling `$cancelRequest()` on the call's
33846 * return value. Calling `$cancelRequest()` for a non-cancellable or an already
33847 * completed/cancelled request will have no effect.<br />
33848 * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the
33850 * [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5)
33851 * for more information.
33852 * - **`responseType`** - `{string}` - see
33853 * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType).
33854 * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
33855 * `response` and `responseError`. Both `response` and `responseError` interceptors get called
33856 * with `http response` object. See {@link ng.$http $http interceptors}.
33858 * @param {Object} options Hash with custom settings that should extend the
33859 * default `$resourceProvider` behavior. The supported options are:
33861 * - **`stripTrailingSlashes`** – {boolean} – If true then the trailing
33862 * slashes from any calculated URL will be stripped. (Defaults to true.)
33863 * - **`cancellable`** – {boolean} – If true, the request made by a "non-instance" call will be
33864 * cancelled (if not already completed) by calling `$cancelRequest()` on the call's return value.
33865 * This can be overwritten per action. (Defaults to false.)
33867 * @returns {Object} A resource "class" object with methods for the default set of resource actions
33868 * optionally extended with custom `actions`. The default set contains these actions:
33870 * { 'get': {method:'GET'},
33871 * 'save': {method:'POST'},
33872 * 'query': {method:'GET', isArray:true},
33873 * 'remove': {method:'DELETE'},
33874 * 'delete': {method:'DELETE'} };
33877 * Calling these methods invoke an {@link ng.$http} with the specified http method,
33878 * destination and parameters. When the data is returned from the server then the object is an
33879 * instance of the resource class. The actions `save`, `remove` and `delete` are available on it
33880 * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
33881 * read, update, delete) on server-side data like this:
33883 * var User = $resource('/user/:userId', {userId:'@id'});
33884 * var user = User.get({userId:123}, function() {
33890 * It is important to realize that invoking a $resource object method immediately returns an
33891 * empty reference (object or array depending on `isArray`). Once the data is returned from the
33892 * server the existing reference is populated with the actual data. This is a useful trick since
33893 * usually the resource is assigned to a model which is then rendered by the view. Having an empty
33894 * object results in no rendering, once the data arrives from the server then the object is
33895 * populated with the data and the view automatically re-renders itself showing the new data. This
33896 * means that in most cases one never has to write a callback function for the action methods.
33898 * The action methods on the class object or instance object can be invoked with the following
33901 * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
33902 * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
33903 * - non-GET instance actions: `instance.$action([parameters], [success], [error])`
33906 * Success callback is called with (value (Object|Array), responseHeaders (Function),
33907 * status (number), statusText (string)) arguments, where the value is the populated resource
33908 * instance or collection object. The error callback is called with (httpResponse) argument.
33910 * Class actions return empty instance (with additional properties below).
33911 * Instance actions return promise of the action.
33913 * The Resource instances and collections have these additional properties:
33915 * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this
33916 * instance or collection.
33918 * On success, the promise is resolved with the same resource instance or collection object,
33919 * updated with data from server. This makes it easy to use in
33920 * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view
33921 * rendering until the resource(s) are loaded.
33923 * On failure, the promise is rejected with the {@link ng.$http http response} object, without
33924 * the `resource` property.
33926 * If an interceptor object was provided, the promise will instead be resolved with the value
33927 * returned by the interceptor.
33929 * - `$resolved`: `true` after first server interaction is completed (either with success or
33930 * rejection), `false` before that. Knowing if the Resource has been resolved is useful in
33933 * The Resource instances and collections have these additional methods:
33935 * - `$cancelRequest`: If there is a cancellable, pending request related to the instance or
33936 * collection, calling this method will abort the request.
33938 * The Resource instances have these additional methods:
33940 * - `toJSON`: It returns a simple object without any of the extra properties added as part of
33941 * the Resource API. This object can be serialized through {@link angular.toJson} safely
33942 * without attaching Angular-specific fields. Notice that `JSON.stringify` (and
33943 * `angular.toJson`) automatically use this method when serializing a Resource instance
33944 * (see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON()_behavior)).
33948 * # Credit card resource
33951 // Define CreditCard class
33952 var CreditCard = $resource('/user/:userId/card/:cardId',
33953 {userId:123, cardId:'@id'}, {
33954 charge: {method:'POST', params:{charge:true}}
33957 // We can retrieve a collection from the server
33958 var cards = CreditCard.query(function() {
33959 // GET: /user/123/card
33960 // server returns: [ {id:456, number:'1234', name:'Smith'} ];
33962 var card = cards[0];
33963 // each item is an instance of CreditCard
33964 expect(card instanceof CreditCard).toEqual(true);
33965 card.name = "J. Smith";
33966 // non GET methods are mapped onto the instances
33968 // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
33969 // server returns: {id:456, number:'1234', name: 'J. Smith'};
33971 // our custom method is mapped as well.
33972 card.$charge({amount:9.99});
33973 // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
33976 // we can create an instance as well
33977 var newCard = new CreditCard({number:'0123'});
33978 newCard.name = "Mike Smith";
33980 // POST: /user/123/card {number:'0123', name:'Mike Smith'}
33981 // server returns: {id:789, number:'0123', name: 'Mike Smith'};
33982 expect(newCard.id).toEqual(789);
33985 * The object returned from this function execution is a resource "class" which has "static" method
33986 * for each action in the definition.
33988 * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and
33995 * When the data is returned from the server then the object is an instance of the resource type and
33996 * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
33997 * operations (create, read, update, delete) on server-side data.
34000 var User = $resource('/user/:userId', {userId:'@id'});
34001 User.get({userId:123}, function(user) {
34007 * It's worth noting that the success callback for `get`, `query` and other methods gets passed
34008 * in the response that came from the server as well as $http header getter function, so one
34009 * could rewrite the above example and get access to http headers as:
34012 var User = $resource('/user/:userId', {userId:'@id'});
34013 User.get({userId:123}, function(user, getResponseHeaders){
34015 user.$save(function(user, putResponseHeaders) {
34016 //user => saved user object
34017 //putResponseHeaders => $http header getter
34022 * You can also access the raw `$http` promise via the `$promise` property on the object returned
34025 var User = $resource('/user/:userId', {userId:'@id'});
34026 User.get({userId:123})
34027 .$promise.then(function(user) {
34028 $scope.user = user;
34034 * # Creating a custom 'PUT' request
34036 * In this example we create a custom method on our resource to make a PUT request
34038 * var app = angular.module('app', ['ngResource', 'ngRoute']);
34040 * // Some APIs expect a PUT request in the format URL/object/ID
34041 * // Here we are creating an 'update' method
34042 * app.factory('Notes', ['$resource', function($resource) {
34043 * return $resource('/notes/:id', null,
34045 * 'update': { method:'PUT' }
34049 * // In our controller we get the ID from the URL using ngRoute and $routeParams
34050 * // We pass in $routeParams and our Notes factory along with $scope
34051 * app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes',
34052 function($scope, $routeParams, Notes) {
34053 * // First get a note object from the factory
34054 * var note = Notes.get({ id:$routeParams.id });
34057 * // Now call update passing in the ID first then the object you are updating
34058 * Notes.update({ id:$id }, note);
34060 * // This will PUT /notes/ID with the note object in the request payload
34066 * # Cancelling requests
34068 * If an action's configuration specifies that it is cancellable, you can cancel the request related
34069 * to an instance or collection (as long as it is a result of a "non-instance" call):
34072 // ...defining the `Hotel` resource...
34073 var Hotel = $resource('/api/hotel/:id', {id: '@id'}, {
34074 // Let's make the `query()` method cancellable
34075 query: {method: 'get', isArray: true, cancellable: true}
34078 // ...somewhere in the PlanVacationController...
34080 this.onDestinationChanged = function onDestinationChanged(destination) {
34081 // We don't care about any pending request for hotels
34082 // in a different destination any more
34083 this.availableHotels.$cancelRequest();
34085 // Let's query for hotels in '<destination>'
34086 // (calls: /api/hotel?location=<destination>)
34087 this.availableHotels = Hotel.query({location: destination});
34092 angular.module('ngResource', ['ng']).
34093 provider('$resource', function ResourceProvider() {
34094 var PROTOCOL_AND_IPV6_REGEX = /^https?:\/\/\[[^\]]*][^/]*/;
34096 var provider = this;
34100 * @name $resourceProvider#defaults
34102 * Object containing default options used when creating `$resource` instances.
34104 * The default values satisfy a wide range of usecases, but you may choose to overwrite any of
34105 * them to further customize your instances. The available properties are:
34107 * - **stripTrailingSlashes** – `{boolean}` – If true, then the trailing slashes from any
34108 * calculated URL will be stripped.<br />
34109 * (Defaults to true.)
34110 * - **cancellable** – `{boolean}` – If true, the request made by a "non-instance" call will be
34111 * cancelled (if not already completed) by calling `$cancelRequest()` on the call's return
34112 * value. For more details, see {@link ngResource.$resource}. This can be overwritten per
34113 * resource class or action.<br />
34114 * (Defaults to false.)
34115 * - **actions** - `{Object.<Object>}` - A hash with default actions declarations. Actions are
34116 * high-level methods corresponding to RESTful actions/methods on resources. An action may
34117 * specify what HTTP method to use, what URL to hit, if the return value will be a single
34118 * object or a collection (array) of objects etc. For more details, see
34119 * {@link ngResource.$resource}. The actions can also be enhanced or overwritten per resource
34121 * The default actions are:
34124 * get: {method: 'GET'},
34125 * save: {method: 'POST'},
34126 * query: {method: 'GET', isArray: true},
34127 * remove: {method: 'DELETE'},
34128 * delete: {method: 'DELETE'}
34134 * For example, you can specify a new `update` action that uses the `PUT` HTTP verb:
34139 * config(['$resourceProvider', function ($resourceProvider) {
34140 * $resourceProvider.defaults.actions.update = {
34146 * Or you can even overwrite the whole `actions` list and specify your own:
34151 * config(['$resourceProvider', function ($resourceProvider) {
34152 * $resourceProvider.defaults.actions = {
34153 * create: {method: 'POST'},
34154 * get: {method: 'GET'},
34155 * getAll: {method: 'GET', isArray:true},
34156 * update: {method: 'PUT'},
34157 * delete: {method: 'DELETE'}
34164 // Strip slashes by default
34165 stripTrailingSlashes: true,
34167 // Make non-instance requests cancellable (via `$cancelRequest()`)
34168 cancellable: false,
34170 // Default actions configuration
34172 'get': {method: 'GET'},
34173 'save': {method: 'POST'},
34174 'query': {method: 'GET', isArray: true},
34175 'remove': {method: 'DELETE'},
34176 'delete': {method: 'DELETE'}
34180 this.$get = ['$http', '$log', '$q', '$timeout', function($http, $log, $q, $timeout) {
34182 var noop = angular.noop,
34183 forEach = angular.forEach,
34184 extend = angular.extend,
34185 copy = angular.copy,
34186 isArray = angular.isArray,
34187 isDefined = angular.isDefined,
34188 isFunction = angular.isFunction,
34189 isNumber = angular.isNumber,
34190 encodeUriQuery = angular.$$encodeUriQuery,
34191 encodeUriSegment = angular.$$encodeUriSegment;
34193 function Route(template, defaults) {
34194 this.template = template;
34195 this.defaults = extend({}, provider.defaults, defaults);
34196 this.urlParams = {};
34199 Route.prototype = {
34200 setUrlParams: function(config, params, actionUrl) {
34202 url = actionUrl || self.template,
34205 protocolAndIpv6 = '';
34207 var urlParams = self.urlParams = Object.create(null);
34208 forEach(url.split(/\W/), function(param) {
34209 if (param === 'hasOwnProperty') {
34210 throw $resourceMinErr('badname', 'hasOwnProperty is not a valid parameter name.');
34212 if (!(new RegExp('^\\d+$').test(param)) && param &&
34213 (new RegExp('(^|[^\\\\]):' + param + '(\\W|$)').test(url))) {
34214 urlParams[param] = {
34215 isQueryParamValue: (new RegExp('\\?.*=:' + param + '(?:\\W|$)')).test(url)
34219 url = url.replace(/\\:/g, ':');
34220 url = url.replace(PROTOCOL_AND_IPV6_REGEX, function(match) {
34221 protocolAndIpv6 = match;
34225 params = params || {};
34226 forEach(self.urlParams, function(paramInfo, urlParam) {
34227 val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
34228 if (isDefined(val) && val !== null) {
34229 if (paramInfo.isQueryParamValue) {
34230 encodedVal = encodeUriQuery(val, true);
34232 encodedVal = encodeUriSegment(val);
34234 url = url.replace(new RegExp(':' + urlParam + '(\\W|$)', 'g'), function(match, p1) {
34235 return encodedVal + p1;
34238 url = url.replace(new RegExp('(/?):' + urlParam + '(\\W|$)', 'g'), function(match,
34239 leadingSlashes, tail) {
34240 if (tail.charAt(0) === '/') {
34243 return leadingSlashes + tail;
34249 // strip trailing slashes and set the url (unless this behavior is specifically disabled)
34250 if (self.defaults.stripTrailingSlashes) {
34251 url = url.replace(/\/+$/, '') || '/';
34254 // Collapse `/.` if found in the last URL path segment before the query.
34255 // E.g. `http://url.com/id/.format?q=x` becomes `http://url.com/id.format?q=x`.
34256 url = url.replace(/\/\.(?=\w+($|\?))/, '.');
34257 // Replace escaped `/\.` with `/.`.
34258 // (If `\.` comes from a param value, it will be encoded as `%5C.`.)
34259 config.url = protocolAndIpv6 + url.replace(/\/(\\|%5C)\./, '/.');
34262 // set params - delegate param encoding to $http
34263 forEach(params, function(value, key) {
34264 if (!self.urlParams[key]) {
34265 config.params = config.params || {};
34266 config.params[key] = value;
34273 function resourceFactory(url, paramDefaults, actions, options) {
34274 var route = new Route(url, options);
34276 actions = extend({}, provider.defaults.actions, actions);
34278 function extractParams(data, actionParams) {
34280 actionParams = extend({}, paramDefaults, actionParams);
34281 forEach(actionParams, function(value, key) {
34282 if (isFunction(value)) { value = value(data); }
34283 ids[key] = value && value.charAt && value.charAt(0) === '@' ?
34284 lookupDottedPath(data, value.substr(1)) : value;
34289 function defaultResponseInterceptor(response) {
34290 return response.resource;
34293 function Resource(value) {
34294 shallowClearAndCopy(value || {}, this);
34297 Resource.prototype.toJSON = function() {
34298 var data = extend({}, this);
34299 delete data.$promise;
34300 delete data.$resolved;
34301 delete data.$cancelRequest;
34305 forEach(actions, function(action, name) {
34306 var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method);
34307 var numericTimeout = action.timeout;
34308 var cancellable = isDefined(action.cancellable) ?
34309 action.cancellable : route.defaults.cancellable;
34311 if (numericTimeout && !isNumber(numericTimeout)) {
34312 $log.debug('ngResource:\n' +
34313 ' Only numeric values are allowed as `timeout`.\n' +
34314 ' Promises are not supported in $resource, because the same value would ' +
34315 'be used for multiple requests. If you are looking for a way to cancel ' +
34316 'requests, you should use the `cancellable` option.');
34317 delete action.timeout;
34318 numericTimeout = null;
34321 Resource[name] = function(a1, a2, a3, a4) {
34322 var params = {}, data, success, error;
34324 switch (arguments.length) {
34331 if (isFunction(a2)) {
34332 if (isFunction(a1)) {
34349 if (isFunction(a1)) success = a1;
34350 else if (hasBody) data = a1;
34355 throw $resourceMinErr('badargs',
34356 'Expected up to 4 arguments [params, data, success, error], got {0} arguments',
34360 var isInstanceCall = this instanceof Resource;
34361 var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data));
34362 var httpConfig = {};
34363 var responseInterceptor = action.interceptor && action.interceptor.response ||
34364 defaultResponseInterceptor;
34365 var responseErrorInterceptor = action.interceptor && action.interceptor.responseError ||
34367 var hasError = !!error;
34368 var hasResponseErrorInterceptor = !!responseErrorInterceptor;
34369 var timeoutDeferred;
34370 var numericTimeoutPromise;
34372 forEach(action, function(value, key) {
34375 httpConfig[key] = copy(value);
34379 case 'interceptor':
34380 case 'cancellable':
34385 if (!isInstanceCall && cancellable) {
34386 timeoutDeferred = $q.defer();
34387 httpConfig.timeout = timeoutDeferred.promise;
34389 if (numericTimeout) {
34390 numericTimeoutPromise = $timeout(timeoutDeferred.resolve, numericTimeout);
34394 if (hasBody) httpConfig.data = data;
34395 route.setUrlParams(httpConfig,
34396 extend({}, extractParams(data, action.params || {}), params),
34399 var promise = $http(httpConfig).then(function(response) {
34400 var data = response.data;
34403 // Need to convert action.isArray to boolean in case it is undefined
34404 if (isArray(data) !== (!!action.isArray)) {
34405 throw $resourceMinErr('badcfg',
34406 'Error in resource configuration for action `{0}`. Expected response to ' +
34407 'contain an {1} but got an {2} (Request: {3} {4})', name, action.isArray ? 'array' : 'object',
34408 isArray(data) ? 'array' : 'object', httpConfig.method, httpConfig.url);
34410 if (action.isArray) {
34412 forEach(data, function(item) {
34413 if (typeof item === 'object') {
34414 value.push(new Resource(item));
34416 // Valid JSON values may be string literals, and these should not be converted
34417 // into objects. These items will not have access to the Resource prototype
34418 // methods, but unfortunately there
34423 var promise = value.$promise; // Save the promise
34424 shallowClearAndCopy(data, value);
34425 value.$promise = promise; // Restore the promise
34428 response.resource = value;
34433 promise = promise['finally'](function() {
34434 value.$resolved = true;
34435 if (!isInstanceCall && cancellable) {
34436 value.$cancelRequest = noop;
34437 $timeout.cancel(numericTimeoutPromise);
34438 timeoutDeferred = numericTimeoutPromise = httpConfig.timeout = null;
34442 promise = promise.then(
34443 function(response) {
34444 var value = responseInterceptor(response);
34445 (success || noop)(value, response.headers, response.status, response.statusText);
34448 (hasError || hasResponseErrorInterceptor) ?
34449 function(response) {
34450 if (hasError && !hasResponseErrorInterceptor) {
34451 // Avoid `Possibly Unhandled Rejection` error,
34452 // but still fulfill the returned promise with a rejection
34453 promise.catch(noop);
34455 if (hasError) error(response);
34456 return hasResponseErrorInterceptor ?
34457 responseErrorInterceptor(response) :
34458 $q.reject(response);
34462 if (!isInstanceCall) {
34463 // we are creating instance / collection
34464 // - set the initial promise
34465 // - return the instance / collection
34466 value.$promise = promise;
34467 value.$resolved = false;
34468 if (cancellable) value.$cancelRequest = cancelRequest;
34476 function cancelRequest(value) {
34477 promise.catch(noop);
34478 timeoutDeferred.resolve(value);
34483 Resource.prototype['$' + name] = function(params, success, error) {
34484 if (isFunction(params)) {
34485 error = success; success = params; params = {};
34487 var result = Resource[name].call(this, params, this, success, error);
34488 return result.$promise || result;
34492 Resource.bind = function(additionalParamDefaults) {
34493 var extendedParamDefaults = extend({}, paramDefaults, additionalParamDefaults);
34494 return resourceFactory(url, extendedParamDefaults, actions, options);
34500 return resourceFactory;
34505 })(window, window.angular);
34510 /***/ function(module, exports, __webpack_require__) {
34512 __webpack_require__(7);
34514 module.exports = 'ui.bootstrap';
34519 /***/ function(module, exports) {
34522 * angular-ui-bootstrap
34523 * http://angular-ui.github.io/bootstrap/
34525 * Version: 1.3.3 - 2016-05-22
34527 */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.datepicker","ui.bootstrap.position","ui.bootstrap.datepickerPopup","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"]);
34528 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/year.html","uib/template/datepickerPopup/popup.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"]);
34529 angular.module('ui.bootstrap.collapse', [])
34531 .directive('uibCollapse', ['$animate', '$q', '$parse', '$injector', function($animate, $q, $parse, $injector) {
34532 var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null;
34534 link: function(scope, element, attrs) {
34535 var expandingExpr = $parse(attrs.expanding),
34536 expandedExpr = $parse(attrs.expanded),
34537 collapsingExpr = $parse(attrs.collapsing),
34538 collapsedExpr = $parse(attrs.collapsed);
34540 if (!scope.$eval(attrs.uibCollapse)) {
34541 element.addClass('in')
34542 .addClass('collapse')
34543 .attr('aria-expanded', true)
34544 .attr('aria-hidden', false)
34545 .css({height: 'auto'});
34548 function expand() {
34549 if (element.hasClass('collapse') && element.hasClass('in')) {
34553 $q.resolve(expandingExpr(scope))
34555 element.removeClass('collapse')
34556 .addClass('collapsing')
34557 .attr('aria-expanded', true)
34558 .attr('aria-hidden', false);
34561 $animateCss(element, {
34564 to: { height: element[0].scrollHeight + 'px' }
34565 }).start()['finally'](expandDone);
34567 $animate.addClass(element, 'in', {
34568 to: { height: element[0].scrollHeight + 'px' }
34569 }).then(expandDone);
34574 function expandDone() {
34575 element.removeClass('collapsing')
34576 .addClass('collapse')
34577 .css({height: 'auto'});
34578 expandedExpr(scope);
34581 function collapse() {
34582 if (!element.hasClass('collapse') && !element.hasClass('in')) {
34583 return collapseDone();
34586 $q.resolve(collapsingExpr(scope))
34589 // IMPORTANT: The height must be set before adding "collapsing" class.
34590 // Otherwise, the browser attempts to animate from height 0 (in
34591 // collapsing class) to the given height here.
34592 .css({height: element[0].scrollHeight + 'px'})
34593 // initially all panel collapse have the collapse class, this removal
34594 // prevents the animation from jumping to collapsed state
34595 .removeClass('collapse')
34596 .addClass('collapsing')
34597 .attr('aria-expanded', false)
34598 .attr('aria-hidden', true);
34601 $animateCss(element, {
34604 }).start()['finally'](collapseDone);
34606 $animate.removeClass(element, 'in', {
34608 }).then(collapseDone);
34613 function collapseDone() {
34614 element.css({height: '0'}); // Required so that collapse works when animation is disabled
34615 element.removeClass('collapsing')
34616 .addClass('collapse');
34617 collapsedExpr(scope);
34620 scope.$watch(attrs.uibCollapse, function(shouldCollapse) {
34621 if (shouldCollapse) {
34631 angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
34633 .constant('uibAccordionConfig', {
34637 .controller('UibAccordionController', ['$scope', '$attrs', 'uibAccordionConfig', function($scope, $attrs, accordionConfig) {
34638 // This array keeps track of the accordion groups
34641 // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
34642 this.closeOthers = function(openGroup) {
34643 var closeOthers = angular.isDefined($attrs.closeOthers) ?
34644 $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
34646 angular.forEach(this.groups, function(group) {
34647 if (group !== openGroup) {
34648 group.isOpen = false;
34654 // This is called from the accordion-group directive to add itself to the accordion
34655 this.addGroup = function(groupScope) {
34657 this.groups.push(groupScope);
34659 groupScope.$on('$destroy', function(event) {
34660 that.removeGroup(groupScope);
34664 // This is called from the accordion-group directive when to remove itself
34665 this.removeGroup = function(group) {
34666 var index = this.groups.indexOf(group);
34667 if (index !== -1) {
34668 this.groups.splice(index, 1);
34673 // The accordion directive simply sets up the directive controller
34674 // and adds an accordion CSS class to itself element.
34675 .directive('uibAccordion', function() {
34677 controller: 'UibAccordionController',
34678 controllerAs: 'accordion',
34680 templateUrl: function(element, attrs) {
34681 return attrs.templateUrl || 'uib/template/accordion/accordion.html';
34686 // The accordion-group directive indicates a block of html that will expand and collapse in an accordion
34687 .directive('uibAccordionGroup', function() {
34689 require: '^uibAccordion', // We need this directive to be inside an accordion
34690 transclude: true, // It transcludes the contents of the directive into the template
34691 replace: true, // The element containing the directive will be replaced with the template
34692 templateUrl: function(element, attrs) {
34693 return attrs.templateUrl || 'uib/template/accordion/accordion-group.html';
34696 heading: '@', // Interpolate the heading attribute onto this scope
34697 panelClass: '@?', // Ditto with panelClass
34701 controller: function() {
34702 this.setHeading = function(element) {
34703 this.heading = element;
34706 link: function(scope, element, attrs, accordionCtrl) {
34707 accordionCtrl.addGroup(scope);
34709 scope.openClass = attrs.openClass || 'panel-open';
34710 scope.panelClass = attrs.panelClass || 'panel-default';
34711 scope.$watch('isOpen', function(value) {
34712 element.toggleClass(scope.openClass, !!value);
34714 accordionCtrl.closeOthers(scope);
34718 scope.toggleOpen = function($event) {
34719 if (!scope.isDisabled) {
34720 if (!$event || $event.which === 32) {
34721 scope.isOpen = !scope.isOpen;
34726 var id = 'accordiongroup-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
34727 scope.headingId = id + '-tab';
34728 scope.panelId = id + '-panel';
34733 // Use accordion-heading below an accordion-group to provide a heading containing HTML
34734 .directive('uibAccordionHeading', function() {
34736 transclude: true, // Grab the contents to be used as the heading
34737 template: '', // In effect remove this element!
34739 require: '^uibAccordionGroup',
34740 link: function(scope, element, attrs, accordionGroupCtrl, transclude) {
34741 // Pass the heading to the accordion-group controller
34742 // so that it can be transcluded into the right place in the template
34743 // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
34744 accordionGroupCtrl.setHeading(transclude(scope, angular.noop));
34749 // Use in the accordion-group template to indicate where you want the heading to be transcluded
34750 // You must provide the property on the accordion-group controller that will hold the transcluded element
34751 .directive('uibAccordionTransclude', function() {
34753 require: '^uibAccordionGroup',
34754 link: function(scope, element, attrs, controller) {
34755 scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) {
34757 var elem = angular.element(element[0].querySelector(getHeaderSelectors()));
34759 elem.append(heading);
34765 function getHeaderSelectors() {
34766 return 'uib-accordion-header,' +
34767 'data-uib-accordion-header,' +
34768 'x-uib-accordion-header,' +
34769 'uib\\:accordion-header,' +
34770 '[uib-accordion-header],' +
34771 '[data-uib-accordion-header],' +
34772 '[x-uib-accordion-header]';
34776 angular.module('ui.bootstrap.alert', [])
34778 .controller('UibAlertController', ['$scope', '$attrs', '$interpolate', '$timeout', function($scope, $attrs, $interpolate, $timeout) {
34779 $scope.closeable = !!$attrs.close;
34781 var dismissOnTimeout = angular.isDefined($attrs.dismissOnTimeout) ?
34782 $interpolate($attrs.dismissOnTimeout)($scope.$parent) : null;
34784 if (dismissOnTimeout) {
34785 $timeout(function() {
34787 }, parseInt(dismissOnTimeout, 10));
34791 .directive('uibAlert', function() {
34793 controller: 'UibAlertController',
34794 controllerAs: 'alert',
34795 templateUrl: function(element, attrs) {
34796 return attrs.templateUrl || 'uib/template/alert/alert.html';
34807 angular.module('ui.bootstrap.buttons', [])
34809 .constant('uibButtonConfig', {
34810 activeClass: 'active',
34811 toggleEvent: 'click'
34814 .controller('UibButtonsController', ['uibButtonConfig', function(buttonConfig) {
34815 this.activeClass = buttonConfig.activeClass || 'active';
34816 this.toggleEvent = buttonConfig.toggleEvent || 'click';
34819 .directive('uibBtnRadio', ['$parse', function($parse) {
34821 require: ['uibBtnRadio', 'ngModel'],
34822 controller: 'UibButtonsController',
34823 controllerAs: 'buttons',
34824 link: function(scope, element, attrs, ctrls) {
34825 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
34826 var uncheckableExpr = $parse(attrs.uibUncheckable);
34828 element.find('input').css({display: 'none'});
34831 ngModelCtrl.$render = function() {
34832 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.uibBtnRadio)));
34836 element.on(buttonsCtrl.toggleEvent, function() {
34837 if (attrs.disabled) {
34841 var isActive = element.hasClass(buttonsCtrl.activeClass);
34843 if (!isActive || angular.isDefined(attrs.uncheckable)) {
34844 scope.$apply(function() {
34845 ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.uibBtnRadio));
34846 ngModelCtrl.$render();
34851 if (attrs.uibUncheckable) {
34852 scope.$watch(uncheckableExpr, function(uncheckable) {
34853 attrs.$set('uncheckable', uncheckable ? '' : undefined);
34860 .directive('uibBtnCheckbox', function() {
34862 require: ['uibBtnCheckbox', 'ngModel'],
34863 controller: 'UibButtonsController',
34864 controllerAs: 'button',
34865 link: function(scope, element, attrs, ctrls) {
34866 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
34868 element.find('input').css({display: 'none'});
34870 function getTrueValue() {
34871 return getCheckboxValue(attrs.btnCheckboxTrue, true);
34874 function getFalseValue() {
34875 return getCheckboxValue(attrs.btnCheckboxFalse, false);
34878 function getCheckboxValue(attribute, defaultValue) {
34879 return angular.isDefined(attribute) ? scope.$eval(attribute) : defaultValue;
34883 ngModelCtrl.$render = function() {
34884 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
34888 element.on(buttonsCtrl.toggleEvent, function() {
34889 if (attrs.disabled) {
34893 scope.$apply(function() {
34894 ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
34895 ngModelCtrl.$render();
34902 angular.module('ui.bootstrap.carousel', [])
34904 .controller('UibCarouselController', ['$scope', '$element', '$interval', '$timeout', '$animate', function($scope, $element, $interval, $timeout, $animate) {
34906 slides = self.slides = $scope.slides = [],
34907 SLIDE_DIRECTION = 'uib-slideDirection',
34908 currentIndex = $scope.active,
34909 currentInterval, isPlaying, bufferedTransitions = [];
34911 var destroyed = false;
34913 self.addSlide = function(slide, element) {
34918 slides.sort(function(a, b) {
34919 return +a.slide.index - +b.slide.index;
34921 //if this is the first slide or the slide is set to active, select it
34922 if (slide.index === $scope.active || slides.length === 1 && !angular.isNumber($scope.active)) {
34923 if ($scope.$currentTransition) {
34924 $scope.$currentTransition = null;
34927 currentIndex = slide.index;
34928 $scope.active = slide.index;
34929 setActive(currentIndex);
34930 self.select(slides[findSlideIndex(slide)]);
34931 if (slides.length === 1) {
34937 self.getCurrentIndex = function() {
34938 for (var i = 0; i < slides.length; i++) {
34939 if (slides[i].slide.index === currentIndex) {
34945 self.next = $scope.next = function() {
34946 var newIndex = (self.getCurrentIndex() + 1) % slides.length;
34948 if (newIndex === 0 && $scope.noWrap()) {
34953 return self.select(slides[newIndex], 'next');
34956 self.prev = $scope.prev = function() {
34957 var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1;
34959 if ($scope.noWrap() && newIndex === slides.length - 1) {
34964 return self.select(slides[newIndex], 'prev');
34967 self.removeSlide = function(slide) {
34968 var index = findSlideIndex(slide);
34970 var bufferedIndex = bufferedTransitions.indexOf(slides[index]);
34971 if (bufferedIndex !== -1) {
34972 bufferedTransitions.splice(bufferedIndex, 1);
34975 //get the index of the slide inside the carousel
34976 slides.splice(index, 1);
34977 if (slides.length > 0 && currentIndex === index) {
34978 if (index >= slides.length) {
34979 currentIndex = slides.length - 1;
34980 $scope.active = currentIndex;
34981 setActive(currentIndex);
34982 self.select(slides[slides.length - 1]);
34984 currentIndex = index;
34985 $scope.active = currentIndex;
34986 setActive(currentIndex);
34987 self.select(slides[index]);
34989 } else if (currentIndex > index) {
34991 $scope.active = currentIndex;
34994 //clean the active value when no more slide
34995 if (slides.length === 0) {
34996 currentIndex = null;
34997 $scope.active = null;
34998 clearBufferedTransitions();
35002 /* direction: "prev" or "next" */
35003 self.select = $scope.select = function(nextSlide, direction) {
35004 var nextIndex = findSlideIndex(nextSlide.slide);
35005 //Decide direction if it's not given
35006 if (direction === undefined) {
35007 direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
35009 //Prevent this user-triggered transition from occurring if there is already one in progress
35010 if (nextSlide.slide.index !== currentIndex &&
35011 !$scope.$currentTransition) {
35012 goNext(nextSlide.slide, nextIndex, direction);
35013 } else if (nextSlide && nextSlide.slide.index !== currentIndex && $scope.$currentTransition) {
35014 bufferedTransitions.push(slides[nextIndex]);
35018 /* Allow outside people to call indexOf on slides array */
35019 $scope.indexOfSlide = function(slide) {
35020 return +slide.slide.index;
35023 $scope.isActive = function(slide) {
35024 return $scope.active === slide.slide.index;
35027 $scope.isPrevDisabled = function() {
35028 return $scope.active === 0 && $scope.noWrap();
35031 $scope.isNextDisabled = function() {
35032 return $scope.active === slides.length - 1 && $scope.noWrap();
35035 $scope.pause = function() {
35036 if (!$scope.noPause) {
35042 $scope.play = function() {
35049 $scope.$on('$destroy', function() {
35054 $scope.$watch('noTransition', function(noTransition) {
35055 $animate.enabled($element, !noTransition);
35058 $scope.$watch('interval', restartTimer);
35060 $scope.$watchCollection('slides', resetTransition);
35062 $scope.$watch('active', function(index) {
35063 if (angular.isNumber(index) && currentIndex !== index) {
35064 for (var i = 0; i < slides.length; i++) {
35065 if (slides[i].slide.index === index) {
35071 var slide = slides[index];
35074 self.select(slides[index]);
35075 currentIndex = index;
35080 function clearBufferedTransitions() {
35081 while (bufferedTransitions.length) {
35082 bufferedTransitions.shift();
35086 function getSlideByIndex(index) {
35087 for (var i = 0, l = slides.length; i < l; ++i) {
35088 if (slides[i].index === index) {
35094 function setActive(index) {
35095 for (var i = 0; i < slides.length; i++) {
35096 slides[i].slide.active = i === index;
35100 function goNext(slide, index, direction) {
35105 angular.extend(slide, {direction: direction});
35106 angular.extend(slides[currentIndex].slide || {}, {direction: direction});
35107 if ($animate.enabled($element) && !$scope.$currentTransition &&
35108 slides[index].element && self.slides.length > 1) {
35109 slides[index].element.data(SLIDE_DIRECTION, slide.direction);
35110 var currentIdx = self.getCurrentIndex();
35112 if (angular.isNumber(currentIdx) && slides[currentIdx].element) {
35113 slides[currentIdx].element.data(SLIDE_DIRECTION, slide.direction);
35116 $scope.$currentTransition = true;
35117 $animate.on('addClass', slides[index].element, function(element, phase) {
35118 if (phase === 'close') {
35119 $scope.$currentTransition = null;
35120 $animate.off('addClass', element);
35121 if (bufferedTransitions.length) {
35122 var nextSlide = bufferedTransitions.pop().slide;
35123 var nextIndex = nextSlide.index;
35124 var nextDirection = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
35125 clearBufferedTransitions();
35127 goNext(nextSlide, nextIndex, nextDirection);
35133 $scope.active = slide.index;
35134 currentIndex = slide.index;
35137 //every time you change slides, reset the timer
35141 function findSlideIndex(slide) {
35142 for (var i = 0; i < slides.length; i++) {
35143 if (slides[i].slide === slide) {
35149 function resetTimer() {
35150 if (currentInterval) {
35151 $interval.cancel(currentInterval);
35152 currentInterval = null;
35156 function resetTransition(slides) {
35157 if (!slides.length) {
35158 $scope.$currentTransition = null;
35159 clearBufferedTransitions();
35163 function restartTimer() {
35165 var interval = +$scope.interval;
35166 if (!isNaN(interval) && interval > 0) {
35167 currentInterval = $interval(timerFn, interval);
35171 function timerFn() {
35172 var interval = +$scope.interval;
35173 if (isPlaying && !isNaN(interval) && interval > 0 && slides.length) {
35181 .directive('uibCarousel', function() {
35185 controller: 'UibCarouselController',
35186 controllerAs: 'carousel',
35187 templateUrl: function(element, attrs) {
35188 return attrs.templateUrl || 'uib/template/carousel/carousel.html';
35200 .directive('uibSlide', function() {
35202 require: '^uibCarousel',
35205 templateUrl: function(element, attrs) {
35206 return attrs.templateUrl || 'uib/template/carousel/slide.html';
35212 link: function (scope, element, attrs, carouselCtrl) {
35213 carouselCtrl.addSlide(scope, element);
35214 //when the scope is destroyed then remove the slide from the current slides array
35215 scope.$on('$destroy', function() {
35216 carouselCtrl.removeSlide(scope);
35222 .animation('.item', ['$animateCss',
35223 function($animateCss) {
35224 var SLIDE_DIRECTION = 'uib-slideDirection';
35226 function removeClass(element, className, callback) {
35227 element.removeClass(className);
35234 beforeAddClass: function(element, className, done) {
35235 if (className === 'active') {
35236 var stopped = false;
35237 var direction = element.data(SLIDE_DIRECTION);
35238 var directionClass = direction === 'next' ? 'left' : 'right';
35239 var removeClassFn = removeClass.bind(this, element,
35240 directionClass + ' ' + direction, done);
35241 element.addClass(direction);
35243 $animateCss(element, {addClass: directionClass})
35245 .done(removeClassFn);
35247 return function() {
35253 beforeRemoveClass: function (element, className, done) {
35254 if (className === 'active') {
35255 var stopped = false;
35256 var direction = element.data(SLIDE_DIRECTION);
35257 var directionClass = direction === 'next' ? 'left' : 'right';
35258 var removeClassFn = removeClass.bind(this, element, directionClass, done);
35260 $animateCss(element, {addClass: directionClass})
35262 .done(removeClassFn);
35264 return function() {
35273 angular.module('ui.bootstrap.dateparser', [])
35275 .service('uibDateParser', ['$log', '$locale', 'dateFilter', 'orderByFilter', function($log, $locale, dateFilter, orderByFilter) {
35276 // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js
35277 var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
35280 var formatCodeToRegex;
35282 this.init = function() {
35283 localeId = $locale.id;
35286 this.formatters = {};
35288 formatCodeToRegex = [
35292 apply: function(value) { this.year = +value; },
35293 formatter: function(date) {
35294 var _date = new Date();
35295 _date.setFullYear(Math.abs(date.getFullYear()));
35296 return dateFilter(_date, 'yyyy');
35302 apply: function(value) { value = +value; this.year = value < 69 ? value + 2000 : value + 1900; },
35303 formatter: function(date) {
35304 var _date = new Date();
35305 _date.setFullYear(Math.abs(date.getFullYear()));
35306 return dateFilter(_date, 'yy');
35312 apply: function(value) { this.year = +value; },
35313 formatter: function(date) {
35314 var _date = new Date();
35315 _date.setFullYear(Math.abs(date.getFullYear()));
35316 return dateFilter(_date, 'y');
35321 regex: '0?[1-9]|1[0-2]',
35322 apply: function(value) { this.month = value - 1; },
35323 formatter: function(date) {
35324 var value = date.getMonth();
35325 if (/^[0-9]$/.test(value)) {
35326 return dateFilter(date, 'MM');
35329 return dateFilter(date, 'M');
35334 regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
35335 apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); },
35336 formatter: function(date) { return dateFilter(date, 'MMMM'); }
35340 regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
35341 apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); },
35342 formatter: function(date) { return dateFilter(date, 'MMM'); }
35346 regex: '0[1-9]|1[0-2]',
35347 apply: function(value) { this.month = value - 1; },
35348 formatter: function(date) { return dateFilter(date, 'MM'); }
35352 regex: '[1-9]|1[0-2]',
35353 apply: function(value) { this.month = value - 1; },
35354 formatter: function(date) { return dateFilter(date, 'M'); }
35358 regex: '[0-2]?[0-9]{1}|3[0-1]{1}',
35359 apply: function(value) { this.date = +value; },
35360 formatter: function(date) {
35361 var value = date.getDate();
35362 if (/^[1-9]$/.test(value)) {
35363 return dateFilter(date, 'dd');
35366 return dateFilter(date, 'd');
35371 regex: '[0-2][0-9]{1}|3[0-1]{1}',
35372 apply: function(value) { this.date = +value; },
35373 formatter: function(date) { return dateFilter(date, 'dd'); }
35377 regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
35378 apply: function(value) { this.date = +value; },
35379 formatter: function(date) { return dateFilter(date, 'd'); }
35383 regex: $locale.DATETIME_FORMATS.DAY.join('|'),
35384 formatter: function(date) { return dateFilter(date, 'EEEE'); }
35388 regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|'),
35389 formatter: function(date) { return dateFilter(date, 'EEE'); }
35393 regex: '(?:0|1)[0-9]|2[0-3]',
35394 apply: function(value) { this.hours = +value; },
35395 formatter: function(date) { return dateFilter(date, 'HH'); }
35399 regex: '0[0-9]|1[0-2]',
35400 apply: function(value) { this.hours = +value; },
35401 formatter: function(date) { return dateFilter(date, 'hh'); }
35405 regex: '1?[0-9]|2[0-3]',
35406 apply: function(value) { this.hours = +value; },
35407 formatter: function(date) { return dateFilter(date, 'H'); }
35411 regex: '[0-9]|1[0-2]',
35412 apply: function(value) { this.hours = +value; },
35413 formatter: function(date) { return dateFilter(date, 'h'); }
35417 regex: '[0-5][0-9]',
35418 apply: function(value) { this.minutes = +value; },
35419 formatter: function(date) { return dateFilter(date, 'mm'); }
35423 regex: '[0-9]|[1-5][0-9]',
35424 apply: function(value) { this.minutes = +value; },
35425 formatter: function(date) { return dateFilter(date, 'm'); }
35429 regex: '[0-9][0-9][0-9]',
35430 apply: function(value) { this.milliseconds = +value; },
35431 formatter: function(date) { return dateFilter(date, 'sss'); }
35435 regex: '[0-5][0-9]',
35436 apply: function(value) { this.seconds = +value; },
35437 formatter: function(date) { return dateFilter(date, 'ss'); }
35441 regex: '[0-9]|[1-5][0-9]',
35442 apply: function(value) { this.seconds = +value; },
35443 formatter: function(date) { return dateFilter(date, 's'); }
35447 regex: $locale.DATETIME_FORMATS.AMPMS.join('|'),
35448 apply: function(value) {
35449 if (this.hours === 12) {
35453 if (value === 'PM') {
35457 formatter: function(date) { return dateFilter(date, 'a'); }
35461 regex: '[+-]\\d{4}',
35462 apply: function(value) {
35463 var matches = value.match(/([+-])(\d{2})(\d{2})/),
35465 hours = matches[2],
35466 minutes = matches[3];
35467 this.hours += toInt(sign + hours);
35468 this.minutes += toInt(sign + minutes);
35470 formatter: function(date) {
35471 return dateFilter(date, 'Z');
35476 regex: '[0-4][0-9]|5[0-3]',
35477 formatter: function(date) { return dateFilter(date, 'ww'); }
35481 regex: '[0-9]|[1-4][0-9]|5[0-3]',
35482 formatter: function(date) { return dateFilter(date, 'w'); }
35486 regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\s/g, '\\s'),
35487 formatter: function(date) { return dateFilter(date, 'GGGG'); }
35491 regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
35492 formatter: function(date) { return dateFilter(date, 'GGG'); }
35496 regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
35497 formatter: function(date) { return dateFilter(date, 'GG'); }
35501 regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
35502 formatter: function(date) { return dateFilter(date, 'G'); }
35509 function createParser(format, func) {
35510 var map = [], regex = format.split('');
35512 // check for literal values
35513 var quoteIndex = format.indexOf('\'');
35514 if (quoteIndex > -1) {
35515 var inLiteral = false;
35516 format = format.split('');
35517 for (var i = quoteIndex; i < format.length; i++) {
35519 if (format[i] === '\'') {
35520 if (i + 1 < format.length && format[i+1] === '\'') { // escaped single quote
35523 } else { // end of literal
35530 if (format[i] === '\'') { // start of literal
35538 format = format.join('');
35541 angular.forEach(formatCodeToRegex, function(data) {
35542 var index = format.indexOf(data.key);
35545 format = format.split('');
35547 regex[index] = '(' + data.regex + ')';
35548 format[index] = '$'; // Custom symbol to define consumed part of format
35549 for (var i = index + 1, n = index + data.key.length; i < n; i++) {
35553 format = format.join('');
35559 matcher: data.regex
35565 regex: new RegExp('^' + regex.join('') + '$'),
35566 map: orderByFilter(map, 'index')
35570 this.filter = function(date, format) {
35571 if (!angular.isDate(date) || isNaN(date) || !format) {
35575 format = $locale.DATETIME_FORMATS[format] || format;
35577 if ($locale.id !== localeId) {
35581 if (!this.formatters[format]) {
35582 this.formatters[format] = createParser(format, 'formatter');
35585 var parser = this.formatters[format],
35588 var _format = format;
35590 return map.reduce(function(str, mapper, i) {
35591 var match = _format.match(new RegExp('(.*)' + mapper.key));
35592 if (match && angular.isString(match[1])) {
35594 _format = _format.replace(match[1] + mapper.key, '');
35597 var endStr = i === map.length - 1 ? _format : '';
35599 if (mapper.apply) {
35600 return str + mapper.apply.call(null, date) + endStr;
35603 return str + endStr;
35607 this.parse = function(input, format, baseDate) {
35608 if (!angular.isString(input) || !format) {
35612 format = $locale.DATETIME_FORMATS[format] || format;
35613 format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&');
35615 if ($locale.id !== localeId) {
35619 if (!this.parsers[format]) {
35620 this.parsers[format] = createParser(format, 'apply');
35623 var parser = this.parsers[format],
35624 regex = parser.regex,
35626 results = input.match(regex),
35628 if (results && results.length) {
35630 if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) {
35632 year: baseDate.getFullYear(),
35633 month: baseDate.getMonth(),
35634 date: baseDate.getDate(),
35635 hours: baseDate.getHours(),
35636 minutes: baseDate.getMinutes(),
35637 seconds: baseDate.getSeconds(),
35638 milliseconds: baseDate.getMilliseconds()
35642 $log.warn('dateparser:', 'baseDate is not a valid date');
35644 fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 };
35647 for (var i = 1, n = results.length; i < n; i++) {
35648 var mapper = map[i - 1];
35649 if (mapper.matcher === 'Z') {
35653 if (mapper.apply) {
35654 mapper.apply.call(fields, results[i]);
35658 var datesetter = tzOffset ? Date.prototype.setUTCFullYear :
35659 Date.prototype.setFullYear;
35660 var timesetter = tzOffset ? Date.prototype.setUTCHours :
35661 Date.prototype.setHours;
35663 if (isValid(fields.year, fields.month, fields.date)) {
35664 if (angular.isDate(baseDate) && !isNaN(baseDate.getTime()) && !tzOffset) {
35665 dt = new Date(baseDate);
35666 datesetter.call(dt, fields.year, fields.month, fields.date);
35667 timesetter.call(dt, fields.hours, fields.minutes,
35668 fields.seconds, fields.milliseconds);
35671 datesetter.call(dt, fields.year, fields.month, fields.date);
35672 timesetter.call(dt, fields.hours || 0, fields.minutes || 0,
35673 fields.seconds || 0, fields.milliseconds || 0);
35681 // Check if date is valid for specific month (and year for February).
35682 // Month: 0 = Jan, 1 = Feb, etc
35683 function isValid(year, month, date) {
35688 if (month === 1 && date > 28) {
35689 return date === 29 && (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0);
35692 if (month === 3 || month === 5 || month === 8 || month === 10) {
35699 function toInt(str) {
35700 return parseInt(str, 10);
35703 this.toTimezone = toTimezone;
35704 this.fromTimezone = fromTimezone;
35705 this.timezoneToOffset = timezoneToOffset;
35706 this.addDateMinutes = addDateMinutes;
35707 this.convertTimezoneToLocal = convertTimezoneToLocal;
35709 function toTimezone(date, timezone) {
35710 return date && timezone ? convertTimezoneToLocal(date, timezone) : date;
35713 function fromTimezone(date, timezone) {
35714 return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date;
35717 //https://github.com/angular/angular.js/blob/622c42169699ec07fc6daaa19fe6d224e5d2f70e/src/Angular.js#L1207
35718 function timezoneToOffset(timezone, fallback) {
35719 timezone = timezone.replace(/:/g, '');
35720 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
35721 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
35724 function addDateMinutes(date, minutes) {
35725 date = new Date(date.getTime());
35726 date.setMinutes(date.getMinutes() + minutes);
35730 function convertTimezoneToLocal(date, timezone, reverse) {
35731 reverse = reverse ? -1 : 1;
35732 var dateTimezoneOffset = date.getTimezoneOffset();
35733 var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
35734 return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset));
35738 // Avoiding use of ng-class as it creates a lot of watchers when a class is to be applied to
35739 // at most one element.
35740 angular.module('ui.bootstrap.isClass', [])
35741 .directive('uibIsClass', [
35743 function ($animate) {
35744 // 11111111 22222222
35745 var ON_REGEXP = /^\s*([\s\S]+?)\s+on\s+([\s\S]+?)\s*$/;
35746 // 11111111 22222222
35747 var IS_REGEXP = /^\s*([\s\S]+?)\s+for\s+([\s\S]+?)\s*$/;
35749 var dataPerTracked = {};
35753 compile: function(tElement, tAttrs) {
35754 var linkedScopes = [];
35755 var instances = [];
35756 var expToData = {};
35757 var lastActivated = null;
35758 var onExpMatches = tAttrs.uibIsClass.match(ON_REGEXP);
35759 var onExp = onExpMatches[2];
35760 var expsStr = onExpMatches[1];
35761 var exps = expsStr.split(',');
35765 function linkFn(scope, element, attrs) {
35766 linkedScopes.push(scope);
35772 exps.forEach(function(exp, k) {
35773 addForExp(exp, scope);
35776 scope.$on('$destroy', removeScope);
35779 function addForExp(exp, scope) {
35780 var matches = exp.match(IS_REGEXP);
35781 var clazz = scope.$eval(matches[1]);
35782 var compareWithExp = matches[2];
35783 var data = expToData[exp];
35785 var watchFn = function(compareWithVal) {
35786 var newActivated = null;
35787 instances.some(function(instance) {
35788 var thisVal = instance.scope.$eval(onExp);
35789 if (thisVal === compareWithVal) {
35790 newActivated = instance;
35794 if (data.lastActivated !== newActivated) {
35795 if (data.lastActivated) {
35796 $animate.removeClass(data.lastActivated.element, clazz);
35798 if (newActivated) {
35799 $animate.addClass(newActivated.element, clazz);
35801 data.lastActivated = newActivated;
35804 expToData[exp] = data = {
35805 lastActivated: null,
35808 compareWithExp: compareWithExp,
35809 watcher: scope.$watch(compareWithExp, watchFn)
35812 data.watchFn(scope.$eval(compareWithExp));
35815 function removeScope(e) {
35816 var removedScope = e.targetScope;
35817 var index = linkedScopes.indexOf(removedScope);
35818 linkedScopes.splice(index, 1);
35819 instances.splice(index, 1);
35820 if (linkedScopes.length) {
35821 var newWatchScope = linkedScopes[0];
35822 angular.forEach(expToData, function(data) {
35823 if (data.scope === removedScope) {
35824 data.watcher = newWatchScope.$watch(data.compareWithExp, data.watchFn);
35825 data.scope = newWatchScope;
35835 angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.isClass'])
35837 .value('$datepickerSuppressError', false)
35839 .value('$datepickerLiteralWarning', true)
35841 .constant('uibDatepickerConfig', {
35842 datepickerMode: 'day',
35844 formatMonth: 'MMMM',
35845 formatYear: 'yyyy',
35846 formatDayHeader: 'EEE',
35847 formatDayTitle: 'MMMM yyyy',
35848 formatMonthTitle: 'yyyy',
35853 ngModelOptions: {},
35854 shortcutPropagation: false,
35860 .controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$locale', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerLiteralWarning', '$datepickerSuppressError', 'uibDateParser',
35861 function($scope, $attrs, $parse, $interpolate, $locale, $log, dateFilter, datepickerConfig, $datepickerLiteralWarning, $datepickerSuppressError, dateParser) {
35863 ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl;
35864 ngModelOptions = {},
35865 watchListeners = [],
35866 optionsUsed = !!$attrs.datepickerOptions;
35868 if (!$scope.datepickerOptions) {
35869 $scope.datepickerOptions = {};
35873 this.modes = ['day', 'month', 'year'];
35883 'formatMonthTitle',
35890 'shortcutPropagation',
35894 ].forEach(function(key) {
35896 case 'customClass':
35897 case 'dateDisabled':
35898 $scope[key] = $scope.datepickerOptions[key] || angular.noop;
35900 case 'datepickerMode':
35901 $scope.datepickerMode = angular.isDefined($scope.datepickerOptions.datepickerMode) ?
35902 $scope.datepickerOptions.datepickerMode : datepickerConfig.datepickerMode;
35905 case 'formatDayHeader':
35906 case 'formatDayTitle':
35907 case 'formatMonth':
35908 case 'formatMonthTitle':
35910 self[key] = angular.isDefined($scope.datepickerOptions[key]) ?
35911 $interpolate($scope.datepickerOptions[key])($scope.$parent) :
35912 datepickerConfig[key];
35915 case 'shortcutPropagation':
35916 case 'yearColumns':
35918 self[key] = angular.isDefined($scope.datepickerOptions[key]) ?
35919 $scope.datepickerOptions[key] : datepickerConfig[key];
35921 case 'startingDay':
35922 if (angular.isDefined($scope.datepickerOptions.startingDay)) {
35923 self.startingDay = $scope.datepickerOptions.startingDay;
35924 } else if (angular.isNumber(datepickerConfig.startingDay)) {
35925 self.startingDay = datepickerConfig.startingDay;
35927 self.startingDay = ($locale.DATETIME_FORMATS.FIRSTDAYOFWEEK + 8) % 7;
35933 $scope.$watch('datepickerOptions.' + key, function(value) {
35935 if (angular.isDate(value)) {
35936 self[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone);
35938 if ($datepickerLiteralWarning) {
35939 $log.warn('Literal date support has been deprecated, please switch to date object usage');
35942 self[key] = new Date(dateFilter(value, 'medium'));
35945 self[key] = datepickerConfig[key] ?
35946 dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) :
35950 self.refreshView();
35956 if ($scope.datepickerOptions[key]) {
35957 $scope.$watch(function() { return $scope.datepickerOptions[key]; }, function(value) {
35958 self[key] = $scope[key] = angular.isDefined(value) ? value : datepickerOptions[key];
35959 if (key === 'minMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) < self.modes.indexOf(self[key]) ||
35960 key === 'maxMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) > self.modes.indexOf(self[key])) {
35961 $scope.datepickerMode = self[key];
35962 $scope.datepickerOptions.datepickerMode = self[key];
35966 self[key] = $scope[key] = datepickerConfig[key] || null;
35973 $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
35975 $scope.disabled = angular.isDefined($attrs.disabled) || false;
35976 if (angular.isDefined($attrs.ngDisabled)) {
35977 watchListeners.push($scope.$parent.$watch($attrs.ngDisabled, function(disabled) {
35978 $scope.disabled = disabled;
35979 self.refreshView();
35983 $scope.isActive = function(dateObject) {
35984 if (self.compare(dateObject.date, self.activeDate) === 0) {
35985 $scope.activeDateId = dateObject.uid;
35991 this.init = function(ngModelCtrl_) {
35992 ngModelCtrl = ngModelCtrl_;
35993 ngModelOptions = ngModelCtrl_.$options || datepickerConfig.ngModelOptions;
35994 if ($scope.datepickerOptions.initDate) {
35995 self.activeDate = dateParser.fromTimezone($scope.datepickerOptions.initDate, ngModelOptions.timezone) || new Date();
35996 $scope.$watch('datepickerOptions.initDate', function(initDate) {
35997 if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
35998 self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone);
35999 self.refreshView();
36003 self.activeDate = new Date();
36006 var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : new Date();
36007 this.activeDate = !isNaN(date) ?
36008 dateParser.fromTimezone(date, ngModelOptions.timezone) :
36009 dateParser.fromTimezone(new Date(), ngModelOptions.timezone);
36011 ngModelCtrl.$render = function() {
36016 this.render = function() {
36017 if (ngModelCtrl.$viewValue) {
36018 var date = new Date(ngModelCtrl.$viewValue),
36019 isValid = !isNaN(date);
36022 this.activeDate = dateParser.fromTimezone(date, ngModelOptions.timezone);
36023 } else if (!$datepickerSuppressError) {
36024 $log.error('Datepicker directive: "ng-model" value must be a Date object');
36027 this.refreshView();
36030 this.refreshView = function() {
36031 if (this.element) {
36032 $scope.selectedDt = null;
36033 this._refreshView();
36034 if ($scope.activeDt) {
36035 $scope.activeDateId = $scope.activeDt.uid;
36038 var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
36039 date = dateParser.fromTimezone(date, ngModelOptions.timezone);
36040 ngModelCtrl.$setValidity('dateDisabled', !date ||
36041 this.element && !this.isDisabled(date));
36045 this.createDateObject = function(date, format) {
36046 var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
36047 model = dateParser.fromTimezone(model, ngModelOptions.timezone);
36048 var today = new Date();
36049 today = dateParser.fromTimezone(today, ngModelOptions.timezone);
36050 var time = this.compare(date, today);
36053 label: dateParser.filter(date, format),
36054 selected: model && this.compare(date, model) === 0,
36055 disabled: this.isDisabled(date),
36057 current: time === 0,
36059 customClass: this.customClass(date) || null
36062 if (model && this.compare(date, model) === 0) {
36063 $scope.selectedDt = dt;
36066 if (self.activeDate && this.compare(dt.date, self.activeDate) === 0) {
36067 $scope.activeDt = dt;
36073 this.isDisabled = function(date) {
36074 return $scope.disabled ||
36075 this.minDate && this.compare(date, this.minDate) < 0 ||
36076 this.maxDate && this.compare(date, this.maxDate) > 0 ||
36077 $scope.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode});
36080 this.customClass = function(date) {
36081 return $scope.customClass({date: date, mode: $scope.datepickerMode});
36084 // Split array into smaller arrays
36085 this.split = function(arr, size) {
36087 while (arr.length > 0) {
36088 arrays.push(arr.splice(0, size));
36093 $scope.select = function(date) {
36094 if ($scope.datepickerMode === self.minMode) {
36095 var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.timezone) : new Date(0, 0, 0, 0, 0, 0, 0);
36096 dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
36097 dt = dateParser.toTimezone(dt, ngModelOptions.timezone);
36098 ngModelCtrl.$setViewValue(dt);
36099 ngModelCtrl.$render();
36101 self.activeDate = date;
36102 setMode(self.modes[self.modes.indexOf($scope.datepickerMode) - 1]);
36104 $scope.$emit('uib:datepicker.mode');
36107 $scope.$broadcast('uib:datepicker.focus');
36110 $scope.move = function(direction) {
36111 var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
36112 month = self.activeDate.getMonth() + direction * (self.step.months || 0);
36113 self.activeDate.setFullYear(year, month, 1);
36114 self.refreshView();
36117 $scope.toggleMode = function(direction) {
36118 direction = direction || 1;
36120 if ($scope.datepickerMode === self.maxMode && direction === 1 ||
36121 $scope.datepickerMode === self.minMode && direction === -1) {
36125 setMode(self.modes[self.modes.indexOf($scope.datepickerMode) + direction]);
36127 $scope.$emit('uib:datepicker.mode');
36130 // Key event mapper
36131 $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' };
36133 var focusElement = function() {
36134 self.element[0].focus();
36137 // Listen for focus requests from popup directive
36138 $scope.$on('uib:datepicker.focus', focusElement);
36140 $scope.keydown = function(evt) {
36141 var key = $scope.keys[evt.which];
36143 if (!key || evt.shiftKey || evt.altKey || $scope.disabled) {
36147 evt.preventDefault();
36148 if (!self.shortcutPropagation) {
36149 evt.stopPropagation();
36152 if (key === 'enter' || key === 'space') {
36153 if (self.isDisabled(self.activeDate)) {
36154 return; // do nothing
36156 $scope.select(self.activeDate);
36157 } else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
36158 $scope.toggleMode(key === 'up' ? 1 : -1);
36160 self.handleKeyDown(key, evt);
36161 self.refreshView();
36165 $scope.$on('$destroy', function() {
36166 //Clear all watch listeners on destroy
36167 while (watchListeners.length) {
36168 watchListeners.shift()();
36172 function setMode(mode) {
36173 $scope.datepickerMode = mode;
36174 $scope.datepickerOptions.datepickerMode = mode;
36178 .controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
36179 var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
36181 this.step = { months: 1 };
36182 this.element = $element;
36183 function getDaysInMonth(year, month) {
36184 return month === 1 && year % 4 === 0 &&
36185 (year % 100 !== 0 || year % 400 === 0) ? 29 : DAYS_IN_MONTH[month];
36188 this.init = function(ctrl) {
36189 angular.extend(ctrl, this);
36190 scope.showWeeks = ctrl.showWeeks;
36191 ctrl.refreshView();
36194 this.getDates = function(startDate, n) {
36195 var dates = new Array(n), current = new Date(startDate), i = 0, date;
36197 date = new Date(current);
36199 current.setDate(current.getDate() + 1);
36204 this._refreshView = function() {
36205 var year = this.activeDate.getFullYear(),
36206 month = this.activeDate.getMonth(),
36207 firstDayOfMonth = new Date(this.activeDate);
36209 firstDayOfMonth.setFullYear(year, month, 1);
36211 var difference = this.startingDay - firstDayOfMonth.getDay(),
36212 numDisplayedFromPreviousMonth = difference > 0 ?
36213 7 - difference : - difference,
36214 firstDate = new Date(firstDayOfMonth);
36216 if (numDisplayedFromPreviousMonth > 0) {
36217 firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
36220 // 42 is the number of days on a six-week calendar
36221 var days = this.getDates(firstDate, 42);
36222 for (var i = 0; i < 42; i ++) {
36223 days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), {
36224 secondary: days[i].getMonth() !== month,
36225 uid: scope.uniqueId + '-' + i
36229 scope.labels = new Array(7);
36230 for (var j = 0; j < 7; j++) {
36231 scope.labels[j] = {
36232 abbr: dateFilter(days[j].date, this.formatDayHeader),
36233 full: dateFilter(days[j].date, 'EEEE')
36237 scope.title = dateFilter(this.activeDate, this.formatDayTitle);
36238 scope.rows = this.split(days, 7);
36240 if (scope.showWeeks) {
36241 scope.weekNumbers = [];
36242 var thursdayIndex = (4 + 7 - this.startingDay) % 7,
36243 numWeeks = scope.rows.length;
36244 for (var curWeek = 0; curWeek < numWeeks; curWeek++) {
36245 scope.weekNumbers.push(
36246 getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date));
36251 this.compare = function(date1, date2) {
36252 var _date1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate());
36253 var _date2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
36254 _date1.setFullYear(date1.getFullYear());
36255 _date2.setFullYear(date2.getFullYear());
36256 return _date1 - _date2;
36259 function getISO8601WeekNumber(date) {
36260 var checkDate = new Date(date);
36261 checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
36262 var time = checkDate.getTime();
36263 checkDate.setMonth(0); // Compare with Jan 1
36264 checkDate.setDate(1);
36265 return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
36268 this.handleKeyDown = function(key, evt) {
36269 var date = this.activeDate.getDate();
36271 if (key === 'left') {
36273 } else if (key === 'up') {
36275 } else if (key === 'right') {
36277 } else if (key === 'down') {
36279 } else if (key === 'pageup' || key === 'pagedown') {
36280 var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1);
36281 this.activeDate.setMonth(month, 1);
36282 date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date);
36283 } else if (key === 'home') {
36285 } else if (key === 'end') {
36286 date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth());
36288 this.activeDate.setDate(date);
36292 .controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
36293 this.step = { years: 1 };
36294 this.element = $element;
36296 this.init = function(ctrl) {
36297 angular.extend(ctrl, this);
36298 ctrl.refreshView();
36301 this._refreshView = function() {
36302 var months = new Array(12),
36303 year = this.activeDate.getFullYear(),
36306 for (var i = 0; i < 12; i++) {
36307 date = new Date(this.activeDate);
36308 date.setFullYear(year, i, 1);
36309 months[i] = angular.extend(this.createDateObject(date, this.formatMonth), {
36310 uid: scope.uniqueId + '-' + i
36314 scope.title = dateFilter(this.activeDate, this.formatMonthTitle);
36315 scope.rows = this.split(months, 3);
36318 this.compare = function(date1, date2) {
36319 var _date1 = new Date(date1.getFullYear(), date1.getMonth());
36320 var _date2 = new Date(date2.getFullYear(), date2.getMonth());
36321 _date1.setFullYear(date1.getFullYear());
36322 _date2.setFullYear(date2.getFullYear());
36323 return _date1 - _date2;
36326 this.handleKeyDown = function(key, evt) {
36327 var date = this.activeDate.getMonth();
36329 if (key === 'left') {
36331 } else if (key === 'up') {
36333 } else if (key === 'right') {
36335 } else if (key === 'down') {
36337 } else if (key === 'pageup' || key === 'pagedown') {
36338 var year = this.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1);
36339 this.activeDate.setFullYear(year);
36340 } else if (key === 'home') {
36342 } else if (key === 'end') {
36345 this.activeDate.setMonth(date);
36349 .controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
36350 var columns, range;
36351 this.element = $element;
36353 function getStartingYear(year) {
36354 return parseInt((year - 1) / range, 10) * range + 1;
36357 this.yearpickerInit = function() {
36358 columns = this.yearColumns;
36359 range = this.yearRows * columns;
36360 this.step = { years: range };
36363 this._refreshView = function() {
36364 var years = new Array(range), date;
36366 for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) {
36367 date = new Date(this.activeDate);
36368 date.setFullYear(start + i, 0, 1);
36369 years[i] = angular.extend(this.createDateObject(date, this.formatYear), {
36370 uid: scope.uniqueId + '-' + i
36374 scope.title = [years[0].label, years[range - 1].label].join(' - ');
36375 scope.rows = this.split(years, columns);
36376 scope.columns = columns;
36379 this.compare = function(date1, date2) {
36380 return date1.getFullYear() - date2.getFullYear();
36383 this.handleKeyDown = function(key, evt) {
36384 var date = this.activeDate.getFullYear();
36386 if (key === 'left') {
36388 } else if (key === 'up') {
36389 date = date - columns;
36390 } else if (key === 'right') {
36392 } else if (key === 'down') {
36393 date = date + columns;
36394 } else if (key === 'pageup' || key === 'pagedown') {
36395 date += (key === 'pageup' ? - 1 : 1) * range;
36396 } else if (key === 'home') {
36397 date = getStartingYear(this.activeDate.getFullYear());
36398 } else if (key === 'end') {
36399 date = getStartingYear(this.activeDate.getFullYear()) + range - 1;
36401 this.activeDate.setFullYear(date);
36405 .directive('uibDatepicker', function() {
36408 templateUrl: function(element, attrs) {
36409 return attrs.templateUrl || 'uib/template/datepicker/datepicker.html';
36412 datepickerOptions: '=?'
36414 require: ['uibDatepicker', '^ngModel'],
36415 controller: 'UibDatepickerController',
36416 controllerAs: 'datepicker',
36417 link: function(scope, element, attrs, ctrls) {
36418 var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
36420 datepickerCtrl.init(ngModelCtrl);
36425 .directive('uibDaypicker', function() {
36428 templateUrl: function(element, attrs) {
36429 return attrs.templateUrl || 'uib/template/datepicker/day.html';
36431 require: ['^uibDatepicker', 'uibDaypicker'],
36432 controller: 'UibDaypickerController',
36433 link: function(scope, element, attrs, ctrls) {
36434 var datepickerCtrl = ctrls[0],
36435 daypickerCtrl = ctrls[1];
36437 daypickerCtrl.init(datepickerCtrl);
36442 .directive('uibMonthpicker', function() {
36445 templateUrl: function(element, attrs) {
36446 return attrs.templateUrl || 'uib/template/datepicker/month.html';
36448 require: ['^uibDatepicker', 'uibMonthpicker'],
36449 controller: 'UibMonthpickerController',
36450 link: function(scope, element, attrs, ctrls) {
36451 var datepickerCtrl = ctrls[0],
36452 monthpickerCtrl = ctrls[1];
36454 monthpickerCtrl.init(datepickerCtrl);
36459 .directive('uibYearpicker', function() {
36462 templateUrl: function(element, attrs) {
36463 return attrs.templateUrl || 'uib/template/datepicker/year.html';
36465 require: ['^uibDatepicker', 'uibYearpicker'],
36466 controller: 'UibYearpickerController',
36467 link: function(scope, element, attrs, ctrls) {
36468 var ctrl = ctrls[0];
36469 angular.extend(ctrl, ctrls[1]);
36470 ctrl.yearpickerInit();
36472 ctrl.refreshView();
36477 angular.module('ui.bootstrap.position', [])
36480 * A set of utility methods for working with the DOM.
36481 * It is meant to be used where we need to absolute-position elements in
36482 * relation to another element (this is the case for tooltips, popovers,
36483 * typeahead suggestions etc.).
36485 .factory('$uibPosition', ['$document', '$window', function($document, $window) {
36487 * Used by scrollbarWidth() function to cache scrollbar's width.
36488 * Do not access this variable directly, use scrollbarWidth() instead.
36490 var SCROLLBAR_WIDTH;
36492 * scrollbar on body and html element in IE and Edge overlay
36493 * content and should be considered 0 width.
36495 var BODY_SCROLLBAR_WIDTH;
36496 var OVERFLOW_REGEX = {
36497 normal: /(auto|scroll)/,
36498 hidden: /(auto|scroll|hidden)/
36500 var PLACEMENT_REGEX = {
36501 auto: /\s?auto?\s?/i,
36502 primary: /^(top|bottom|left|right)$/,
36503 secondary: /^(top|bottom|left|right|center)$/,
36504 vertical: /^(top|bottom)$/
36506 var BODY_REGEX = /(HTML|BODY)/;
36511 * Provides a raw DOM element from a jQuery/jQLite element.
36513 * @param {element} elem - The element to convert.
36515 * @returns {element} A HTML element.
36517 getRawNode: function(elem) {
36518 return elem.nodeName ? elem : elem[0] || elem;
36522 * Provides a parsed number for a style property. Strips
36523 * units and casts invalid numbers to 0.
36525 * @param {string} value - The style value to parse.
36527 * @returns {number} A valid number.
36529 parseStyle: function(value) {
36530 value = parseFloat(value);
36531 return isFinite(value) ? value : 0;
36535 * Provides the closest positioned ancestor.
36537 * @param {element} element - The element to get the offest parent for.
36539 * @returns {element} The closest positioned ancestor.
36541 offsetParent: function(elem) {
36542 elem = this.getRawNode(elem);
36544 var offsetParent = elem.offsetParent || $document[0].documentElement;
36546 function isStaticPositioned(el) {
36547 return ($window.getComputedStyle(el).position || 'static') === 'static';
36550 while (offsetParent && offsetParent !== $document[0].documentElement && isStaticPositioned(offsetParent)) {
36551 offsetParent = offsetParent.offsetParent;
36554 return offsetParent || $document[0].documentElement;
36558 * Provides the scrollbar width, concept from TWBS measureScrollbar()
36559 * function in https://github.com/twbs/bootstrap/blob/master/js/modal.js
36560 * In IE and Edge, scollbar on body and html element overlay and should
36561 * return a width of 0.
36563 * @returns {number} The width of the browser scollbar.
36565 scrollbarWidth: function(isBody) {
36567 if (angular.isUndefined(BODY_SCROLLBAR_WIDTH)) {
36568 var bodyElem = $document.find('body');
36569 bodyElem.addClass('uib-position-body-scrollbar-measure');
36570 BODY_SCROLLBAR_WIDTH = $window.innerWidth - bodyElem[0].clientWidth;
36571 BODY_SCROLLBAR_WIDTH = isFinite(BODY_SCROLLBAR_WIDTH) ? BODY_SCROLLBAR_WIDTH : 0;
36572 bodyElem.removeClass('uib-position-body-scrollbar-measure');
36574 return BODY_SCROLLBAR_WIDTH;
36577 if (angular.isUndefined(SCROLLBAR_WIDTH)) {
36578 var scrollElem = angular.element('<div class="uib-position-scrollbar-measure"></div>');
36579 $document.find('body').append(scrollElem);
36580 SCROLLBAR_WIDTH = scrollElem[0].offsetWidth - scrollElem[0].clientWidth;
36581 SCROLLBAR_WIDTH = isFinite(SCROLLBAR_WIDTH) ? SCROLLBAR_WIDTH : 0;
36582 scrollElem.remove();
36585 return SCROLLBAR_WIDTH;
36589 * Provides the padding required on an element to replace the scrollbar.
36591 * @returns {object} An object with the following properties:
36593 * <li>**scrollbarWidth**: the width of the scrollbar</li>
36594 * <li>**widthOverflow**: whether the the width is overflowing</li>
36595 * <li>**right**: the amount of right padding on the element needed to replace the scrollbar</li>
36596 * <li>**rightOriginal**: the amount of right padding currently on the element</li>
36597 * <li>**heightOverflow**: whether the the height is overflowing</li>
36598 * <li>**bottom**: the amount of bottom padding on the element needed to replace the scrollbar</li>
36599 * <li>**bottomOriginal**: the amount of bottom padding currently on the element</li>
36602 scrollbarPadding: function(elem) {
36603 elem = this.getRawNode(elem);
36605 var elemStyle = $window.getComputedStyle(elem);
36606 var paddingRight = this.parseStyle(elemStyle.paddingRight);
36607 var paddingBottom = this.parseStyle(elemStyle.paddingBottom);
36608 var scrollParent = this.scrollParent(elem, false, true);
36609 var scrollbarWidth = this.scrollbarWidth(scrollParent, BODY_REGEX.test(scrollParent.tagName));
36612 scrollbarWidth: scrollbarWidth,
36613 widthOverflow: scrollParent.scrollWidth > scrollParent.clientWidth,
36614 right: paddingRight + scrollbarWidth,
36615 originalRight: paddingRight,
36616 heightOverflow: scrollParent.scrollHeight > scrollParent.clientHeight,
36617 bottom: paddingBottom + scrollbarWidth,
36618 originalBottom: paddingBottom
36623 * Checks to see if the element is scrollable.
36625 * @param {element} elem - The element to check.
36626 * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
36627 * default is false.
36629 * @returns {boolean} Whether the element is scrollable.
36631 isScrollable: function(elem, includeHidden) {
36632 elem = this.getRawNode(elem);
36634 var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
36635 var elemStyle = $window.getComputedStyle(elem);
36636 return overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX);
36640 * Provides the closest scrollable ancestor.
36641 * A port of the jQuery UI scrollParent method:
36642 * https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js
36644 * @param {element} elem - The element to find the scroll parent of.
36645 * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
36646 * default is false.
36647 * @param {boolean=} [includeSelf=false] - Should the element being passed be
36648 * included in the scrollable llokup.
36650 * @returns {element} A HTML element.
36652 scrollParent: function(elem, includeHidden, includeSelf) {
36653 elem = this.getRawNode(elem);
36655 var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
36656 var documentEl = $document[0].documentElement;
36657 var elemStyle = $window.getComputedStyle(elem);
36658 if (includeSelf && overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX)) {
36661 var excludeStatic = elemStyle.position === 'absolute';
36662 var scrollParent = elem.parentElement || documentEl;
36664 if (scrollParent === documentEl || elemStyle.position === 'fixed') {
36668 while (scrollParent.parentElement && scrollParent !== documentEl) {
36669 var spStyle = $window.getComputedStyle(scrollParent);
36670 if (excludeStatic && spStyle.position !== 'static') {
36671 excludeStatic = false;
36674 if (!excludeStatic && overflowRegex.test(spStyle.overflow + spStyle.overflowY + spStyle.overflowX)) {
36677 scrollParent = scrollParent.parentElement;
36680 return scrollParent;
36684 * Provides read-only equivalent of jQuery's position function:
36685 * http://api.jquery.com/position/ - distance to closest positioned
36686 * ancestor. Does not account for margins by default like jQuery position.
36688 * @param {element} elem - The element to caclulate the position on.
36689 * @param {boolean=} [includeMargins=false] - Should margins be accounted
36690 * for, default is false.
36692 * @returns {object} An object with the following properties:
36694 * <li>**width**: the width of the element</li>
36695 * <li>**height**: the height of the element</li>
36696 * <li>**top**: distance to top edge of offset parent</li>
36697 * <li>**left**: distance to left edge of offset parent</li>
36700 position: function(elem, includeMagins) {
36701 elem = this.getRawNode(elem);
36703 var elemOffset = this.offset(elem);
36704 if (includeMagins) {
36705 var elemStyle = $window.getComputedStyle(elem);
36706 elemOffset.top -= this.parseStyle(elemStyle.marginTop);
36707 elemOffset.left -= this.parseStyle(elemStyle.marginLeft);
36709 var parent = this.offsetParent(elem);
36710 var parentOffset = {top: 0, left: 0};
36712 if (parent !== $document[0].documentElement) {
36713 parentOffset = this.offset(parent);
36714 parentOffset.top += parent.clientTop - parent.scrollTop;
36715 parentOffset.left += parent.clientLeft - parent.scrollLeft;
36719 width: Math.round(angular.isNumber(elemOffset.width) ? elemOffset.width : elem.offsetWidth),
36720 height: Math.round(angular.isNumber(elemOffset.height) ? elemOffset.height : elem.offsetHeight),
36721 top: Math.round(elemOffset.top - parentOffset.top),
36722 left: Math.round(elemOffset.left - parentOffset.left)
36727 * Provides read-only equivalent of jQuery's offset function:
36728 * http://api.jquery.com/offset/ - distance to viewport. Does
36729 * not account for borders, margins, or padding on the body
36732 * @param {element} elem - The element to calculate the offset on.
36734 * @returns {object} An object with the following properties:
36736 * <li>**width**: the width of the element</li>
36737 * <li>**height**: the height of the element</li>
36738 * <li>**top**: distance to top edge of viewport</li>
36739 * <li>**right**: distance to bottom edge of viewport</li>
36742 offset: function(elem) {
36743 elem = this.getRawNode(elem);
36745 var elemBCR = elem.getBoundingClientRect();
36747 width: Math.round(angular.isNumber(elemBCR.width) ? elemBCR.width : elem.offsetWidth),
36748 height: Math.round(angular.isNumber(elemBCR.height) ? elemBCR.height : elem.offsetHeight),
36749 top: Math.round(elemBCR.top + ($window.pageYOffset || $document[0].documentElement.scrollTop)),
36750 left: Math.round(elemBCR.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft))
36755 * Provides offset distance to the closest scrollable ancestor
36756 * or viewport. Accounts for border and scrollbar width.
36758 * Right and bottom dimensions represent the distance to the
36759 * respective edge of the viewport element. If the element
36760 * edge extends beyond the viewport, a negative value will be
36763 * @param {element} elem - The element to get the viewport offset for.
36764 * @param {boolean=} [useDocument=false] - Should the viewport be the document element instead
36765 * of the first scrollable element, default is false.
36766 * @param {boolean=} [includePadding=true] - Should the padding on the offset parent element
36767 * be accounted for, default is true.
36769 * @returns {object} An object with the following properties:
36771 * <li>**top**: distance to the top content edge of viewport element</li>
36772 * <li>**bottom**: distance to the bottom content edge of viewport element</li>
36773 * <li>**left**: distance to the left content edge of viewport element</li>
36774 * <li>**right**: distance to the right content edge of viewport element</li>
36777 viewportOffset: function(elem, useDocument, includePadding) {
36778 elem = this.getRawNode(elem);
36779 includePadding = includePadding !== false ? true : false;
36781 var elemBCR = elem.getBoundingClientRect();
36782 var offsetBCR = {top: 0, left: 0, bottom: 0, right: 0};
36784 var offsetParent = useDocument ? $document[0].documentElement : this.scrollParent(elem);
36785 var offsetParentBCR = offsetParent.getBoundingClientRect();
36787 offsetBCR.top = offsetParentBCR.top + offsetParent.clientTop;
36788 offsetBCR.left = offsetParentBCR.left + offsetParent.clientLeft;
36789 if (offsetParent === $document[0].documentElement) {
36790 offsetBCR.top += $window.pageYOffset;
36791 offsetBCR.left += $window.pageXOffset;
36793 offsetBCR.bottom = offsetBCR.top + offsetParent.clientHeight;
36794 offsetBCR.right = offsetBCR.left + offsetParent.clientWidth;
36796 if (includePadding) {
36797 var offsetParentStyle = $window.getComputedStyle(offsetParent);
36798 offsetBCR.top += this.parseStyle(offsetParentStyle.paddingTop);
36799 offsetBCR.bottom -= this.parseStyle(offsetParentStyle.paddingBottom);
36800 offsetBCR.left += this.parseStyle(offsetParentStyle.paddingLeft);
36801 offsetBCR.right -= this.parseStyle(offsetParentStyle.paddingRight);
36805 top: Math.round(elemBCR.top - offsetBCR.top),
36806 bottom: Math.round(offsetBCR.bottom - elemBCR.bottom),
36807 left: Math.round(elemBCR.left - offsetBCR.left),
36808 right: Math.round(offsetBCR.right - elemBCR.right)
36813 * Provides an array of placement values parsed from a placement string.
36814 * Along with the 'auto' indicator, supported placement strings are:
36816 * <li>top: element on top, horizontally centered on host element.</li>
36817 * <li>top-left: element on top, left edge aligned with host element left edge.</li>
36818 * <li>top-right: element on top, lerightft edge aligned with host element right edge.</li>
36819 * <li>bottom: element on bottom, horizontally centered on host element.</li>
36820 * <li>bottom-left: element on bottom, left edge aligned with host element left edge.</li>
36821 * <li>bottom-right: element on bottom, right edge aligned with host element right edge.</li>
36822 * <li>left: element on left, vertically centered on host element.</li>
36823 * <li>left-top: element on left, top edge aligned with host element top edge.</li>
36824 * <li>left-bottom: element on left, bottom edge aligned with host element bottom edge.</li>
36825 * <li>right: element on right, vertically centered on host element.</li>
36826 * <li>right-top: element on right, top edge aligned with host element top edge.</li>
36827 * <li>right-bottom: element on right, bottom edge aligned with host element bottom edge.</li>
36829 * A placement string with an 'auto' indicator is expected to be
36830 * space separated from the placement, i.e: 'auto bottom-left' If
36831 * the primary and secondary placement values do not match 'top,
36832 * bottom, left, right' then 'top' will be the primary placement and
36833 * 'center' will be the secondary placement. If 'auto' is passed, true
36834 * will be returned as the 3rd value of the array.
36836 * @param {string} placement - The placement string to parse.
36838 * @returns {array} An array with the following values
36840 * <li>**[0]**: The primary placement.</li>
36841 * <li>**[1]**: The secondary placement.</li>
36842 * <li>**[2]**: If auto is passed: true, else undefined.</li>
36845 parsePlacement: function(placement) {
36846 var autoPlace = PLACEMENT_REGEX.auto.test(placement);
36848 placement = placement.replace(PLACEMENT_REGEX.auto, '');
36851 placement = placement.split('-');
36853 placement[0] = placement[0] || 'top';
36854 if (!PLACEMENT_REGEX.primary.test(placement[0])) {
36855 placement[0] = 'top';
36858 placement[1] = placement[1] || 'center';
36859 if (!PLACEMENT_REGEX.secondary.test(placement[1])) {
36860 placement[1] = 'center';
36864 placement[2] = true;
36866 placement[2] = false;
36873 * Provides coordinates for an element to be positioned relative to
36874 * another element. Passing 'auto' as part of the placement parameter
36875 * will enable smart placement - where the element fits. i.e:
36876 * 'auto left-top' will check to see if there is enough space to the left
36877 * of the hostElem to fit the targetElem, if not place right (same for secondary
36878 * top placement). Available space is calculated using the viewportOffset
36881 * @param {element} hostElem - The element to position against.
36882 * @param {element} targetElem - The element to position.
36883 * @param {string=} [placement=top] - The placement for the targetElem,
36884 * default is 'top'. 'center' is assumed as secondary placement for
36885 * 'top', 'left', 'right', and 'bottom' placements. Available placements are:
36888 * <li>top-right</li>
36889 * <li>top-left</li>
36891 * <li>bottom-left</li>
36892 * <li>bottom-right</li>
36894 * <li>left-top</li>
36895 * <li>left-bottom</li>
36897 * <li>right-top</li>
36898 * <li>right-bottom</li>
36900 * @param {boolean=} [appendToBody=false] - Should the top and left values returned
36901 * be calculated from the body element, default is false.
36903 * @returns {object} An object with the following properties:
36905 * <li>**top**: Value for targetElem top.</li>
36906 * <li>**left**: Value for targetElem left.</li>
36907 * <li>**placement**: The resolved placement.</li>
36910 positionElements: function(hostElem, targetElem, placement, appendToBody) {
36911 hostElem = this.getRawNode(hostElem);
36912 targetElem = this.getRawNode(targetElem);
36914 // need to read from prop to support tests.
36915 var targetWidth = angular.isDefined(targetElem.offsetWidth) ? targetElem.offsetWidth : targetElem.prop('offsetWidth');
36916 var targetHeight = angular.isDefined(targetElem.offsetHeight) ? targetElem.offsetHeight : targetElem.prop('offsetHeight');
36918 placement = this.parsePlacement(placement);
36920 var hostElemPos = appendToBody ? this.offset(hostElem) : this.position(hostElem);
36921 var targetElemPos = {top: 0, left: 0, placement: ''};
36923 if (placement[2]) {
36924 var viewportOffset = this.viewportOffset(hostElem, appendToBody);
36926 var targetElemStyle = $window.getComputedStyle(targetElem);
36927 var adjustedSize = {
36928 width: targetWidth + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginLeft) + this.parseStyle(targetElemStyle.marginRight))),
36929 height: targetHeight + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginTop) + this.parseStyle(targetElemStyle.marginBottom)))
36932 placement[0] = placement[0] === 'top' && adjustedSize.height > viewportOffset.top && adjustedSize.height <= viewportOffset.bottom ? 'bottom' :
36933 placement[0] === 'bottom' && adjustedSize.height > viewportOffset.bottom && adjustedSize.height <= viewportOffset.top ? 'top' :
36934 placement[0] === 'left' && adjustedSize.width > viewportOffset.left && adjustedSize.width <= viewportOffset.right ? 'right' :
36935 placement[0] === 'right' && adjustedSize.width > viewportOffset.right && adjustedSize.width <= viewportOffset.left ? 'left' :
36938 placement[1] = placement[1] === 'top' && adjustedSize.height - hostElemPos.height > viewportOffset.bottom && adjustedSize.height - hostElemPos.height <= viewportOffset.top ? 'bottom' :
36939 placement[1] === 'bottom' && adjustedSize.height - hostElemPos.height > viewportOffset.top && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom ? 'top' :
36940 placement[1] === 'left' && adjustedSize.width - hostElemPos.width > viewportOffset.right && adjustedSize.width - hostElemPos.width <= viewportOffset.left ? 'right' :
36941 placement[1] === 'right' && adjustedSize.width - hostElemPos.width > viewportOffset.left && adjustedSize.width - hostElemPos.width <= viewportOffset.right ? 'left' :
36944 if (placement[1] === 'center') {
36945 if (PLACEMENT_REGEX.vertical.test(placement[0])) {
36946 var xOverflow = hostElemPos.width / 2 - targetWidth / 2;
36947 if (viewportOffset.left + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.right) {
36948 placement[1] = 'left';
36949 } else if (viewportOffset.right + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.left) {
36950 placement[1] = 'right';
36953 var yOverflow = hostElemPos.height / 2 - adjustedSize.height / 2;
36954 if (viewportOffset.top + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom) {
36955 placement[1] = 'top';
36956 } else if (viewportOffset.bottom + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.top) {
36957 placement[1] = 'bottom';
36963 switch (placement[0]) {
36965 targetElemPos.top = hostElemPos.top - targetHeight;
36968 targetElemPos.top = hostElemPos.top + hostElemPos.height;
36971 targetElemPos.left = hostElemPos.left - targetWidth;
36974 targetElemPos.left = hostElemPos.left + hostElemPos.width;
36978 switch (placement[1]) {
36980 targetElemPos.top = hostElemPos.top;
36983 targetElemPos.top = hostElemPos.top + hostElemPos.height - targetHeight;
36986 targetElemPos.left = hostElemPos.left;
36989 targetElemPos.left = hostElemPos.left + hostElemPos.width - targetWidth;
36992 if (PLACEMENT_REGEX.vertical.test(placement[0])) {
36993 targetElemPos.left = hostElemPos.left + hostElemPos.width / 2 - targetWidth / 2;
36995 targetElemPos.top = hostElemPos.top + hostElemPos.height / 2 - targetHeight / 2;
37000 targetElemPos.top = Math.round(targetElemPos.top);
37001 targetElemPos.left = Math.round(targetElemPos.left);
37002 targetElemPos.placement = placement[1] === 'center' ? placement[0] : placement[0] + '-' + placement[1];
37004 return targetElemPos;
37008 * Provides a way for positioning tooltip & dropdown
37009 * arrows when using placement options beyond the standard
37010 * left, right, top, or bottom.
37012 * @param {element} elem - The tooltip/dropdown element.
37013 * @param {string} placement - The placement for the elem.
37015 positionArrow: function(elem, placement) {
37016 elem = this.getRawNode(elem);
37018 var innerElem = elem.querySelector('.tooltip-inner, .popover-inner');
37023 var isTooltip = angular.element(innerElem).hasClass('tooltip-inner');
37025 var arrowElem = isTooltip ? elem.querySelector('.tooltip-arrow') : elem.querySelector('.arrow');
37037 placement = this.parsePlacement(placement);
37038 if (placement[1] === 'center') {
37039 // no adjustment necessary - just reset styles
37040 angular.element(arrowElem).css(arrowCss);
37044 var borderProp = 'border-' + placement[0] + '-width';
37045 var borderWidth = $window.getComputedStyle(arrowElem)[borderProp];
37047 var borderRadiusProp = 'border-';
37048 if (PLACEMENT_REGEX.vertical.test(placement[0])) {
37049 borderRadiusProp += placement[0] + '-' + placement[1];
37051 borderRadiusProp += placement[1] + '-' + placement[0];
37053 borderRadiusProp += '-radius';
37054 var borderRadius = $window.getComputedStyle(isTooltip ? innerElem : elem)[borderRadiusProp];
37056 switch (placement[0]) {
37058 arrowCss.bottom = isTooltip ? '0' : '-' + borderWidth;
37061 arrowCss.top = isTooltip ? '0' : '-' + borderWidth;
37064 arrowCss.right = isTooltip ? '0' : '-' + borderWidth;
37067 arrowCss.left = isTooltip ? '0' : '-' + borderWidth;
37071 arrowCss[placement[1]] = borderRadius;
37073 angular.element(arrowElem).css(arrowCss);
37078 angular.module('ui.bootstrap.datepickerPopup', ['ui.bootstrap.datepicker', 'ui.bootstrap.position'])
37080 .value('$datepickerPopupLiteralWarning', true)
37082 .constant('uibDatepickerPopupConfig', {
37083 altInputFormats: [],
37084 appendToBody: false,
37085 clearText: 'Clear',
37086 closeOnDateSelection: true,
37088 currentText: 'Today',
37089 datepickerPopup: 'yyyy-MM-dd',
37090 datepickerPopupTemplateUrl: 'uib/template/datepickerPopup/popup.html',
37091 datepickerTemplateUrl: 'uib/template/datepicker/datepicker.html',
37093 date: 'yyyy-MM-dd',
37094 'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',
37098 showButtonBar: true,
37099 placement: 'auto bottom-left'
37102 .controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$log', '$parse', '$window', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig', '$datepickerPopupLiteralWarning',
37103 function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig, $datepickerPopupLiteralWarning) {
37105 isHtml5DateInput = false;
37106 var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
37107 datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, scrollParentEl,
37108 ngModel, ngModelOptions, $popup, altInputFormats, watchListeners = [],
37111 this.init = function(_ngModel_) {
37112 ngModel = _ngModel_;
37113 ngModelOptions = _ngModel_.$options;
37114 closeOnDateSelection = angular.isDefined($attrs.closeOnDateSelection) ?
37115 $scope.$parent.$eval($attrs.closeOnDateSelection) :
37116 datepickerPopupConfig.closeOnDateSelection;
37117 appendToBody = angular.isDefined($attrs.datepickerAppendToBody) ?
37118 $scope.$parent.$eval($attrs.datepickerAppendToBody) :
37119 datepickerPopupConfig.appendToBody;
37120 onOpenFocus = angular.isDefined($attrs.onOpenFocus) ?
37121 $scope.$parent.$eval($attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus;
37122 datepickerPopupTemplateUrl = angular.isDefined($attrs.datepickerPopupTemplateUrl) ?
37123 $attrs.datepickerPopupTemplateUrl :
37124 datepickerPopupConfig.datepickerPopupTemplateUrl;
37125 datepickerTemplateUrl = angular.isDefined($attrs.datepickerTemplateUrl) ?
37126 $attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl;
37127 altInputFormats = angular.isDefined($attrs.altInputFormats) ?
37128 $scope.$parent.$eval($attrs.altInputFormats) :
37129 datepickerPopupConfig.altInputFormats;
37131 $scope.showButtonBar = angular.isDefined($attrs.showButtonBar) ?
37132 $scope.$parent.$eval($attrs.showButtonBar) :
37133 datepickerPopupConfig.showButtonBar;
37135 if (datepickerPopupConfig.html5Types[$attrs.type]) {
37136 dateFormat = datepickerPopupConfig.html5Types[$attrs.type];
37137 isHtml5DateInput = true;
37139 dateFormat = $attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup;
37140 $attrs.$observe('uibDatepickerPopup', function(value, oldValue) {
37141 var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
37142 // Invalidate the $modelValue to ensure that formatters re-run
37143 // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
37144 if (newDateFormat !== dateFormat) {
37145 dateFormat = newDateFormat;
37146 ngModel.$modelValue = null;
37149 throw new Error('uibDatepickerPopup must have a date format specified.');
37156 throw new Error('uibDatepickerPopup must have a date format specified.');
37159 if (isHtml5DateInput && $attrs.uibDatepickerPopup) {
37160 throw new Error('HTML5 date input types do not support custom formats.');
37163 // popup element used to display calendar
37164 popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>');
37165 if (ngModelOptions) {
37166 timezone = ngModelOptions.timezone;
37167 $scope.ngModelOptions = angular.copy(ngModelOptions);
37168 $scope.ngModelOptions.timezone = null;
37169 if ($scope.ngModelOptions.updateOnDefault === true) {
37170 $scope.ngModelOptions.updateOn = $scope.ngModelOptions.updateOn ?
37171 $scope.ngModelOptions.updateOn + ' default' : 'default';
37174 popupEl.attr('ng-model-options', 'ngModelOptions');
37180 'ng-model': 'date',
37181 'ng-change': 'dateSelection(date)',
37182 'template-url': datepickerPopupTemplateUrl
37185 // datepicker element
37186 datepickerEl = angular.element(popupEl.children()[0]);
37187 datepickerEl.attr('template-url', datepickerTemplateUrl);
37189 if (!$scope.datepickerOptions) {
37190 $scope.datepickerOptions = {};
37193 if (isHtml5DateInput) {
37194 if ($attrs.type === 'month') {
37195 $scope.datepickerOptions.datepickerMode = 'month';
37196 $scope.datepickerOptions.minMode = 'month';
37200 datepickerEl.attr('datepicker-options', 'datepickerOptions');
37202 if (!isHtml5DateInput) {
37203 // Internal API to maintain the correct ng-invalid-[key] class
37204 ngModel.$$parserName = 'date';
37205 ngModel.$validators.date = validator;
37206 ngModel.$parsers.unshift(parseDate);
37207 ngModel.$formatters.push(function(value) {
37208 if (ngModel.$isEmpty(value)) {
37209 $scope.date = value;
37213 if (angular.isNumber(value)) {
37214 value = new Date(value);
37217 $scope.date = dateParser.fromTimezone(value, timezone);
37219 return dateParser.filter($scope.date, dateFormat);
37222 ngModel.$formatters.push(function(value) {
37223 $scope.date = dateParser.fromTimezone(value, timezone);
37228 // Detect changes in the view from the text box
37229 ngModel.$viewChangeListeners.push(function() {
37230 $scope.date = parseDateString(ngModel.$viewValue);
37233 $element.on('keydown', inputKeydownBind);
37235 $popup = $compile(popupEl)($scope);
37236 // Prevent jQuery cache memory leak (template is now redundant after linking)
37239 if (appendToBody) {
37240 $document.find('body').append($popup);
37242 $element.after($popup);
37245 $scope.$on('$destroy', function() {
37246 if ($scope.isOpen === true) {
37247 if (!$rootScope.$$phase) {
37248 $scope.$apply(function() {
37249 $scope.isOpen = false;
37255 $element.off('keydown', inputKeydownBind);
37256 $document.off('click', documentClickBind);
37257 if (scrollParentEl) {
37258 scrollParentEl.off('scroll', positionPopup);
37260 angular.element($window).off('resize', positionPopup);
37262 //Clear all watch listeners on destroy
37263 while (watchListeners.length) {
37264 watchListeners.shift()();
37269 $scope.getText = function(key) {
37270 return $scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
37273 $scope.isDisabled = function(date) {
37274 if (date === 'today') {
37275 date = dateParser.fromTimezone(new Date(), timezone);
37279 angular.forEach(['minDate', 'maxDate'], function(key) {
37280 if (!$scope.datepickerOptions[key]) {
37282 } else if (angular.isDate($scope.datepickerOptions[key])) {
37283 dates[key] = dateParser.fromTimezone(new Date($scope.datepickerOptions[key]), timezone);
37285 if ($datepickerPopupLiteralWarning) {
37286 $log.warn('Literal date support has been deprecated, please switch to date object usage');
37289 dates[key] = new Date(dateFilter($scope.datepickerOptions[key], 'medium'));
37293 return $scope.datepickerOptions &&
37294 dates.minDate && $scope.compare(date, dates.minDate) < 0 ||
37295 dates.maxDate && $scope.compare(date, dates.maxDate) > 0;
37298 $scope.compare = function(date1, date2) {
37299 return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
37303 $scope.dateSelection = function(dt) {
37304 if (angular.isDefined(dt)) {
37307 var date = $scope.date ? dateParser.filter($scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function
37308 $element.val(date);
37309 ngModel.$setViewValue(date);
37311 if (closeOnDateSelection) {
37312 $scope.isOpen = false;
37313 $element[0].focus();
37317 $scope.keydown = function(evt) {
37318 if (evt.which === 27) {
37319 evt.stopPropagation();
37320 $scope.isOpen = false;
37321 $element[0].focus();
37325 $scope.select = function(date, evt) {
37326 evt.stopPropagation();
37328 if (date === 'today') {
37329 var today = new Date();
37330 if (angular.isDate($scope.date)) {
37331 date = new Date($scope.date);
37332 date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
37334 date = new Date(today.setHours(0, 0, 0, 0));
37337 $scope.dateSelection(date);
37340 $scope.close = function(evt) {
37341 evt.stopPropagation();
37343 $scope.isOpen = false;
37344 $element[0].focus();
37347 $scope.disabled = angular.isDefined($attrs.disabled) || false;
37348 if ($attrs.ngDisabled) {
37349 watchListeners.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(disabled) {
37350 $scope.disabled = disabled;
37354 $scope.$watch('isOpen', function(value) {
37356 if (!$scope.disabled) {
37357 $timeout(function() {
37361 $scope.$broadcast('uib:datepicker.focus');
37364 $document.on('click', documentClickBind);
37366 var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;
37367 if (appendToBody || $position.parsePlacement(placement)[2]) {
37368 scrollParentEl = scrollParentEl || angular.element($position.scrollParent($element));
37369 if (scrollParentEl) {
37370 scrollParentEl.on('scroll', positionPopup);
37373 scrollParentEl = null;
37376 angular.element($window).on('resize', positionPopup);
37379 $scope.isOpen = false;
37382 $document.off('click', documentClickBind);
37383 if (scrollParentEl) {
37384 scrollParentEl.off('scroll', positionPopup);
37386 angular.element($window).off('resize', positionPopup);
37390 function cameltoDash(string) {
37391 return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
37394 function parseDateString(viewValue) {
37395 var date = dateParser.parse(viewValue, dateFormat, $scope.date);
37397 for (var i = 0; i < altInputFormats.length; i++) {
37398 date = dateParser.parse(viewValue, altInputFormats[i], $scope.date);
37399 if (!isNaN(date)) {
37407 function parseDate(viewValue) {
37408 if (angular.isNumber(viewValue)) {
37409 // presumably timestamp to date object
37410 viewValue = new Date(viewValue);
37417 if (angular.isDate(viewValue) && !isNaN(viewValue)) {
37421 if (angular.isString(viewValue)) {
37422 var date = parseDateString(viewValue);
37423 if (!isNaN(date)) {
37424 return dateParser.toTimezone(date, timezone);
37428 return ngModel.$options && ngModel.$options.allowInvalid ? viewValue : undefined;
37431 function validator(modelValue, viewValue) {
37432 var value = modelValue || viewValue;
37434 if (!$attrs.ngRequired && !value) {
37438 if (angular.isNumber(value)) {
37439 value = new Date(value);
37446 if (angular.isDate(value) && !isNaN(value)) {
37450 if (angular.isString(value)) {
37451 return !isNaN(parseDateString(viewValue));
37457 function documentClickBind(event) {
37458 if (!$scope.isOpen && $scope.disabled) {
37462 var popup = $popup[0];
37463 var dpContainsTarget = $element[0].contains(event.target);
37464 // The popup node may not be an element node
37465 // In some browsers (IE) only element nodes have the 'contains' function
37466 var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target);
37467 if ($scope.isOpen && !(dpContainsTarget || popupContainsTarget)) {
37468 $scope.$apply(function() {
37469 $scope.isOpen = false;
37474 function inputKeydownBind(evt) {
37475 if (evt.which === 27 && $scope.isOpen) {
37476 evt.preventDefault();
37477 evt.stopPropagation();
37478 $scope.$apply(function() {
37479 $scope.isOpen = false;
37481 $element[0].focus();
37482 } else if (evt.which === 40 && !$scope.isOpen) {
37483 evt.preventDefault();
37484 evt.stopPropagation();
37485 $scope.$apply(function() {
37486 $scope.isOpen = true;
37491 function positionPopup() {
37492 if ($scope.isOpen) {
37493 var dpElement = angular.element($popup[0].querySelector('.uib-datepicker-popup'));
37494 var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;
37495 var position = $position.positionElements($element, dpElement, placement, appendToBody);
37496 dpElement.css({top: position.top + 'px', left: position.left + 'px'});
37497 if (dpElement.hasClass('uib-position-measure')) {
37498 dpElement.removeClass('uib-position-measure');
37503 $scope.$on('uib:datepicker.mode', function() {
37504 $timeout(positionPopup, 0, false);
37508 .directive('uibDatepickerPopup', function() {
37510 require: ['ngModel', 'uibDatepickerPopup'],
37511 controller: 'UibDatepickerPopupController',
37513 datepickerOptions: '=?',
37519 link: function(scope, element, attrs, ctrls) {
37520 var ngModel = ctrls[0],
37523 ctrl.init(ngModel);
37528 .directive('uibDatepickerPopupWrap', function() {
37532 templateUrl: function(element, attrs) {
37533 return attrs.templateUrl || 'uib/template/datepickerPopup/popup.html';
37538 angular.module('ui.bootstrap.debounce', [])
37540 * A helper, internal service that debounces a function
37542 .factory('$$debounce', ['$timeout', function($timeout) {
37543 return function(callback, debounceTime) {
37544 var timeoutPromise;
37546 return function() {
37548 var args = Array.prototype.slice.call(arguments);
37549 if (timeoutPromise) {
37550 $timeout.cancel(timeoutPromise);
37553 timeoutPromise = $timeout(function() {
37554 callback.apply(self, args);
37560 angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
37562 .constant('uibDropdownConfig', {
37563 appendToOpenClass: 'uib-dropdown-open',
37567 .service('uibDropdownService', ['$document', '$rootScope', function($document, $rootScope) {
37568 var openScope = null;
37570 this.open = function(dropdownScope, element) {
37572 $document.on('click', closeDropdown);
37573 element.on('keydown', keybindFilter);
37576 if (openScope && openScope !== dropdownScope) {
37577 openScope.isOpen = false;
37580 openScope = dropdownScope;
37583 this.close = function(dropdownScope, element) {
37584 if (openScope === dropdownScope) {
37586 $document.off('click', closeDropdown);
37587 element.off('keydown', keybindFilter);
37591 var closeDropdown = function(evt) {
37592 // This method may still be called during the same mouse event that
37593 // unbound this event handler. So check openScope before proceeding.
37594 if (!openScope) { return; }
37596 if (evt && openScope.getAutoClose() === 'disabled') { return; }
37598 if (evt && evt.which === 3) { return; }
37600 var toggleElement = openScope.getToggleElement();
37601 if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
37605 var dropdownElement = openScope.getDropdownElement();
37606 if (evt && openScope.getAutoClose() === 'outsideClick' &&
37607 dropdownElement && dropdownElement[0].contains(evt.target)) {
37611 openScope.isOpen = false;
37613 if (!$rootScope.$$phase) {
37614 openScope.$apply();
37618 var keybindFilter = function(evt) {
37619 if (evt.which === 27) {
37620 evt.stopPropagation();
37621 openScope.focusToggleElement();
37623 } else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen) {
37624 evt.preventDefault();
37625 evt.stopPropagation();
37626 openScope.focusDropdownEntry(evt.which);
37631 .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) {
37633 scope = $scope.$new(), // create a child scope so we are not polluting original one
37635 appendToOpenClass = dropdownConfig.appendToOpenClass,
37636 openClass = dropdownConfig.openClass,
37638 setIsOpen = angular.noop,
37639 toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
37640 appendToBody = false,
37642 keynavEnabled = false,
37643 selectedOption = null,
37644 body = $document.find('body');
37646 $element.addClass('dropdown');
37648 this.init = function() {
37649 if ($attrs.isOpen) {
37650 getIsOpen = $parse($attrs.isOpen);
37651 setIsOpen = getIsOpen.assign;
37653 $scope.$watch(getIsOpen, function(value) {
37654 scope.isOpen = !!value;
37658 if (angular.isDefined($attrs.dropdownAppendTo)) {
37659 var appendToEl = $parse($attrs.dropdownAppendTo)(scope);
37661 appendTo = angular.element(appendToEl);
37665 appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
37666 keynavEnabled = angular.isDefined($attrs.keyboardNav);
37668 if (appendToBody && !appendTo) {
37672 if (appendTo && self.dropdownMenu) {
37673 appendTo.append(self.dropdownMenu);
37674 $element.on('$destroy', function handleDestroyEvent() {
37675 self.dropdownMenu.remove();
37680 this.toggle = function(open) {
37681 scope.isOpen = arguments.length ? !!open : !scope.isOpen;
37682 if (angular.isFunction(setIsOpen)) {
37683 setIsOpen(scope, scope.isOpen);
37686 return scope.isOpen;
37689 // Allow other directives to watch status
37690 this.isOpen = function() {
37691 return scope.isOpen;
37694 scope.getToggleElement = function() {
37695 return self.toggleElement;
37698 scope.getAutoClose = function() {
37699 return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled'
37702 scope.getElement = function() {
37706 scope.isKeynavEnabled = function() {
37707 return keynavEnabled;
37710 scope.focusDropdownEntry = function(keyCode) {
37711 var elems = self.dropdownMenu ? //If append to body is used.
37712 angular.element(self.dropdownMenu).find('a') :
37713 $element.find('ul').eq(0).find('a');
37717 if (!angular.isNumber(self.selectedOption)) {
37718 self.selectedOption = 0;
37720 self.selectedOption = self.selectedOption === elems.length - 1 ?
37721 self.selectedOption :
37722 self.selectedOption + 1;
37727 if (!angular.isNumber(self.selectedOption)) {
37728 self.selectedOption = elems.length - 1;
37730 self.selectedOption = self.selectedOption === 0 ?
37731 0 : self.selectedOption - 1;
37736 elems[self.selectedOption].focus();
37739 scope.getDropdownElement = function() {
37740 return self.dropdownMenu;
37743 scope.focusToggleElement = function() {
37744 if (self.toggleElement) {
37745 self.toggleElement[0].focus();
37749 scope.$watch('isOpen', function(isOpen, wasOpen) {
37750 if (appendTo && self.dropdownMenu) {
37751 var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true),
37757 top: pos.top + 'px',
37758 display: isOpen ? 'block' : 'none'
37761 rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
37763 css.left = pos.left + 'px';
37764 css.right = 'auto';
37767 scrollbarWidth = $position.scrollbarWidth(true);
37768 css.right = window.innerWidth - scrollbarWidth -
37769 (pos.left + $element.prop('offsetWidth')) + 'px';
37772 // Need to adjust our positioning to be relative to the appendTo container
37773 // if it's not the body element
37774 if (!appendToBody) {
37775 var appendOffset = $position.offset(appendTo);
37777 css.top = pos.top - appendOffset.top + 'px';
37780 css.left = pos.left - appendOffset.left + 'px';
37782 css.right = window.innerWidth -
37783 (pos.left - appendOffset.left + $element.prop('offsetWidth')) + 'px';
37787 self.dropdownMenu.css(css);
37790 var openContainer = appendTo ? appendTo : $element;
37791 var hasOpenClass = openContainer.hasClass(appendTo ? appendToOpenClass : openClass);
37793 if (hasOpenClass === !isOpen) {
37794 $animate[isOpen ? 'addClass' : 'removeClass'](openContainer, appendTo ? appendToOpenClass : openClass).then(function() {
37795 if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
37796 toggleInvoker($scope, { open: !!isOpen });
37802 if (self.dropdownMenuTemplateUrl) {
37803 $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) {
37804 templateScope = scope.$new();
37805 $compile(tplContent.trim())(templateScope, function(dropdownElement) {
37806 var newEl = dropdownElement;
37807 self.dropdownMenu.replaceWith(newEl);
37808 self.dropdownMenu = newEl;
37813 scope.focusToggleElement();
37814 uibDropdownService.open(scope, $element);
37816 if (self.dropdownMenuTemplateUrl) {
37817 if (templateScope) {
37818 templateScope.$destroy();
37820 var newEl = angular.element('<ul class="dropdown-menu"></ul>');
37821 self.dropdownMenu.replaceWith(newEl);
37822 self.dropdownMenu = newEl;
37825 uibDropdownService.close(scope, $element);
37826 self.selectedOption = null;
37829 if (angular.isFunction(setIsOpen)) {
37830 setIsOpen($scope, isOpen);
37835 .directive('uibDropdown', function() {
37837 controller: 'UibDropdownController',
37838 link: function(scope, element, attrs, dropdownCtrl) {
37839 dropdownCtrl.init();
37844 .directive('uibDropdownMenu', function() {
37847 require: '?^uibDropdown',
37848 link: function(scope, element, attrs, dropdownCtrl) {
37849 if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {
37853 element.addClass('dropdown-menu');
37855 var tplUrl = attrs.templateUrl;
37857 dropdownCtrl.dropdownMenuTemplateUrl = tplUrl;
37860 if (!dropdownCtrl.dropdownMenu) {
37861 dropdownCtrl.dropdownMenu = element;
37867 .directive('uibDropdownToggle', function() {
37869 require: '?^uibDropdown',
37870 link: function(scope, element, attrs, dropdownCtrl) {
37871 if (!dropdownCtrl) {
37875 element.addClass('dropdown-toggle');
37877 dropdownCtrl.toggleElement = element;
37879 var toggleDropdown = function(event) {
37880 event.preventDefault();
37882 if (!element.hasClass('disabled') && !attrs.disabled) {
37883 scope.$apply(function() {
37884 dropdownCtrl.toggle();
37889 element.bind('click', toggleDropdown);
37892 element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
37893 scope.$watch(dropdownCtrl.isOpen, function(isOpen) {
37894 element.attr('aria-expanded', !!isOpen);
37897 scope.$on('$destroy', function() {
37898 element.unbind('click', toggleDropdown);
37904 angular.module('ui.bootstrap.stackedMap', [])
37906 * A helper, internal data structure that acts as a map but also allows getting / removing
37907 * elements in the LIFO order
37909 .factory('$$stackedMap', function() {
37911 createNew: function() {
37915 add: function(key, value) {
37921 get: function(key) {
37922 for (var i = 0; i < stack.length; i++) {
37923 if (key === stack[i].key) {
37930 for (var i = 0; i < stack.length; i++) {
37931 keys.push(stack[i].key);
37936 return stack[stack.length - 1];
37938 remove: function(key) {
37940 for (var i = 0; i < stack.length; i++) {
37941 if (key === stack[i].key) {
37946 return stack.splice(idx, 1)[0];
37948 removeTop: function() {
37949 return stack.splice(stack.length - 1, 1)[0];
37951 length: function() {
37952 return stack.length;
37958 angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.position'])
37960 * A helper, internal data structure that stores all references attached to key
37962 .factory('$$multiMap', function() {
37964 createNew: function() {
37968 entries: function() {
37969 return Object.keys(map).map(function(key) {
37976 get: function(key) {
37979 hasKey: function(key) {
37983 return Object.keys(map);
37985 put: function(key, value) {
37990 map[key].push(value);
37992 remove: function(key, value) {
37993 var values = map[key];
37999 var idx = values.indexOf(value);
38002 values.splice(idx, 1);
38005 if (!values.length) {
38015 * Pluggable resolve mechanism for the modal resolve resolution
38016 * Supports UI Router's $resolve service
38018 .provider('$uibResolve', function() {
38019 var resolve = this;
38020 this.resolver = null;
38022 this.setResolver = function(resolver) {
38023 this.resolver = resolver;
38026 this.$get = ['$injector', '$q', function($injector, $q) {
38027 var resolver = resolve.resolver ? $injector.get(resolve.resolver) : null;
38029 resolve: function(invocables, locals, parent, self) {
38031 return resolver.resolve(invocables, locals, parent, self);
38036 angular.forEach(invocables, function(value) {
38037 if (angular.isFunction(value) || angular.isArray(value)) {
38038 promises.push($q.resolve($injector.invoke(value)));
38039 } else if (angular.isString(value)) {
38040 promises.push($q.resolve($injector.get(value)));
38042 promises.push($q.resolve(value));
38046 return $q.all(promises).then(function(resolves) {
38047 var resolveObj = {};
38048 var resolveIter = 0;
38049 angular.forEach(invocables, function(value, key) {
38050 resolveObj[key] = resolves[resolveIter++];
38061 * A helper directive for the $modal service. It creates a backdrop element.
38063 .directive('uibModalBackdrop', ['$animate', '$injector', '$uibModalStack',
38064 function($animate, $injector, $modalStack) {
38067 templateUrl: 'uib/template/modal/backdrop.html',
38068 compile: function(tElement, tAttrs) {
38069 tElement.addClass(tAttrs.backdropClass);
38074 function linkFn(scope, element, attrs) {
38075 if (attrs.modalInClass) {
38076 $animate.addClass(element, attrs.modalInClass);
38078 scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
38079 var done = setIsAsync();
38080 if (scope.modalOptions.animation) {
38081 $animate.removeClass(element, attrs.modalInClass).then(done);
38090 .directive('uibModalWindow', ['$uibModalStack', '$q', '$animateCss', '$document',
38091 function($modalStack, $q, $animateCss, $document) {
38098 templateUrl: function(tElement, tAttrs) {
38099 return tAttrs.templateUrl || 'uib/template/modal/window.html';
38101 link: function(scope, element, attrs) {
38102 element.addClass(attrs.windowClass || '');
38103 element.addClass(attrs.windowTopClass || '');
38104 scope.size = attrs.size;
38106 scope.close = function(evt) {
38107 var modal = $modalStack.getTop();
38108 if (modal && modal.value.backdrop &&
38109 modal.value.backdrop !== 'static' &&
38110 evt.target === evt.currentTarget) {
38111 evt.preventDefault();
38112 evt.stopPropagation();
38113 $modalStack.dismiss(modal.key, 'backdrop click');
38117 // moved from template to fix issue #2280
38118 element.on('click', scope.close);
38120 // This property is only added to the scope for the purpose of detecting when this directive is rendered.
38121 // We can detect that by using this property in the template associated with this directive and then use
38122 // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
38123 scope.$isRendered = true;
38125 // Deferred object that will be resolved when this modal is render.
38126 var modalRenderDeferObj = $q.defer();
38127 // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready.
38128 // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template.
38129 attrs.$observe('modalRender', function(value) {
38130 if (value === 'true') {
38131 modalRenderDeferObj.resolve();
38135 modalRenderDeferObj.promise.then(function() {
38136 var animationPromise = null;
38138 if (attrs.modalInClass) {
38139 animationPromise = $animateCss(element, {
38140 addClass: attrs.modalInClass
38143 scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
38144 var done = setIsAsync();
38145 $animateCss(element, {
38146 removeClass: attrs.modalInClass
38147 }).start().then(done);
38152 $q.when(animationPromise).then(function() {
38153 // Notify {@link $modalStack} that modal is rendered.
38154 var modal = $modalStack.getTop();
38156 $modalStack.modalRendered(modal.key);
38160 * If something within the freshly-opened modal already has focus (perhaps via a
38161 * directive that causes focus). then no need to try and focus anything.
38163 if (!($document[0].activeElement && element[0].contains($document[0].activeElement))) {
38164 var inputWithAutofocus = element[0].querySelector('[autofocus]');
38166 * Auto-focusing of a freshly-opened modal element causes any child elements
38167 * with the autofocus attribute to lose focus. This is an issue on touch
38168 * based devices which will show and then hide the onscreen keyboard.
38169 * Attempts to refocus the autofocus element via JavaScript will not reopen
38170 * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
38171 * the modal element if the modal does not contain an autofocus element.
38173 if (inputWithAutofocus) {
38174 inputWithAutofocus.focus();
38176 element[0].focus();
38185 .directive('uibModalAnimationClass', function() {
38187 compile: function(tElement, tAttrs) {
38188 if (tAttrs.modalAnimation) {
38189 tElement.addClass(tAttrs.uibModalAnimationClass);
38195 .directive('uibModalTransclude', function() {
38197 link: function(scope, element, attrs, controller, transclude) {
38198 transclude(scope.$parent, function(clone) {
38200 element.append(clone);
38206 .factory('$uibModalStack', ['$animate', '$animateCss', '$document',
38207 '$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap', '$uibPosition',
38208 function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap, $uibPosition) {
38209 var OPENED_MODAL_CLASS = 'modal-open';
38211 var backdropDomEl, backdropScope;
38212 var openedWindows = $$stackedMap.createNew();
38213 var openedClasses = $$multiMap.createNew();
38214 var $modalStack = {
38215 NOW_CLOSING_EVENT: 'modal.stack.now-closing'
38217 var topModalIndex = 0;
38218 var previousTopOpenedModal = null;
38220 //Modal focus behavior
38221 var tabableSelector = 'a[href], area[href], input:not([disabled]), ' +
38222 'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' +
38223 'iframe, object, embed, *[tabindex], *[contenteditable=true]';
38224 var scrollbarPadding;
38226 function isVisible(element) {
38227 return !!(element.offsetWidth ||
38228 element.offsetHeight ||
38229 element.getClientRects().length);
38232 function backdropIndex() {
38233 var topBackdropIndex = -1;
38234 var opened = openedWindows.keys();
38235 for (var i = 0; i < opened.length; i++) {
38236 if (openedWindows.get(opened[i]).value.backdrop) {
38237 topBackdropIndex = i;
38241 // If any backdrop exist, ensure that it's index is always
38242 // right below the top modal
38243 if (topBackdropIndex > -1 && topBackdropIndex < topModalIndex) {
38244 topBackdropIndex = topModalIndex;
38246 return topBackdropIndex;
38249 $rootScope.$watch(backdropIndex, function(newBackdropIndex) {
38250 if (backdropScope) {
38251 backdropScope.index = newBackdropIndex;
38255 function removeModalWindow(modalInstance, elementToReceiveFocus) {
38256 var modalWindow = openedWindows.get(modalInstance).value;
38257 var appendToElement = modalWindow.appendTo;
38259 //clean up the stack
38260 openedWindows.remove(modalInstance);
38261 previousTopOpenedModal = openedWindows.top();
38262 if (previousTopOpenedModal) {
38263 topModalIndex = parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10);
38266 removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() {
38267 var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS;
38268 openedClasses.remove(modalBodyClass, modalInstance);
38269 var areAnyOpen = openedClasses.hasKey(modalBodyClass);
38270 appendToElement.toggleClass(modalBodyClass, areAnyOpen);
38271 if (!areAnyOpen && scrollbarPadding && scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
38272 if (scrollbarPadding.originalRight) {
38273 appendToElement.css({paddingRight: scrollbarPadding.originalRight + 'px'});
38275 appendToElement.css({paddingRight: ''});
38277 scrollbarPadding = null;
38279 toggleTopWindowClass(true);
38280 }, modalWindow.closedDeferred);
38281 checkRemoveBackdrop();
38283 //move focus to specified element if available, or else to body
38284 if (elementToReceiveFocus && elementToReceiveFocus.focus) {
38285 elementToReceiveFocus.focus();
38286 } else if (appendToElement.focus) {
38287 appendToElement.focus();
38291 // Add or remove "windowTopClass" from the top window in the stack
38292 function toggleTopWindowClass(toggleSwitch) {
38295 if (openedWindows.length() > 0) {
38296 modalWindow = openedWindows.top().value;
38297 modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch);
38301 function checkRemoveBackdrop() {
38302 //remove backdrop if no longer needed
38303 if (backdropDomEl && backdropIndex() === -1) {
38304 var backdropScopeRef = backdropScope;
38305 removeAfterAnimate(backdropDomEl, backdropScope, function() {
38306 backdropScopeRef = null;
38308 backdropDomEl = undefined;
38309 backdropScope = undefined;
38313 function removeAfterAnimate(domEl, scope, done, closedDeferred) {
38315 var asyncPromise = null;
38316 var setIsAsync = function() {
38317 if (!asyncDeferred) {
38318 asyncDeferred = $q.defer();
38319 asyncPromise = asyncDeferred.promise;
38322 return function asyncDone() {
38323 asyncDeferred.resolve();
38326 scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync);
38328 // Note that it's intentional that asyncPromise might be null.
38329 // That's when setIsAsync has not been called during the
38330 // NOW_CLOSING_EVENT broadcast.
38331 return $q.when(asyncPromise).then(afterAnimating);
38333 function afterAnimating() {
38334 if (afterAnimating.done) {
38337 afterAnimating.done = true;
38339 $animate.leave(domEl).then(function() {
38341 if (closedDeferred) {
38342 closedDeferred.resolve();
38353 $document.on('keydown', keydownListener);
38355 $rootScope.$on('$destroy', function() {
38356 $document.off('keydown', keydownListener);
38359 function keydownListener(evt) {
38360 if (evt.isDefaultPrevented()) {
38364 var modal = openedWindows.top();
38366 switch (evt.which) {
38368 if (modal.value.keyboard) {
38369 evt.preventDefault();
38370 $rootScope.$apply(function() {
38371 $modalStack.dismiss(modal.key, 'escape key press');
38377 var list = $modalStack.loadFocusElementList(modal);
38378 var focusChanged = false;
38379 if (evt.shiftKey) {
38380 if ($modalStack.isFocusInFirstItem(evt, list) || $modalStack.isModalFocused(evt, modal)) {
38381 focusChanged = $modalStack.focusLastFocusableElement(list);
38384 if ($modalStack.isFocusInLastItem(evt, list)) {
38385 focusChanged = $modalStack.focusFirstFocusableElement(list);
38389 if (focusChanged) {
38390 evt.preventDefault();
38391 evt.stopPropagation();
38400 $modalStack.open = function(modalInstance, modal) {
38401 var modalOpener = $document[0].activeElement,
38402 modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS;
38404 toggleTopWindowClass(false);
38406 // Store the current top first, to determine what index we ought to use
38407 // for the current top modal
38408 previousTopOpenedModal = openedWindows.top();
38410 openedWindows.add(modalInstance, {
38411 deferred: modal.deferred,
38412 renderDeferred: modal.renderDeferred,
38413 closedDeferred: modal.closedDeferred,
38414 modalScope: modal.scope,
38415 backdrop: modal.backdrop,
38416 keyboard: modal.keyboard,
38417 openedClass: modal.openedClass,
38418 windowTopClass: modal.windowTopClass,
38419 animation: modal.animation,
38420 appendTo: modal.appendTo
38423 openedClasses.put(modalBodyClass, modalInstance);
38425 var appendToElement = modal.appendTo,
38426 currBackdropIndex = backdropIndex();
38428 if (!appendToElement.length) {
38429 throw new Error('appendTo element not found. Make sure that the element passed is in DOM.');
38432 if (currBackdropIndex >= 0 && !backdropDomEl) {
38433 backdropScope = $rootScope.$new(true);
38434 backdropScope.modalOptions = modal;
38435 backdropScope.index = currBackdropIndex;
38436 backdropDomEl = angular.element('<div uib-modal-backdrop="modal-backdrop"></div>');
38437 backdropDomEl.attr('backdrop-class', modal.backdropClass);
38438 if (modal.animation) {
38439 backdropDomEl.attr('modal-animation', 'true');
38441 $compile(backdropDomEl)(backdropScope);
38442 $animate.enter(backdropDomEl, appendToElement);
38443 scrollbarPadding = $uibPosition.scrollbarPadding(appendToElement);
38444 if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
38445 appendToElement.css({paddingRight: scrollbarPadding.right + 'px'});
38449 // Set the top modal index based on the index of the previous top modal
38450 topModalIndex = previousTopOpenedModal ? parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10) + 1 : 0;
38451 var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>');
38452 angularDomEl.attr({
38453 'template-url': modal.windowTemplateUrl,
38454 'window-class': modal.windowClass,
38455 'window-top-class': modal.windowTopClass,
38456 'size': modal.size,
38457 'index': topModalIndex,
38458 'animate': 'animate'
38459 }).html(modal.content);
38460 if (modal.animation) {
38461 angularDomEl.attr('modal-animation', 'true');
38464 appendToElement.addClass(modalBodyClass);
38465 $animate.enter($compile(angularDomEl)(modal.scope), appendToElement);
38467 openedWindows.top().value.modalDomEl = angularDomEl;
38468 openedWindows.top().value.modalOpener = modalOpener;
38471 function broadcastClosing(modalWindow, resultOrReason, closing) {
38472 return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented;
38475 $modalStack.close = function(modalInstance, result) {
38476 var modalWindow = openedWindows.get(modalInstance);
38477 if (modalWindow && broadcastClosing(modalWindow, result, true)) {
38478 modalWindow.value.modalScope.$$uibDestructionScheduled = true;
38479 modalWindow.value.deferred.resolve(result);
38480 removeModalWindow(modalInstance, modalWindow.value.modalOpener);
38483 return !modalWindow;
38486 $modalStack.dismiss = function(modalInstance, reason) {
38487 var modalWindow = openedWindows.get(modalInstance);
38488 if (modalWindow && broadcastClosing(modalWindow, reason, false)) {
38489 modalWindow.value.modalScope.$$uibDestructionScheduled = true;
38490 modalWindow.value.deferred.reject(reason);
38491 removeModalWindow(modalInstance, modalWindow.value.modalOpener);
38494 return !modalWindow;
38497 $modalStack.dismissAll = function(reason) {
38498 var topModal = this.getTop();
38499 while (topModal && this.dismiss(topModal.key, reason)) {
38500 topModal = this.getTop();
38504 $modalStack.getTop = function() {
38505 return openedWindows.top();
38508 $modalStack.modalRendered = function(modalInstance) {
38509 var modalWindow = openedWindows.get(modalInstance);
38511 modalWindow.value.renderDeferred.resolve();
38515 $modalStack.focusFirstFocusableElement = function(list) {
38516 if (list.length > 0) {
38523 $modalStack.focusLastFocusableElement = function(list) {
38524 if (list.length > 0) {
38525 list[list.length - 1].focus();
38531 $modalStack.isModalFocused = function(evt, modalWindow) {
38532 if (evt && modalWindow) {
38533 var modalDomEl = modalWindow.value.modalDomEl;
38534 if (modalDomEl && modalDomEl.length) {
38535 return (evt.target || evt.srcElement) === modalDomEl[0];
38541 $modalStack.isFocusInFirstItem = function(evt, list) {
38542 if (list.length > 0) {
38543 return (evt.target || evt.srcElement) === list[0];
38548 $modalStack.isFocusInLastItem = function(evt, list) {
38549 if (list.length > 0) {
38550 return (evt.target || evt.srcElement) === list[list.length - 1];
38555 $modalStack.loadFocusElementList = function(modalWindow) {
38557 var modalDomE1 = modalWindow.value.modalDomEl;
38558 if (modalDomE1 && modalDomE1.length) {
38559 var elements = modalDomE1[0].querySelectorAll(tabableSelector);
38561 Array.prototype.filter.call(elements, function(element) {
38562 return isVisible(element);
38568 return $modalStack;
38571 .provider('$uibModal', function() {
38572 var $modalProvider = {
38575 backdrop: true, //can also be false or 'static'
38578 $get: ['$rootScope', '$q', '$document', '$templateRequest', '$controller', '$uibResolve', '$uibModalStack',
38579 function ($rootScope, $q, $document, $templateRequest, $controller, $uibResolve, $modalStack) {
38582 function getTemplatePromise(options) {
38583 return options.template ? $q.when(options.template) :
38584 $templateRequest(angular.isFunction(options.templateUrl) ?
38585 options.templateUrl() : options.templateUrl);
38588 var promiseChain = null;
38589 $modal.getPromiseChain = function() {
38590 return promiseChain;
38593 $modal.open = function(modalOptions) {
38594 var modalResultDeferred = $q.defer();
38595 var modalOpenedDeferred = $q.defer();
38596 var modalClosedDeferred = $q.defer();
38597 var modalRenderDeferred = $q.defer();
38599 //prepare an instance of a modal to be injected into controllers and returned to a caller
38600 var modalInstance = {
38601 result: modalResultDeferred.promise,
38602 opened: modalOpenedDeferred.promise,
38603 closed: modalClosedDeferred.promise,
38604 rendered: modalRenderDeferred.promise,
38605 close: function (result) {
38606 return $modalStack.close(modalInstance, result);
38608 dismiss: function (reason) {
38609 return $modalStack.dismiss(modalInstance, reason);
38613 //merge and clean up options
38614 modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
38615 modalOptions.resolve = modalOptions.resolve || {};
38616 modalOptions.appendTo = modalOptions.appendTo || $document.find('body').eq(0);
38619 if (!modalOptions.template && !modalOptions.templateUrl) {
38620 throw new Error('One of template or templateUrl options is required.');
38623 var templateAndResolvePromise =
38624 $q.all([getTemplatePromise(modalOptions), $uibResolve.resolve(modalOptions.resolve, {}, null, null)]);
38626 function resolveWithTemplate() {
38627 return templateAndResolvePromise;
38630 // Wait for the resolution of the existing promise chain.
38631 // Then switch to our own combined promise dependency (regardless of how the previous modal fared).
38632 // Then add to $modalStack and resolve opened.
38633 // Finally clean up the chain variable if no subsequent modal has overwritten it.
38635 samePromise = promiseChain = $q.all([promiseChain])
38636 .then(resolveWithTemplate, resolveWithTemplate)
38637 .then(function resolveSuccess(tplAndVars) {
38638 var providedScope = modalOptions.scope || $rootScope;
38640 var modalScope = providedScope.$new();
38641 modalScope.$close = modalInstance.close;
38642 modalScope.$dismiss = modalInstance.dismiss;
38644 modalScope.$on('$destroy', function() {
38645 if (!modalScope.$$uibDestructionScheduled) {
38646 modalScope.$dismiss('$uibUnscheduledDestruction');
38650 var ctrlInstance, ctrlInstantiate, ctrlLocals = {};
38653 if (modalOptions.controller) {
38654 ctrlLocals.$scope = modalScope;
38655 ctrlLocals.$scope.$resolve = {};
38656 ctrlLocals.$uibModalInstance = modalInstance;
38657 angular.forEach(tplAndVars[1], function(value, key) {
38658 ctrlLocals[key] = value;
38659 ctrlLocals.$scope.$resolve[key] = value;
38662 // the third param will make the controller instantiate later,private api
38663 // @see https://github.com/angular/angular.js/blob/master/src/ng/controller.js#L126
38664 ctrlInstantiate = $controller(modalOptions.controller, ctrlLocals, true, modalOptions.controllerAs);
38665 if (modalOptions.controllerAs && modalOptions.bindToController) {
38666 ctrlInstance = ctrlInstantiate.instance;
38667 ctrlInstance.$close = modalScope.$close;
38668 ctrlInstance.$dismiss = modalScope.$dismiss;
38669 angular.extend(ctrlInstance, {
38670 $resolve: ctrlLocals.$scope.$resolve
38674 ctrlInstance = ctrlInstantiate();
38676 if (angular.isFunction(ctrlInstance.$onInit)) {
38677 ctrlInstance.$onInit();
38681 $modalStack.open(modalInstance, {
38683 deferred: modalResultDeferred,
38684 renderDeferred: modalRenderDeferred,
38685 closedDeferred: modalClosedDeferred,
38686 content: tplAndVars[0],
38687 animation: modalOptions.animation,
38688 backdrop: modalOptions.backdrop,
38689 keyboard: modalOptions.keyboard,
38690 backdropClass: modalOptions.backdropClass,
38691 windowTopClass: modalOptions.windowTopClass,
38692 windowClass: modalOptions.windowClass,
38693 windowTemplateUrl: modalOptions.windowTemplateUrl,
38694 size: modalOptions.size,
38695 openedClass: modalOptions.openedClass,
38696 appendTo: modalOptions.appendTo
38698 modalOpenedDeferred.resolve(true);
38700 }, function resolveError(reason) {
38701 modalOpenedDeferred.reject(reason);
38702 modalResultDeferred.reject(reason);
38703 })['finally'](function() {
38704 if (promiseChain === samePromise) {
38705 promiseChain = null;
38709 return modalInstance;
38717 return $modalProvider;
38720 angular.module('ui.bootstrap.paging', [])
38722 * Helper internal service for generating common controller code between the
38723 * pager and pagination components
38725 .factory('uibPaging', ['$parse', function($parse) {
38727 create: function(ctrl, $scope, $attrs) {
38728 ctrl.setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
38729 ctrl.ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl
38730 ctrl._watchers = [];
38732 ctrl.init = function(ngModelCtrl, config) {
38733 ctrl.ngModelCtrl = ngModelCtrl;
38734 ctrl.config = config;
38736 ngModelCtrl.$render = function() {
38740 if ($attrs.itemsPerPage) {
38741 ctrl._watchers.push($scope.$parent.$watch($attrs.itemsPerPage, function(value) {
38742 ctrl.itemsPerPage = parseInt(value, 10);
38743 $scope.totalPages = ctrl.calculateTotalPages();
38747 ctrl.itemsPerPage = config.itemsPerPage;
38750 $scope.$watch('totalItems', function(newTotal, oldTotal) {
38751 if (angular.isDefined(newTotal) || newTotal !== oldTotal) {
38752 $scope.totalPages = ctrl.calculateTotalPages();
38758 ctrl.calculateTotalPages = function() {
38759 var totalPages = ctrl.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / ctrl.itemsPerPage);
38760 return Math.max(totalPages || 0, 1);
38763 ctrl.render = function() {
38764 $scope.page = parseInt(ctrl.ngModelCtrl.$viewValue, 10) || 1;
38767 $scope.selectPage = function(page, evt) {
38769 evt.preventDefault();
38772 var clickAllowed = !$scope.ngDisabled || !evt;
38773 if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) {
38774 if (evt && evt.target) {
38777 ctrl.ngModelCtrl.$setViewValue(page);
38778 ctrl.ngModelCtrl.$render();
38782 $scope.getText = function(key) {
38783 return $scope[key + 'Text'] || ctrl.config[key + 'Text'];
38786 $scope.noPrevious = function() {
38787 return $scope.page === 1;
38790 $scope.noNext = function() {
38791 return $scope.page === $scope.totalPages;
38794 ctrl.updatePage = function() {
38795 ctrl.setNumPages($scope.$parent, $scope.totalPages); // Readonly variable
38797 if ($scope.page > $scope.totalPages) {
38798 $scope.selectPage($scope.totalPages);
38800 ctrl.ngModelCtrl.$render();
38804 $scope.$on('$destroy', function() {
38805 while (ctrl._watchers.length) {
38806 ctrl._watchers.shift()();
38813 angular.module('ui.bootstrap.pager', ['ui.bootstrap.paging'])
38815 .controller('UibPagerController', ['$scope', '$attrs', 'uibPaging', 'uibPagerConfig', function($scope, $attrs, uibPaging, uibPagerConfig) {
38816 $scope.align = angular.isDefined($attrs.align) ? $scope.$parent.$eval($attrs.align) : uibPagerConfig.align;
38818 uibPaging.create(this, $scope, $attrs);
38821 .constant('uibPagerConfig', {
38823 previousText: '« Previous',
38824 nextText: 'Next »',
38828 .directive('uibPager', ['uibPagerConfig', function(uibPagerConfig) {
38836 require: ['uibPager', '?ngModel'],
38837 controller: 'UibPagerController',
38838 controllerAs: 'pager',
38839 templateUrl: function(element, attrs) {
38840 return attrs.templateUrl || 'uib/template/pager/pager.html';
38843 link: function(scope, element, attrs, ctrls) {
38844 var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
38846 if (!ngModelCtrl) {
38847 return; // do nothing if no ng-model
38850 paginationCtrl.init(ngModelCtrl, uibPagerConfig);
38855 angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging'])
38856 .controller('UibPaginationController', ['$scope', '$attrs', '$parse', 'uibPaging', 'uibPaginationConfig', function($scope, $attrs, $parse, uibPaging, uibPaginationConfig) {
38858 // Setup configuration parameters
38859 var maxSize = angular.isDefined($attrs.maxSize) ? $scope.$parent.$eval($attrs.maxSize) : uibPaginationConfig.maxSize,
38860 rotate = angular.isDefined($attrs.rotate) ? $scope.$parent.$eval($attrs.rotate) : uibPaginationConfig.rotate,
38861 forceEllipses = angular.isDefined($attrs.forceEllipses) ? $scope.$parent.$eval($attrs.forceEllipses) : uibPaginationConfig.forceEllipses,
38862 boundaryLinkNumbers = angular.isDefined($attrs.boundaryLinkNumbers) ? $scope.$parent.$eval($attrs.boundaryLinkNumbers) : uibPaginationConfig.boundaryLinkNumbers,
38863 pageLabel = angular.isDefined($attrs.pageLabel) ? function(idx) { return $scope.$parent.$eval($attrs.pageLabel, {$page: idx}); } : angular.identity;
38864 $scope.boundaryLinks = angular.isDefined($attrs.boundaryLinks) ? $scope.$parent.$eval($attrs.boundaryLinks) : uibPaginationConfig.boundaryLinks;
38865 $scope.directionLinks = angular.isDefined($attrs.directionLinks) ? $scope.$parent.$eval($attrs.directionLinks) : uibPaginationConfig.directionLinks;
38867 uibPaging.create(this, $scope, $attrs);
38869 if ($attrs.maxSize) {
38870 ctrl._watchers.push($scope.$parent.$watch($parse($attrs.maxSize), function(value) {
38871 maxSize = parseInt(value, 10);
38876 // Create page object used in template
38877 function makePage(number, text, isActive) {
38885 function getPages(currentPage, totalPages) {
38888 // Default page limits
38889 var startPage = 1, endPage = totalPages;
38890 var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages;
38892 // recompute if maxSize
38895 // Current page is displayed in the middle of the visible ones
38896 startPage = Math.max(currentPage - Math.floor(maxSize / 2), 1);
38897 endPage = startPage + maxSize - 1;
38899 // Adjust if limit is exceeded
38900 if (endPage > totalPages) {
38901 endPage = totalPages;
38902 startPage = endPage - maxSize + 1;
38905 // Visible pages are paginated with maxSize
38906 startPage = (Math.ceil(currentPage / maxSize) - 1) * maxSize + 1;
38908 // Adjust last page if limit is exceeded
38909 endPage = Math.min(startPage + maxSize - 1, totalPages);
38913 // Add page number links
38914 for (var number = startPage; number <= endPage; number++) {
38915 var page = makePage(number, pageLabel(number), number === currentPage);
38919 // Add links to move between page sets
38920 if (isMaxSized && maxSize > 0 && (!rotate || forceEllipses || boundaryLinkNumbers)) {
38921 if (startPage > 1) {
38922 if (!boundaryLinkNumbers || startPage > 3) { //need ellipsis for all options unless range is too close to beginning
38923 var previousPageSet = makePage(startPage - 1, '...', false);
38924 pages.unshift(previousPageSet);
38926 if (boundaryLinkNumbers) {
38927 if (startPage === 3) { //need to replace ellipsis when the buttons would be sequential
38928 var secondPageLink = makePage(2, '2', false);
38929 pages.unshift(secondPageLink);
38931 //add the first page
38932 var firstPageLink = makePage(1, '1', false);
38933 pages.unshift(firstPageLink);
38937 if (endPage < totalPages) {
38938 if (!boundaryLinkNumbers || endPage < totalPages - 2) { //need ellipsis for all options unless range is too close to end
38939 var nextPageSet = makePage(endPage + 1, '...', false);
38940 pages.push(nextPageSet);
38942 if (boundaryLinkNumbers) {
38943 if (endPage === totalPages - 2) { //need to replace ellipsis when the buttons would be sequential
38944 var secondToLastPageLink = makePage(totalPages - 1, totalPages - 1, false);
38945 pages.push(secondToLastPageLink);
38947 //add the last page
38948 var lastPageLink = makePage(totalPages, totalPages, false);
38949 pages.push(lastPageLink);
38956 var originalRender = this.render;
38957 this.render = function() {
38959 if ($scope.page > 0 && $scope.page <= $scope.totalPages) {
38960 $scope.pages = getPages($scope.page, $scope.totalPages);
38965 .constant('uibPaginationConfig', {
38967 boundaryLinks: false,
38968 boundaryLinkNumbers: false,
38969 directionLinks: true,
38970 firstText: 'First',
38971 previousText: 'Previous',
38975 forceEllipses: false
38978 .directive('uibPagination', ['$parse', 'uibPaginationConfig', function($parse, uibPaginationConfig) {
38988 require: ['uibPagination', '?ngModel'],
38989 controller: 'UibPaginationController',
38990 controllerAs: 'pagination',
38991 templateUrl: function(element, attrs) {
38992 return attrs.templateUrl || 'uib/template/pagination/pagination.html';
38995 link: function(scope, element, attrs, ctrls) {
38996 var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
38998 if (!ngModelCtrl) {
38999 return; // do nothing if no ng-model
39002 paginationCtrl.init(ngModelCtrl, uibPaginationConfig);
39008 * The following features are still outstanding: animation as a
39009 * function, placement as a function, inside, support for more triggers than
39010 * just mouse enter/leave, html tooltips, and selector delegation.
39012 angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.stackedMap'])
39015 * The $tooltip service creates tooltip- and popover-like directives as well as
39016 * houses global options for them.
39018 .provider('$uibTooltip', function() {
39019 // The default options tooltip and popover.
39020 var defaultOptions = {
39022 placementClassPrefix: '',
39025 popupCloseDelay: 0,
39026 useContentExp: false
39029 // Default hide triggers for each show trigger
39031 'mouseenter': 'mouseleave',
39033 'outsideClick': 'outsideClick',
39038 // The options specified to the provider globally.
39039 var globalOptions = {};
39042 * `options({})` allows global configuration of all tooltips in the
39045 * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
39046 * // place tooltips left instead of top by default
39047 * $tooltipProvider.options( { placement: 'left' } );
39050 this.options = function(value) {
39051 angular.extend(globalOptions, value);
39055 * This allows you to extend the set of trigger mappings available. E.g.:
39057 * $tooltipProvider.setTriggers( { 'openTrigger': 'closeTrigger' } );
39059 this.setTriggers = function setTriggers(triggers) {
39060 angular.extend(triggerMap, triggers);
39064 * This is a helper function for translating camel-case to snake_case.
39066 function snake_case(name) {
39067 var regexp = /[A-Z]/g;
39068 var separator = '-';
39069 return name.replace(regexp, function(letter, pos) {
39070 return (pos ? separator : '') + letter.toLowerCase();
39075 * Returns the actual instance of the $tooltip service.
39076 * TODO support multiple triggers
39078 this.$get = ['$window', '$compile', '$timeout', '$document', '$uibPosition', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) {
39079 var openedTooltips = $$stackedMap.createNew();
39080 $document.on('keypress', keypressListener);
39082 $rootScope.$on('$destroy', function() {
39083 $document.off('keypress', keypressListener);
39086 function keypressListener(e) {
39087 if (e.which === 27) {
39088 var last = openedTooltips.top();
39090 last.value.close();
39091 openedTooltips.removeTop();
39097 return function $tooltip(ttType, prefix, defaultTriggerShow, options) {
39098 options = angular.extend({}, defaultOptions, globalOptions, options);
39101 * Returns an object of show and hide triggers.
39103 * If a trigger is supplied,
39104 * it is used to show the tooltip; otherwise, it will use the `trigger`
39105 * option passed to the `$tooltipProvider.options` method; else it will
39106 * default to the trigger supplied to this directive factory.
39108 * The hide trigger is based on the show trigger. If the `trigger` option
39109 * was passed to the `$tooltipProvider.options` method, it will use the
39110 * mapped trigger from `triggerMap` or the passed trigger if the map is
39111 * undefined; otherwise, it uses the `triggerMap` value of the show
39112 * trigger; else it will just use the show trigger.
39114 function getTriggers(trigger) {
39115 var show = (trigger || options.trigger || defaultTriggerShow).split(' ');
39116 var hide = show.map(function(trigger) {
39117 return triggerMap[trigger] || trigger;
39125 var directiveName = snake_case(ttType);
39127 var startSym = $interpolate.startSymbol();
39128 var endSym = $interpolate.endSymbol();
39130 '<div '+ directiveName + '-popup ' +
39131 'uib-title="' + startSym + 'title' + endSym + '" ' +
39132 (options.useContentExp ?
39133 'content-exp="contentExp()" ' :
39134 'content="' + startSym + 'content' + endSym + '" ') +
39135 'placement="' + startSym + 'placement' + endSym + '" ' +
39136 'popup-class="' + startSym + 'popupClass' + endSym + '" ' +
39137 'animation="animation" ' +
39138 'is-open="isOpen" ' +
39139 'origin-scope="origScope" ' +
39140 'class="uib-position-measure"' +
39145 compile: function(tElem, tAttrs) {
39146 var tooltipLinker = $compile(template);
39148 return function link(scope, element, attrs, tooltipCtrl) {
39150 var tooltipLinkedScope;
39151 var transitionTimeout;
39154 var positionTimeout;
39155 var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false;
39156 var triggers = getTriggers(undefined);
39157 var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']);
39158 var ttScope = scope.$new(true);
39159 var repositionScheduled = false;
39160 var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false;
39161 var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false;
39162 var observers = [];
39165 var positionTooltip = function() {
39166 // check if tooltip exists and is not empty
39167 if (!tooltip || !tooltip.html()) { return; }
39169 if (!positionTimeout) {
39170 positionTimeout = $timeout(function() {
39171 var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);
39172 tooltip.css({ top: ttPosition.top + 'px', left: ttPosition.left + 'px' });
39174 if (!tooltip.hasClass(ttPosition.placement.split('-')[0])) {
39175 tooltip.removeClass(lastPlacement.split('-')[0]);
39176 tooltip.addClass(ttPosition.placement.split('-')[0]);
39179 if (!tooltip.hasClass(options.placementClassPrefix + ttPosition.placement)) {
39180 tooltip.removeClass(options.placementClassPrefix + lastPlacement);
39181 tooltip.addClass(options.placementClassPrefix + ttPosition.placement);
39184 // first time through tt element will have the
39185 // uib-position-measure class or if the placement
39186 // has changed we need to position the arrow.
39187 if (tooltip.hasClass('uib-position-measure')) {
39188 $position.positionArrow(tooltip, ttPosition.placement);
39189 tooltip.removeClass('uib-position-measure');
39190 } else if (lastPlacement !== ttPosition.placement) {
39191 $position.positionArrow(tooltip, ttPosition.placement);
39193 lastPlacement = ttPosition.placement;
39195 positionTimeout = null;
39200 // Set up the correct scope to allow transclusion later
39201 ttScope.origScope = scope;
39203 // By default, the tooltip is not open.
39204 // TODO add ability to start tooltip opened
39205 ttScope.isOpen = false;
39206 openedTooltips.add(ttScope, {
39210 function toggleTooltipBind() {
39211 if (!ttScope.isOpen) {
39218 // Show the tooltip with delay if specified, otherwise show it immediately
39219 function showTooltipBind() {
39220 if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) {
39227 if (ttScope.popupDelay) {
39228 // Do nothing if the tooltip was already scheduled to pop-up.
39229 // This happens if show is triggered multiple times before any hide is triggered.
39230 if (!showTimeout) {
39231 showTimeout = $timeout(show, ttScope.popupDelay, false);
39238 function hideTooltipBind() {
39241 if (ttScope.popupCloseDelay) {
39242 if (!hideTimeout) {
39243 hideTimeout = $timeout(hide, ttScope.popupCloseDelay, false);
39250 // Show the tooltip popup element.
39255 // Don't show empty tooltips.
39256 if (!ttScope.content) {
39257 return angular.noop;
39262 // And show the tooltip.
39263 ttScope.$evalAsync(function() {
39264 ttScope.isOpen = true;
39265 assignIsOpen(true);
39270 function cancelShow() {
39272 $timeout.cancel(showTimeout);
39273 showTimeout = null;
39276 if (positionTimeout) {
39277 $timeout.cancel(positionTimeout);
39278 positionTimeout = null;
39282 // Hide the tooltip popup element.
39288 // First things first: we don't show it anymore.
39289 ttScope.$evalAsync(function() {
39291 ttScope.isOpen = false;
39292 assignIsOpen(false);
39293 // And now we remove it from the DOM. However, if we have animation, we
39294 // need to wait for it to expire beforehand.
39295 // FIXME: this is a placeholder for a port of the transitions library.
39296 // The fade transition in TWBS is 150ms.
39297 if (ttScope.animation) {
39298 if (!transitionTimeout) {
39299 transitionTimeout = $timeout(removeTooltip, 150, false);
39308 function cancelHide() {
39310 $timeout.cancel(hideTimeout);
39311 hideTimeout = null;
39314 if (transitionTimeout) {
39315 $timeout.cancel(transitionTimeout);
39316 transitionTimeout = null;
39320 function createTooltip() {
39321 // There can only be one tooltip element per directive shown at once.
39326 tooltipLinkedScope = ttScope.$new();
39327 tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) {
39328 if (appendToBody) {
39329 $document.find('body').append(tooltip);
39331 element.after(tooltip);
39338 function removeTooltip() {
39341 unregisterObservers();
39347 if (tooltipLinkedScope) {
39348 tooltipLinkedScope.$destroy();
39349 tooltipLinkedScope = null;
39354 * Set the initial scope values. Once
39355 * the tooltip is created, the observers
39356 * will be added to keep things in sync.
39358 function prepareTooltip() {
39359 ttScope.title = attrs[prefix + 'Title'];
39360 if (contentParse) {
39361 ttScope.content = contentParse(scope);
39363 ttScope.content = attrs[ttType];
39366 ttScope.popupClass = attrs[prefix + 'Class'];
39367 ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement;
39368 var placement = $position.parsePlacement(ttScope.placement);
39369 lastPlacement = placement[1] ? placement[0] + '-' + placement[1] : placement[0];
39371 var delay = parseInt(attrs[prefix + 'PopupDelay'], 10);
39372 var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10);
39373 ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay;
39374 ttScope.popupCloseDelay = !isNaN(closeDelay) ? closeDelay : options.popupCloseDelay;
39377 function assignIsOpen(isOpen) {
39378 if (isOpenParse && angular.isFunction(isOpenParse.assign)) {
39379 isOpenParse.assign(scope, isOpen);
39383 ttScope.contentExp = function() {
39384 return ttScope.content;
39388 * Observe the relevant attributes.
39390 attrs.$observe('disabled', function(val) {
39395 if (val && ttScope.isOpen) {
39401 scope.$watch(isOpenParse, function(val) {
39402 if (ttScope && !val === ttScope.isOpen) {
39403 toggleTooltipBind();
39408 function prepObservers() {
39409 observers.length = 0;
39411 if (contentParse) {
39413 scope.$watch(contentParse, function(val) {
39414 ttScope.content = val;
39415 if (!val && ttScope.isOpen) {
39422 tooltipLinkedScope.$watch(function() {
39423 if (!repositionScheduled) {
39424 repositionScheduled = true;
39425 tooltipLinkedScope.$$postDigest(function() {
39426 repositionScheduled = false;
39427 if (ttScope && ttScope.isOpen) {
39436 attrs.$observe(ttType, function(val) {
39437 ttScope.content = val;
39438 if (!val && ttScope.isOpen) {
39448 attrs.$observe(prefix + 'Title', function(val) {
39449 ttScope.title = val;
39450 if (ttScope.isOpen) {
39457 attrs.$observe(prefix + 'Placement', function(val) {
39458 ttScope.placement = val ? val : options.placement;
39459 if (ttScope.isOpen) {
39466 function unregisterObservers() {
39467 if (observers.length) {
39468 angular.forEach(observers, function(observer) {
39471 observers.length = 0;
39475 // hide tooltips/popovers for outsideClick trigger
39476 function bodyHideTooltipBind(e) {
39477 if (!ttScope || !ttScope.isOpen || !tooltip) {
39480 // make sure the tooltip/popover link or tool tooltip/popover itself were not clicked
39481 if (!element[0].contains(e.target) && !tooltip[0].contains(e.target)) {
39486 var unregisterTriggers = function() {
39487 triggers.show.forEach(function(trigger) {
39488 if (trigger === 'outsideClick') {
39489 element.off('click', toggleTooltipBind);
39491 element.off(trigger, showTooltipBind);
39492 element.off(trigger, toggleTooltipBind);
39495 triggers.hide.forEach(function(trigger) {
39496 if (trigger === 'outsideClick') {
39497 $document.off('click', bodyHideTooltipBind);
39499 element.off(trigger, hideTooltipBind);
39504 function prepTriggers() {
39505 var val = attrs[prefix + 'Trigger'];
39506 unregisterTriggers();
39508 triggers = getTriggers(val);
39510 if (triggers.show !== 'none') {
39511 triggers.show.forEach(function(trigger, idx) {
39512 if (trigger === 'outsideClick') {
39513 element.on('click', toggleTooltipBind);
39514 $document.on('click', bodyHideTooltipBind);
39515 } else if (trigger === triggers.hide[idx]) {
39516 element.on(trigger, toggleTooltipBind);
39517 } else if (trigger) {
39518 element.on(trigger, showTooltipBind);
39519 element.on(triggers.hide[idx], hideTooltipBind);
39522 element.on('keypress', function(e) {
39523 if (e.which === 27) {
39533 var animation = scope.$eval(attrs[prefix + 'Animation']);
39534 ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation;
39536 var appendToBodyVal;
39537 var appendKey = prefix + 'AppendToBody';
39538 if (appendKey in attrs && attrs[appendKey] === undefined) {
39539 appendToBodyVal = true;
39541 appendToBodyVal = scope.$eval(attrs[appendKey]);
39544 appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;
39546 // Make sure tooltip is destroyed and removed.
39547 scope.$on('$destroy', function onDestroyTooltip() {
39548 unregisterTriggers();
39550 openedTooltips.remove(ttScope);
39560 // This is mostly ngInclude code but with a custom scope
39561 .directive('uibTooltipTemplateTransclude', [
39562 '$animate', '$sce', '$compile', '$templateRequest',
39563 function ($animate, $sce, $compile, $templateRequest) {
39565 link: function(scope, elem, attrs) {
39566 var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope);
39568 var changeCounter = 0,
39573 var cleanupLastIncludeContent = function() {
39574 if (previousElement) {
39575 previousElement.remove();
39576 previousElement = null;
39579 if (currentScope) {
39580 currentScope.$destroy();
39581 currentScope = null;
39584 if (currentElement) {
39585 $animate.leave(currentElement).then(function() {
39586 previousElement = null;
39588 previousElement = currentElement;
39589 currentElement = null;
39593 scope.$watch($sce.parseAsResourceUrl(attrs.uibTooltipTemplateTransclude), function(src) {
39594 var thisChangeId = ++changeCounter;
39597 //set the 2nd param to true to ignore the template request error so that the inner
39598 //contents and scope can be cleaned up.
39599 $templateRequest(src, true).then(function(response) {
39600 if (thisChangeId !== changeCounter) { return; }
39601 var newScope = origScope.$new();
39602 var template = response;
39604 var clone = $compile(template)(newScope, function(clone) {
39605 cleanupLastIncludeContent();
39606 $animate.enter(clone, elem);
39609 currentScope = newScope;
39610 currentElement = clone;
39612 currentScope.$emit('$includeContentLoaded', src);
39614 if (thisChangeId === changeCounter) {
39615 cleanupLastIncludeContent();
39616 scope.$emit('$includeContentError', src);
39619 scope.$emit('$includeContentRequested', src);
39621 cleanupLastIncludeContent();
39625 scope.$on('$destroy', cleanupLastIncludeContent);
39631 * Note that it's intentional that these classes are *not* applied through $animate.
39632 * They must not be animated as they're expected to be present on the tooltip on
39635 .directive('uibTooltipClasses', ['$uibPosition', function($uibPosition) {
39638 link: function(scope, element, attrs) {
39639 // need to set the primary position so the
39640 // arrow has space during position measure.
39641 // tooltip.positionTooltip()
39642 if (scope.placement) {
39643 // // There are no top-left etc... classes
39644 // // in TWBS, so we need the primary position.
39645 var position = $uibPosition.parsePlacement(scope.placement);
39646 element.addClass(position[0]);
39649 if (scope.popupClass) {
39650 element.addClass(scope.popupClass);
39653 if (scope.animation()) {
39654 element.addClass(attrs.tooltipAnimationClass);
39660 .directive('uibTooltipPopup', function() {
39663 scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
39664 templateUrl: 'uib/template/tooltip/tooltip-popup.html'
39668 .directive('uibTooltip', [ '$uibTooltip', function($uibTooltip) {
39669 return $uibTooltip('uibTooltip', 'tooltip', 'mouseenter');
39672 .directive('uibTooltipTemplatePopup', function() {
39675 scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
39676 originScope: '&' },
39677 templateUrl: 'uib/template/tooltip/tooltip-template-popup.html'
39681 .directive('uibTooltipTemplate', ['$uibTooltip', function($uibTooltip) {
39682 return $uibTooltip('uibTooltipTemplate', 'tooltip', 'mouseenter', {
39683 useContentExp: true
39687 .directive('uibTooltipHtmlPopup', function() {
39690 scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
39691 templateUrl: 'uib/template/tooltip/tooltip-html-popup.html'
39695 .directive('uibTooltipHtml', ['$uibTooltip', function($uibTooltip) {
39696 return $uibTooltip('uibTooltipHtml', 'tooltip', 'mouseenter', {
39697 useContentExp: true
39702 * The following features are still outstanding: popup delay, animation as a
39703 * function, placement as a function, inside, support for more triggers than
39704 * just mouse enter/leave, and selector delegatation.
39706 angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip'])
39708 .directive('uibPopoverTemplatePopup', function() {
39711 scope: { uibTitle: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
39712 originScope: '&' },
39713 templateUrl: 'uib/template/popover/popover-template.html'
39717 .directive('uibPopoverTemplate', ['$uibTooltip', function($uibTooltip) {
39718 return $uibTooltip('uibPopoverTemplate', 'popover', 'click', {
39719 useContentExp: true
39723 .directive('uibPopoverHtmlPopup', function() {
39726 scope: { contentExp: '&', uibTitle: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
39727 templateUrl: 'uib/template/popover/popover-html.html'
39731 .directive('uibPopoverHtml', ['$uibTooltip', function($uibTooltip) {
39732 return $uibTooltip('uibPopoverHtml', 'popover', 'click', {
39733 useContentExp: true
39737 .directive('uibPopoverPopup', function() {
39740 scope: { uibTitle: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
39741 templateUrl: 'uib/template/popover/popover.html'
39745 .directive('uibPopover', ['$uibTooltip', function($uibTooltip) {
39746 return $uibTooltip('uibPopover', 'popover', 'click');
39749 angular.module('ui.bootstrap.progressbar', [])
39751 .constant('uibProgressConfig', {
39756 .controller('UibProgressController', ['$scope', '$attrs', 'uibProgressConfig', function($scope, $attrs, progressConfig) {
39758 animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
39761 $scope.max = getMaxOrDefault();
39763 this.addBar = function(bar, element, attrs) {
39765 element.css({'transition': 'none'});
39768 this.bars.push(bar);
39770 bar.max = getMaxOrDefault();
39771 bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar';
39773 bar.$watch('value', function(value) {
39774 bar.recalculatePercentage();
39777 bar.recalculatePercentage = function() {
39778 var totalPercentage = self.bars.reduce(function(total, bar) {
39779 bar.percent = +(100 * bar.value / bar.max).toFixed(2);
39780 return total + bar.percent;
39783 if (totalPercentage > 100) {
39784 bar.percent -= totalPercentage - 100;
39788 bar.$on('$destroy', function() {
39790 self.removeBar(bar);
39794 this.removeBar = function(bar) {
39795 this.bars.splice(this.bars.indexOf(bar), 1);
39796 this.bars.forEach(function (bar) {
39797 bar.recalculatePercentage();
39801 //$attrs.$observe('maxParam', function(maxParam) {
39802 $scope.$watch('maxParam', function(maxParam) {
39803 self.bars.forEach(function(bar) {
39804 bar.max = getMaxOrDefault();
39805 bar.recalculatePercentage();
39809 function getMaxOrDefault () {
39810 return angular.isDefined($scope.maxParam) ? $scope.maxParam : progressConfig.max;
39814 .directive('uibProgress', function() {
39818 controller: 'UibProgressController',
39819 require: 'uibProgress',
39823 templateUrl: 'uib/template/progressbar/progress.html'
39827 .directive('uibBar', function() {
39831 require: '^uibProgress',
39836 templateUrl: 'uib/template/progressbar/bar.html',
39837 link: function(scope, element, attrs, progressCtrl) {
39838 progressCtrl.addBar(scope, element, attrs);
39843 .directive('uibProgressbar', function() {
39847 controller: 'UibProgressController',
39853 templateUrl: 'uib/template/progressbar/progressbar.html',
39854 link: function(scope, element, attrs, progressCtrl) {
39855 progressCtrl.addBar(scope, angular.element(element.children()[0]), {title: attrs.title});
39860 angular.module('ui.bootstrap.rating', [])
39862 .constant('uibRatingConfig', {
39867 titles : ['one', 'two', 'three', 'four', 'five']
39870 .controller('UibRatingController', ['$scope', '$attrs', 'uibRatingConfig', function($scope, $attrs, ratingConfig) {
39871 var ngModelCtrl = { $setViewValue: angular.noop },
39874 this.init = function(ngModelCtrl_) {
39875 ngModelCtrl = ngModelCtrl_;
39876 ngModelCtrl.$render = this.render;
39878 ngModelCtrl.$formatters.push(function(value) {
39879 if (angular.isNumber(value) && value << 0 !== value) {
39880 value = Math.round(value);
39886 this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
39887 this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
39888 this.enableReset = angular.isDefined($attrs.enableReset) ?
39889 $scope.$parent.$eval($attrs.enableReset) : ratingConfig.enableReset;
39890 var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles;
39891 this.titles = angular.isArray(tmpTitles) && tmpTitles.length > 0 ?
39892 tmpTitles : ratingConfig.titles;
39894 var ratingStates = angular.isDefined($attrs.ratingStates) ?
39895 $scope.$parent.$eval($attrs.ratingStates) :
39896 new Array(angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max);
39897 $scope.range = this.buildTemplateObjects(ratingStates);
39900 this.buildTemplateObjects = function(states) {
39901 for (var i = 0, n = states.length; i < n; i++) {
39902 states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff, title: this.getTitle(i) }, states[i]);
39907 this.getTitle = function(index) {
39908 if (index >= this.titles.length) {
39912 return this.titles[index];
39915 $scope.rate = function(value) {
39916 if (!$scope.readonly && value >= 0 && value <= $scope.range.length) {
39917 var newViewValue = self.enableReset && ngModelCtrl.$viewValue === value ? 0 : value;
39918 ngModelCtrl.$setViewValue(newViewValue);
39919 ngModelCtrl.$render();
39923 $scope.enter = function(value) {
39924 if (!$scope.readonly) {
39925 $scope.value = value;
39927 $scope.onHover({value: value});
39930 $scope.reset = function() {
39931 $scope.value = ngModelCtrl.$viewValue;
39935 $scope.onKeydown = function(evt) {
39936 if (/(37|38|39|40)/.test(evt.which)) {
39937 evt.preventDefault();
39938 evt.stopPropagation();
39939 $scope.rate($scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1));
39943 this.render = function() {
39944 $scope.value = ngModelCtrl.$viewValue;
39945 $scope.title = self.getTitle($scope.value - 1);
39949 .directive('uibRating', function() {
39951 require: ['uibRating', 'ngModel'],
39953 readonly: '=?readOnly',
39957 controller: 'UibRatingController',
39958 templateUrl: 'uib/template/rating/rating.html',
39960 link: function(scope, element, attrs, ctrls) {
39961 var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];
39962 ratingCtrl.init(ngModelCtrl);
39967 angular.module('ui.bootstrap.tabs', [])
39969 .controller('UibTabsetController', ['$scope', function ($scope) {
39974 ctrl.select = function(index, evt) {
39976 var previousIndex = findTabIndex(oldIndex);
39977 var previousSelected = ctrl.tabs[previousIndex];
39978 if (previousSelected) {
39979 previousSelected.tab.onDeselect({
39981 $selectedIndex: index
39983 if (evt && evt.isDefaultPrevented()) {
39986 previousSelected.tab.active = false;
39989 var selected = ctrl.tabs[index];
39991 selected.tab.onSelect({
39994 selected.tab.active = true;
39995 ctrl.active = selected.index;
39996 oldIndex = selected.index;
39997 } else if (!selected && angular.isDefined(oldIndex)) {
39998 ctrl.active = null;
40004 ctrl.addTab = function addTab(tab) {
40009 ctrl.tabs.sort(function(t1, t2) {
40010 if (t1.index > t2.index) {
40014 if (t1.index < t2.index) {
40021 if (tab.index === ctrl.active || !angular.isDefined(ctrl.active) && ctrl.tabs.length === 1) {
40022 var newActiveIndex = findTabIndex(tab.index);
40023 ctrl.select(newActiveIndex);
40027 ctrl.removeTab = function removeTab(tab) {
40029 for (var i = 0; i < ctrl.tabs.length; i++) {
40030 if (ctrl.tabs[i].tab === tab) {
40036 if (ctrl.tabs[index].index === ctrl.active) {
40037 var newActiveTabIndex = index === ctrl.tabs.length - 1 ?
40038 index - 1 : index + 1 % ctrl.tabs.length;
40039 ctrl.select(newActiveTabIndex);
40042 ctrl.tabs.splice(index, 1);
40045 $scope.$watch('tabset.active', function(val) {
40046 if (angular.isDefined(val) && val !== oldIndex) {
40047 ctrl.select(findTabIndex(val));
40052 $scope.$on('$destroy', function() {
40056 function findTabIndex(index) {
40057 for (var i = 0; i < ctrl.tabs.length; i++) {
40058 if (ctrl.tabs[i].index === index) {
40065 .directive('uibTabset', function() {
40070 bindToController: {
40074 controller: 'UibTabsetController',
40075 controllerAs: 'tabset',
40076 templateUrl: function(element, attrs) {
40077 return attrs.templateUrl || 'uib/template/tabs/tabset.html';
40079 link: function(scope, element, attrs) {
40080 scope.vertical = angular.isDefined(attrs.vertical) ?
40081 scope.$parent.$eval(attrs.vertical) : false;
40082 scope.justified = angular.isDefined(attrs.justified) ?
40083 scope.$parent.$eval(attrs.justified) : false;
40088 .directive('uibTab', ['$parse', function($parse) {
40090 require: '^uibTabset',
40092 templateUrl: function(element, attrs) {
40093 return attrs.templateUrl || 'uib/template/tabs/tab.html';
40100 onSelect: '&select', //This callback is called in contentHeadingTransclude
40101 //once it inserts the tab's content into the dom
40102 onDeselect: '&deselect'
40104 controller: function() {
40105 //Empty controller so other directives can require being 'under' a tab
40107 controllerAs: 'tab',
40108 link: function(scope, elm, attrs, tabsetCtrl, transclude) {
40109 scope.disabled = false;
40110 if (attrs.disable) {
40111 scope.$parent.$watch($parse(attrs.disable), function(value) {
40112 scope.disabled = !! value;
40116 if (angular.isUndefined(attrs.index)) {
40117 if (tabsetCtrl.tabs && tabsetCtrl.tabs.length) {
40118 scope.index = Math.max.apply(null, tabsetCtrl.tabs.map(function(t) { return t.index; })) + 1;
40124 if (angular.isUndefined(attrs.classes)) {
40125 scope.classes = '';
40128 scope.select = function(evt) {
40129 if (!scope.disabled) {
40131 for (var i = 0; i < tabsetCtrl.tabs.length; i++) {
40132 if (tabsetCtrl.tabs[i].tab === scope) {
40138 tabsetCtrl.select(index, evt);
40142 tabsetCtrl.addTab(scope);
40143 scope.$on('$destroy', function() {
40144 tabsetCtrl.removeTab(scope);
40147 //We need to transclude later, once the content container is ready.
40148 //when this link happens, we're inside a tab heading.
40149 scope.$transcludeFn = transclude;
40154 .directive('uibTabHeadingTransclude', function() {
40157 require: '^uibTab',
40158 link: function(scope, elm) {
40159 scope.$watch('headingElement', function updateHeadingElement(heading) {
40162 elm.append(heading);
40169 .directive('uibTabContentTransclude', function() {
40172 require: '^uibTabset',
40173 link: function(scope, elm, attrs) {
40174 var tab = scope.$eval(attrs.uibTabContentTransclude).tab;
40176 //Now our tab is ready to be transcluded: both the tab heading area
40177 //and the tab content area are loaded. Transclude 'em both.
40178 tab.$transcludeFn(tab.$parent, function(contents) {
40179 angular.forEach(contents, function(node) {
40180 if (isTabHeading(node)) {
40181 //Let tabHeadingTransclude know.
40182 tab.headingElement = node;
40191 function isTabHeading(node) {
40192 return node.tagName && (
40193 node.hasAttribute('uib-tab-heading') ||
40194 node.hasAttribute('data-uib-tab-heading') ||
40195 node.hasAttribute('x-uib-tab-heading') ||
40196 node.tagName.toLowerCase() === 'uib-tab-heading' ||
40197 node.tagName.toLowerCase() === 'data-uib-tab-heading' ||
40198 node.tagName.toLowerCase() === 'x-uib-tab-heading' ||
40199 node.tagName.toLowerCase() === 'uib:tab-heading'
40204 angular.module('ui.bootstrap.timepicker', [])
40206 .constant('uibTimepickerConfig', {
40210 showMeridian: true,
40211 showSeconds: false,
40213 readonlyInput: false,
40216 showSpinners: true,
40217 templateUrl: 'uib/template/timepicker/timepicker.html'
40220 .controller('UibTimepickerController', ['$scope', '$element', '$attrs', '$parse', '$log', '$locale', 'uibTimepickerConfig', function($scope, $element, $attrs, $parse, $log, $locale, timepickerConfig) {
40221 var selected = new Date(),
40223 ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
40224 meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS,
40225 padHours = angular.isDefined($attrs.padHours) ? $scope.$parent.$eval($attrs.padHours) : true;
40227 $scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0;
40228 $element.removeAttr('tabindex');
40230 this.init = function(ngModelCtrl_, inputs) {
40231 ngModelCtrl = ngModelCtrl_;
40232 ngModelCtrl.$render = this.render;
40234 ngModelCtrl.$formatters.unshift(function(modelValue) {
40235 return modelValue ? new Date(modelValue) : null;
40238 var hoursInputEl = inputs.eq(0),
40239 minutesInputEl = inputs.eq(1),
40240 secondsInputEl = inputs.eq(2);
40242 var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel;
40245 this.setupMousewheelEvents(hoursInputEl, minutesInputEl, secondsInputEl);
40248 var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys;
40250 this.setupArrowkeyEvents(hoursInputEl, minutesInputEl, secondsInputEl);
40253 $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput;
40254 this.setupInputEvents(hoursInputEl, minutesInputEl, secondsInputEl);
40257 var hourStep = timepickerConfig.hourStep;
40258 if ($attrs.hourStep) {
40259 watchers.push($scope.$parent.$watch($parse($attrs.hourStep), function(value) {
40264 var minuteStep = timepickerConfig.minuteStep;
40265 if ($attrs.minuteStep) {
40266 watchers.push($scope.$parent.$watch($parse($attrs.minuteStep), function(value) {
40267 minuteStep = +value;
40272 watchers.push($scope.$parent.$watch($parse($attrs.min), function(value) {
40273 var dt = new Date(value);
40274 min = isNaN(dt) ? undefined : dt;
40278 watchers.push($scope.$parent.$watch($parse($attrs.max), function(value) {
40279 var dt = new Date(value);
40280 max = isNaN(dt) ? undefined : dt;
40283 var disabled = false;
40284 if ($attrs.ngDisabled) {
40285 watchers.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(value) {
40290 $scope.noIncrementHours = function() {
40291 var incrementedSelected = addMinutes(selected, hourStep * 60);
40292 return disabled || incrementedSelected > max ||
40293 incrementedSelected < selected && incrementedSelected < min;
40296 $scope.noDecrementHours = function() {
40297 var decrementedSelected = addMinutes(selected, -hourStep * 60);
40298 return disabled || decrementedSelected < min ||
40299 decrementedSelected > selected && decrementedSelected > max;
40302 $scope.noIncrementMinutes = function() {
40303 var incrementedSelected = addMinutes(selected, minuteStep);
40304 return disabled || incrementedSelected > max ||
40305 incrementedSelected < selected && incrementedSelected < min;
40308 $scope.noDecrementMinutes = function() {
40309 var decrementedSelected = addMinutes(selected, -minuteStep);
40310 return disabled || decrementedSelected < min ||
40311 decrementedSelected > selected && decrementedSelected > max;
40314 $scope.noIncrementSeconds = function() {
40315 var incrementedSelected = addSeconds(selected, secondStep);
40316 return disabled || incrementedSelected > max ||
40317 incrementedSelected < selected && incrementedSelected < min;
40320 $scope.noDecrementSeconds = function() {
40321 var decrementedSelected = addSeconds(selected, -secondStep);
40322 return disabled || decrementedSelected < min ||
40323 decrementedSelected > selected && decrementedSelected > max;
40326 $scope.noToggleMeridian = function() {
40327 if (selected.getHours() < 12) {
40328 return disabled || addMinutes(selected, 12 * 60) > max;
40331 return disabled || addMinutes(selected, -12 * 60) < min;
40334 var secondStep = timepickerConfig.secondStep;
40335 if ($attrs.secondStep) {
40336 watchers.push($scope.$parent.$watch($parse($attrs.secondStep), function(value) {
40337 secondStep = +value;
40341 $scope.showSeconds = timepickerConfig.showSeconds;
40342 if ($attrs.showSeconds) {
40343 watchers.push($scope.$parent.$watch($parse($attrs.showSeconds), function(value) {
40344 $scope.showSeconds = !!value;
40349 $scope.showMeridian = timepickerConfig.showMeridian;
40350 if ($attrs.showMeridian) {
40351 watchers.push($scope.$parent.$watch($parse($attrs.showMeridian), function(value) {
40352 $scope.showMeridian = !!value;
40354 if (ngModelCtrl.$error.time) {
40355 // Evaluate from template
40356 var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();
40357 if (angular.isDefined(hours) && angular.isDefined(minutes)) {
40358 selected.setHours(hours);
40367 // Get $scope.hours in 24H mode if valid
40368 function getHoursFromTemplate() {
40369 var hours = +$scope.hours;
40370 var valid = $scope.showMeridian ? hours > 0 && hours < 13 :
40371 hours >= 0 && hours < 24;
40372 if (!valid || $scope.hours === '') {
40376 if ($scope.showMeridian) {
40377 if (hours === 12) {
40380 if ($scope.meridian === meridians[1]) {
40381 hours = hours + 12;
40387 function getMinutesFromTemplate() {
40388 var minutes = +$scope.minutes;
40389 var valid = minutes >= 0 && minutes < 60;
40390 if (!valid || $scope.minutes === '') {
40396 function getSecondsFromTemplate() {
40397 var seconds = +$scope.seconds;
40398 return seconds >= 0 && seconds < 60 ? seconds : undefined;
40401 function pad(value, noPad) {
40402 if (value === null) {
40406 return angular.isDefined(value) && value.toString().length < 2 && !noPad ?
40407 '0' + value : value.toString();
40410 // Respond on mousewheel spin
40411 this.setupMousewheelEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
40412 var isScrollingUp = function(e) {
40413 if (e.originalEvent) {
40414 e = e.originalEvent;
40416 //pick correct delta variable depending on event
40417 var delta = e.wheelDelta ? e.wheelDelta : -e.deltaY;
40418 return e.detail || delta > 0;
40421 hoursInputEl.bind('mousewheel wheel', function(e) {
40423 $scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours());
40425 e.preventDefault();
40428 minutesInputEl.bind('mousewheel wheel', function(e) {
40430 $scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes());
40432 e.preventDefault();
40435 secondsInputEl.bind('mousewheel wheel', function(e) {
40437 $scope.$apply(isScrollingUp(e) ? $scope.incrementSeconds() : $scope.decrementSeconds());
40439 e.preventDefault();
40443 // Respond on up/down arrowkeys
40444 this.setupArrowkeyEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
40445 hoursInputEl.bind('keydown', function(e) {
40447 if (e.which === 38) { // up
40448 e.preventDefault();
40449 $scope.incrementHours();
40451 } else if (e.which === 40) { // down
40452 e.preventDefault();
40453 $scope.decrementHours();
40459 minutesInputEl.bind('keydown', function(e) {
40461 if (e.which === 38) { // up
40462 e.preventDefault();
40463 $scope.incrementMinutes();
40465 } else if (e.which === 40) { // down
40466 e.preventDefault();
40467 $scope.decrementMinutes();
40473 secondsInputEl.bind('keydown', function(e) {
40475 if (e.which === 38) { // up
40476 e.preventDefault();
40477 $scope.incrementSeconds();
40479 } else if (e.which === 40) { // down
40480 e.preventDefault();
40481 $scope.decrementSeconds();
40488 this.setupInputEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
40489 if ($scope.readonlyInput) {
40490 $scope.updateHours = angular.noop;
40491 $scope.updateMinutes = angular.noop;
40492 $scope.updateSeconds = angular.noop;
40496 var invalidate = function(invalidHours, invalidMinutes, invalidSeconds) {
40497 ngModelCtrl.$setViewValue(null);
40498 ngModelCtrl.$setValidity('time', false);
40499 if (angular.isDefined(invalidHours)) {
40500 $scope.invalidHours = invalidHours;
40503 if (angular.isDefined(invalidMinutes)) {
40504 $scope.invalidMinutes = invalidMinutes;
40507 if (angular.isDefined(invalidSeconds)) {
40508 $scope.invalidSeconds = invalidSeconds;
40512 $scope.updateHours = function() {
40513 var hours = getHoursFromTemplate(),
40514 minutes = getMinutesFromTemplate();
40516 ngModelCtrl.$setDirty();
40518 if (angular.isDefined(hours) && angular.isDefined(minutes)) {
40519 selected.setHours(hours);
40520 selected.setMinutes(minutes);
40521 if (selected < min || selected > max) {
40531 hoursInputEl.bind('blur', function(e) {
40532 ngModelCtrl.$setTouched();
40533 if (modelIsEmpty()) {
40535 } else if ($scope.hours === null || $scope.hours === '') {
40537 } else if (!$scope.invalidHours && $scope.hours < 10) {
40538 $scope.$apply(function() {
40539 $scope.hours = pad($scope.hours, !padHours);
40544 $scope.updateMinutes = function() {
40545 var minutes = getMinutesFromTemplate(),
40546 hours = getHoursFromTemplate();
40548 ngModelCtrl.$setDirty();
40550 if (angular.isDefined(minutes) && angular.isDefined(hours)) {
40551 selected.setHours(hours);
40552 selected.setMinutes(minutes);
40553 if (selected < min || selected > max) {
40554 invalidate(undefined, true);
40559 invalidate(undefined, true);
40563 minutesInputEl.bind('blur', function(e) {
40564 ngModelCtrl.$setTouched();
40565 if (modelIsEmpty()) {
40567 } else if ($scope.minutes === null) {
40568 invalidate(undefined, true);
40569 } else if (!$scope.invalidMinutes && $scope.minutes < 10) {
40570 $scope.$apply(function() {
40571 $scope.minutes = pad($scope.minutes);
40576 $scope.updateSeconds = function() {
40577 var seconds = getSecondsFromTemplate();
40579 ngModelCtrl.$setDirty();
40581 if (angular.isDefined(seconds)) {
40582 selected.setSeconds(seconds);
40585 invalidate(undefined, undefined, true);
40589 secondsInputEl.bind('blur', function(e) {
40590 if (modelIsEmpty()) {
40592 } else if (!$scope.invalidSeconds && $scope.seconds < 10) {
40593 $scope.$apply( function() {
40594 $scope.seconds = pad($scope.seconds);
40601 this.render = function() {
40602 var date = ngModelCtrl.$viewValue;
40605 ngModelCtrl.$setValidity('time', false);
40606 $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.');
40612 if (selected < min || selected > max) {
40613 ngModelCtrl.$setValidity('time', false);
40614 $scope.invalidHours = true;
40615 $scope.invalidMinutes = true;
40623 // Call internally when we know that model is valid.
40624 function refresh(keyboardChange) {
40626 ngModelCtrl.$setViewValue(new Date(selected));
40627 updateTemplate(keyboardChange);
40630 function makeValid() {
40631 ngModelCtrl.$setValidity('time', true);
40632 $scope.invalidHours = false;
40633 $scope.invalidMinutes = false;
40634 $scope.invalidSeconds = false;
40637 function updateTemplate(keyboardChange) {
40638 if (!ngModelCtrl.$modelValue) {
40639 $scope.hours = null;
40640 $scope.minutes = null;
40641 $scope.seconds = null;
40642 $scope.meridian = meridians[0];
40644 var hours = selected.getHours(),
40645 minutes = selected.getMinutes(),
40646 seconds = selected.getSeconds();
40648 if ($scope.showMeridian) {
40649 hours = hours === 0 || hours === 12 ? 12 : hours % 12; // Convert 24 to 12 hour system
40652 $scope.hours = keyboardChange === 'h' ? hours : pad(hours, !padHours);
40653 if (keyboardChange !== 'm') {
40654 $scope.minutes = pad(minutes);
40656 $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
40658 if (keyboardChange !== 's') {
40659 $scope.seconds = pad(seconds);
40661 $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
40665 function addSecondsToSelected(seconds) {
40666 selected = addSeconds(selected, seconds);
40670 function addMinutes(selected, minutes) {
40671 return addSeconds(selected, minutes*60);
40674 function addSeconds(date, seconds) {
40675 var dt = new Date(date.getTime() + seconds * 1000);
40676 var newDate = new Date(date);
40677 newDate.setHours(dt.getHours(), dt.getMinutes(), dt.getSeconds());
40681 function modelIsEmpty() {
40682 return ($scope.hours === null || $scope.hours === '') &&
40683 ($scope.minutes === null || $scope.minutes === '') &&
40684 (!$scope.showSeconds || $scope.showSeconds && ($scope.seconds === null || $scope.seconds === ''));
40687 $scope.showSpinners = angular.isDefined($attrs.showSpinners) ?
40688 $scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners;
40690 $scope.incrementHours = function() {
40691 if (!$scope.noIncrementHours()) {
40692 addSecondsToSelected(hourStep * 60 * 60);
40696 $scope.decrementHours = function() {
40697 if (!$scope.noDecrementHours()) {
40698 addSecondsToSelected(-hourStep * 60 * 60);
40702 $scope.incrementMinutes = function() {
40703 if (!$scope.noIncrementMinutes()) {
40704 addSecondsToSelected(minuteStep * 60);
40708 $scope.decrementMinutes = function() {
40709 if (!$scope.noDecrementMinutes()) {
40710 addSecondsToSelected(-minuteStep * 60);
40714 $scope.incrementSeconds = function() {
40715 if (!$scope.noIncrementSeconds()) {
40716 addSecondsToSelected(secondStep);
40720 $scope.decrementSeconds = function() {
40721 if (!$scope.noDecrementSeconds()) {
40722 addSecondsToSelected(-secondStep);
40726 $scope.toggleMeridian = function() {
40727 var minutes = getMinutesFromTemplate(),
40728 hours = getHoursFromTemplate();
40730 if (!$scope.noToggleMeridian()) {
40731 if (angular.isDefined(minutes) && angular.isDefined(hours)) {
40732 addSecondsToSelected(12 * 60 * (selected.getHours() < 12 ? 60 : -60));
40734 $scope.meridian = $scope.meridian === meridians[0] ? meridians[1] : meridians[0];
40739 $scope.blur = function() {
40740 ngModelCtrl.$setTouched();
40743 $scope.$on('$destroy', function() {
40744 while (watchers.length) {
40745 watchers.shift()();
40750 .directive('uibTimepicker', ['uibTimepickerConfig', function(uibTimepickerConfig) {
40752 require: ['uibTimepicker', '?^ngModel'],
40753 controller: 'UibTimepickerController',
40754 controllerAs: 'timepicker',
40757 templateUrl: function(element, attrs) {
40758 return attrs.templateUrl || uibTimepickerConfig.templateUrl;
40760 link: function(scope, element, attrs, ctrls) {
40761 var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
40764 timepickerCtrl.init(ngModelCtrl, element.find('input'));
40770 angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap.position'])
40773 * A helper service that can parse typeahead's syntax (string provided by users)
40774 * Extracted to a separate service for ease of unit testing
40776 .factory('uibTypeaheadParser', ['$parse', function($parse) {
40777 // 00000111000000000000022200000000000000003333333333333330000000000044000
40778 var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
40780 parse: function(input) {
40781 var match = input.match(TYPEAHEAD_REGEXP);
40784 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
40785 ' but got "' + input + '".');
40789 itemName: match[3],
40790 source: $parse(match[4]),
40791 viewMapper: $parse(match[2] || match[1]),
40792 modelMapper: $parse(match[1])
40798 .controller('UibTypeaheadController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$$debounce', '$uibPosition', 'uibTypeaheadParser',
40799 function(originalScope, element, attrs, $compile, $parse, $q, $timeout, $document, $window, $rootScope, $$debounce, $position, typeaheadParser) {
40800 var HOT_KEYS = [9, 13, 27, 38, 40];
40801 var eventDebounceTime = 200;
40802 var modelCtrl, ngModelOptions;
40803 //SUPPORTED ATTRIBUTES (OPTIONS)
40805 //minimal no of characters that needs to be entered before typeahead kicks-in
40806 var minLength = originalScope.$eval(attrs.typeaheadMinLength);
40807 if (!minLength && minLength !== 0) {
40811 originalScope.$watch(attrs.typeaheadMinLength, function (newVal) {
40812 minLength = !newVal && newVal !== 0 ? 1 : newVal;
40815 //minimal wait time after last character typed before typeahead kicks-in
40816 var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
40818 //should it restrict model values to the ones selected from the popup only?
40819 var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
40820 originalScope.$watch(attrs.typeaheadEditable, function (newVal) {
40821 isEditable = newVal !== false;
40824 //binding to a variable that indicates if matches are being retrieved asynchronously
40825 var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
40827 //a function to determine if an event should cause selection
40828 var isSelectEvent = attrs.typeaheadShouldSelect ? $parse(attrs.typeaheadShouldSelect) : function(scope, vals) {
40829 var evt = vals.$event;
40830 return evt.which === 13 || evt.which === 9;
40833 //a callback executed when a match is selected
40834 var onSelectCallback = $parse(attrs.typeaheadOnSelect);
40836 //should it select highlighted popup value when losing focus?
40837 var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false;
40839 //binding to a variable that indicates if there were no results after the query is completed
40840 var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop;
40842 var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
40844 var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;
40846 var appendTo = attrs.typeaheadAppendTo ?
40847 originalScope.$eval(attrs.typeaheadAppendTo) : null;
40849 var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;
40851 //If input matches an item of the list exactly, select it automatically
40852 var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false;
40854 //binding to a variable that indicates if dropdown is open
40855 var isOpenSetter = $parse(attrs.typeaheadIsOpen).assign || angular.noop;
40857 var showHint = originalScope.$eval(attrs.typeaheadShowHint) || false;
40859 //INTERNAL VARIABLES
40861 //model setter executed upon match selection
40862 var parsedModel = $parse(attrs.ngModel);
40863 var invokeModelSetter = $parse(attrs.ngModel + '($$$p)');
40864 var $setModelValue = function(scope, newValue) {
40865 if (angular.isFunction(parsedModel(originalScope)) &&
40866 ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) {
40867 return invokeModelSetter(scope, {$$$p: newValue});
40870 return parsedModel.assign(scope, newValue);
40873 //expressions used by typeahead
40874 var parserResult = typeaheadParser.parse(attrs.uibTypeahead);
40878 //Used to avoid bug in iOS webview where iOS keyboard does not fire
40879 //mousedown & mouseup events
40883 //create a child scope for the typeahead directive so we are not polluting original scope
40884 //with typeahead-specific data (matches, query etc.)
40885 var scope = originalScope.$new();
40886 var offDestroy = originalScope.$on('$destroy', function() {
40889 scope.$on('$destroy', offDestroy);
40892 var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
40894 'aria-autocomplete': 'list',
40895 'aria-expanded': false,
40896 'aria-owns': popupId
40899 var inputsContainer, hintInputElem;
40900 //add read-only input to show hint
40902 inputsContainer = angular.element('<div></div>');
40903 inputsContainer.css('position', 'relative');
40904 element.after(inputsContainer);
40905 hintInputElem = element.clone();
40906 hintInputElem.attr('placeholder', '');
40907 hintInputElem.attr('tabindex', '-1');
40908 hintInputElem.val('');
40909 hintInputElem.css({
40910 'position': 'absolute',
40913 'border-color': 'transparent',
40914 'box-shadow': 'none',
40916 'background': 'none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)',
40920 'position': 'relative',
40921 'vertical-align': 'top',
40922 'background-color': 'transparent'
40924 inputsContainer.append(hintInputElem);
40925 hintInputElem.after(element);
40928 //pop-up element used to display matches
40929 var popUpEl = angular.element('<div uib-typeahead-popup></div>');
40932 matches: 'matches',
40933 active: 'activeIdx',
40934 select: 'select(activeIdx, evt)',
40935 'move-in-progress': 'moveInProgress',
40937 position: 'position',
40938 'assign-is-open': 'assignIsOpen(isOpen)',
40939 debounce: 'debounceUpdate'
40941 //custom item template
40942 if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
40943 popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
40946 if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) {
40947 popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl);
40950 var resetHint = function() {
40952 hintInputElem.val('');
40956 var resetMatches = function() {
40957 scope.matches = [];
40958 scope.activeIdx = -1;
40959 element.attr('aria-expanded', false);
40963 var getMatchId = function(index) {
40964 return popupId + '-option-' + index;
40967 // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
40968 // This attribute is added or removed automatically when the `activeIdx` changes.
40969 scope.$watch('activeIdx', function(index) {
40971 element.removeAttr('aria-activedescendant');
40973 element.attr('aria-activedescendant', getMatchId(index));
40977 var inputIsExactMatch = function(inputValue, index) {
40978 if (scope.matches.length > index && inputValue) {
40979 return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase();
40985 var getMatchesAsync = function(inputValue, evt) {
40986 var locals = {$viewValue: inputValue};
40987 isLoadingSetter(originalScope, true);
40988 isNoResultsSetter(originalScope, false);
40989 $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
40990 //it might happen that several async queries were in progress if a user were typing fast
40991 //but we are interested only in responses that correspond to the current view value
40992 var onCurrentRequest = inputValue === modelCtrl.$viewValue;
40993 if (onCurrentRequest && hasFocus) {
40994 if (matches && matches.length > 0) {
40995 scope.activeIdx = focusFirst ? 0 : -1;
40996 isNoResultsSetter(originalScope, false);
40997 scope.matches.length = 0;
41000 for (var i = 0; i < matches.length; i++) {
41001 locals[parserResult.itemName] = matches[i];
41002 scope.matches.push({
41004 label: parserResult.viewMapper(scope, locals),
41009 scope.query = inputValue;
41010 //position pop-up with matches - we need to re-calculate its position each time we are opening a window
41011 //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
41012 //due to other elements being rendered
41013 recalculatePosition();
41015 element.attr('aria-expanded', true);
41017 //Select the single remaining option if user input matches
41018 if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) {
41019 if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
41020 $$debounce(function() {
41021 scope.select(0, evt);
41022 }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
41024 scope.select(0, evt);
41029 var firstLabel = scope.matches[0].label;
41030 if (angular.isString(inputValue) &&
41031 inputValue.length > 0 &&
41032 firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) {
41033 hintInputElem.val(inputValue + firstLabel.slice(inputValue.length));
41035 hintInputElem.val('');
41040 isNoResultsSetter(originalScope, true);
41043 if (onCurrentRequest) {
41044 isLoadingSetter(originalScope, false);
41048 isLoadingSetter(originalScope, false);
41049 isNoResultsSetter(originalScope, true);
41053 // bind events only if appendToBody params exist - performance feature
41054 if (appendToBody) {
41055 angular.element($window).on('resize', fireRecalculating);
41056 $document.find('body').on('scroll', fireRecalculating);
41059 // Declare the debounced function outside recalculating for
41060 // proper debouncing
41061 var debouncedRecalculate = $$debounce(function() {
41062 // if popup is visible
41063 if (scope.matches.length) {
41064 recalculatePosition();
41067 scope.moveInProgress = false;
41068 }, eventDebounceTime);
41070 // Default progress type
41071 scope.moveInProgress = false;
41073 function fireRecalculating() {
41074 if (!scope.moveInProgress) {
41075 scope.moveInProgress = true;
41079 debouncedRecalculate();
41082 // recalculate actual position and set new values to scope
41083 // after digest loop is popup in right position
41084 function recalculatePosition() {
41085 scope.position = appendToBody ? $position.offset(element) : $position.position(element);
41086 scope.position.top += element.prop('offsetHeight');
41089 //we need to propagate user's query so we can higlight matches
41090 scope.query = undefined;
41092 //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
41093 var timeoutPromise;
41095 var scheduleSearchWithTimeout = function(inputValue) {
41096 timeoutPromise = $timeout(function() {
41097 getMatchesAsync(inputValue);
41101 var cancelPreviousTimeout = function() {
41102 if (timeoutPromise) {
41103 $timeout.cancel(timeoutPromise);
41109 scope.assignIsOpen = function (isOpen) {
41110 isOpenSetter(originalScope, isOpen);
41113 scope.select = function(activeIdx, evt) {
41114 //called from within the $digest() cycle
41119 locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
41120 model = parserResult.modelMapper(originalScope, locals);
41121 $setModelValue(originalScope, model);
41122 modelCtrl.$setValidity('editable', true);
41123 modelCtrl.$setValidity('parse', true);
41125 onSelectCallback(originalScope, {
41128 $label: parserResult.viewMapper(originalScope, locals),
41134 //return focus to the input element if a match was selected via a mouse click event
41135 // use timeout to avoid $rootScope:inprog error
41136 if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) {
41137 $timeout(function() { element[0].focus(); }, 0, false);
41141 //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
41142 element.on('keydown', function(evt) {
41143 //typeahead is open and an "interesting" key was pressed
41144 if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
41148 var shouldSelect = isSelectEvent(originalScope, {$event: evt});
41151 * if there's nothing selected (i.e. focusFirst) and enter or tab is hit
41153 * shift + tab is pressed to bring focus to the previous element
41154 * then clear the results
41156 if (scope.activeIdx === -1 && shouldSelect || evt.which === 9 && !!evt.shiftKey) {
41162 evt.preventDefault();
41164 switch (evt.which) {
41166 evt.stopPropagation();
41169 originalScope.$digest();
41171 case 38: // up arrow
41172 scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
41174 target = popUpEl.find('li')[scope.activeIdx];
41175 target.parentNode.scrollTop = target.offsetTop;
41177 case 40: // down arrow
41178 scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
41180 target = popUpEl.find('li')[scope.activeIdx];
41181 target.parentNode.scrollTop = target.offsetTop;
41184 if (shouldSelect) {
41185 scope.$apply(function() {
41186 if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
41187 $$debounce(function() {
41188 scope.select(scope.activeIdx, evt);
41189 }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
41191 scope.select(scope.activeIdx, evt);
41198 element.bind('focus', function (evt) {
41200 if (minLength === 0 && !modelCtrl.$viewValue) {
41201 $timeout(function() {
41202 getMatchesAsync(modelCtrl.$viewValue, evt);
41207 element.bind('blur', function(evt) {
41208 if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) {
41210 scope.$apply(function() {
41211 if (angular.isObject(scope.debounceUpdate) && angular.isNumber(scope.debounceUpdate.blur)) {
41212 $$debounce(function() {
41213 scope.select(scope.activeIdx, evt);
41214 }, scope.debounceUpdate.blur);
41216 scope.select(scope.activeIdx, evt);
41220 if (!isEditable && modelCtrl.$error.editable) {
41221 modelCtrl.$setViewValue();
41222 // Reset validity as we are clearing
41223 modelCtrl.$setValidity('editable', true);
41224 modelCtrl.$setValidity('parse', true);
41231 // Keep reference to click handler to unbind it.
41232 var dismissClickHandler = function(evt) {
41234 // Firefox treats right click as a click on document
41235 if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) {
41237 if (!$rootScope.$$phase) {
41238 originalScope.$digest();
41243 $document.on('click', dismissClickHandler);
41245 originalScope.$on('$destroy', function() {
41246 $document.off('click', dismissClickHandler);
41247 if (appendToBody || appendTo) {
41251 if (appendToBody) {
41252 angular.element($window).off('resize', fireRecalculating);
41253 $document.find('body').off('scroll', fireRecalculating);
41255 // Prevent jQuery cache memory leak
41259 inputsContainer.remove();
41263 var $popup = $compile(popUpEl)(scope);
41265 if (appendToBody) {
41266 $document.find('body').append($popup);
41267 } else if (appendTo) {
41268 angular.element(appendTo).eq(0).append($popup);
41270 element.after($popup);
41273 this.init = function(_modelCtrl, _ngModelOptions) {
41274 modelCtrl = _modelCtrl;
41275 ngModelOptions = _ngModelOptions;
41277 scope.debounceUpdate = modelCtrl.$options && $parse(modelCtrl.$options.debounce)(originalScope);
41279 //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
41280 //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
41281 modelCtrl.$parsers.unshift(function(inputValue) {
41284 if (minLength === 0 || inputValue && inputValue.length >= minLength) {
41285 if (waitTime > 0) {
41286 cancelPreviousTimeout();
41287 scheduleSearchWithTimeout(inputValue);
41289 getMatchesAsync(inputValue);
41292 isLoadingSetter(originalScope, false);
41293 cancelPreviousTimeout();
41302 // Reset in case user had typed something previously.
41303 modelCtrl.$setValidity('editable', true);
41307 modelCtrl.$setValidity('editable', false);
41311 modelCtrl.$formatters.push(function(modelValue) {
41312 var candidateViewValue, emptyViewValue;
41315 // The validity may be set to false via $parsers (see above) if
41316 // the model is restricted to selected values. If the model
41317 // is set manually it is considered to be valid.
41319 modelCtrl.$setValidity('editable', true);
41322 if (inputFormatter) {
41323 locals.$model = modelValue;
41324 return inputFormatter(originalScope, locals);
41327 //it might happen that we don't have enough info to properly render input value
41328 //we need to check for this situation and simply return model value if we can't apply custom formatting
41329 locals[parserResult.itemName] = modelValue;
41330 candidateViewValue = parserResult.viewMapper(originalScope, locals);
41331 locals[parserResult.itemName] = undefined;
41332 emptyViewValue = parserResult.viewMapper(originalScope, locals);
41334 return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;
41339 .directive('uibTypeahead', function() {
41341 controller: 'UibTypeaheadController',
41342 require: ['ngModel', '^?ngModelOptions', 'uibTypeahead'],
41343 link: function(originalScope, element, attrs, ctrls) {
41344 ctrls[2].init(ctrls[0], ctrls[1]);
41349 .directive('uibTypeaheadPopup', ['$$debounce', function($$debounce) {
41356 moveInProgress: '=',
41362 templateUrl: function(element, attrs) {
41363 return attrs.popupTemplateUrl || 'uib/template/typeahead/typeahead-popup.html';
41365 link: function(scope, element, attrs) {
41366 scope.templateUrl = attrs.templateUrl;
41368 scope.isOpen = function() {
41369 var isDropdownOpen = scope.matches.length > 0;
41370 scope.assignIsOpen({ isOpen: isDropdownOpen });
41371 return isDropdownOpen;
41374 scope.isActive = function(matchIdx) {
41375 return scope.active === matchIdx;
41378 scope.selectActive = function(matchIdx) {
41379 scope.active = matchIdx;
41382 scope.selectMatch = function(activeIdx, evt) {
41383 var debounce = scope.debounce();
41384 if (angular.isNumber(debounce) || angular.isObject(debounce)) {
41385 $$debounce(function() {
41386 scope.select({activeIdx: activeIdx, evt: evt});
41387 }, angular.isNumber(debounce) ? debounce : debounce['default']);
41389 scope.select({activeIdx: activeIdx, evt: evt});
41396 .directive('uibTypeaheadMatch', ['$templateRequest', '$compile', '$parse', function($templateRequest, $compile, $parse) {
41403 link: function(scope, element, attrs) {
41404 var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'uib/template/typeahead/typeahead-match.html';
41405 $templateRequest(tplUrl).then(function(tplContent) {
41406 var tplEl = angular.element(tplContent.trim());
41407 element.replaceWith(tplEl);
41408 $compile(tplEl)(scope);
41414 .filter('uibTypeaheadHighlight', ['$sce', '$injector', '$log', function($sce, $injector, $log) {
41415 var isSanitizePresent;
41416 isSanitizePresent = $injector.has('$sanitize');
41418 function escapeRegexp(queryToEscape) {
41419 // Regex: capture the whole query string and replace it with the string that will be used to match
41420 // the results, for example if the capture is "a" the result will be \a
41421 return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
41424 function containsHtml(matchItem) {
41425 return /<.*>/g.test(matchItem);
41428 return function(matchItem, query) {
41429 if (!isSanitizePresent && containsHtml(matchItem)) {
41430 $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger
41432 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
41433 if (!isSanitizePresent) {
41434 matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive
41440 angular.module("uib/template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) {
41441 $templateCache.put("uib/template/accordion/accordion-group.html",
41442 "<div class=\"panel\" ng-class=\"panelClass || 'panel-default'\">\n" +
41443 " <div role=\"tab\" id=\"{{::headingId}}\" aria-selected=\"{{isOpen}}\" class=\"panel-heading\" ng-keypress=\"toggleOpen($event)\">\n" +
41444 " <h4 class=\"panel-title\">\n" +
41445 " <a role=\"button\" data-toggle=\"collapse\" href aria-expanded=\"{{isOpen}}\" aria-controls=\"{{::panelId}}\" tabindex=\"0\" class=\"accordion-toggle\" ng-click=\"toggleOpen()\" uib-accordion-transclude=\"heading\"><span uib-accordion-header ng-class=\"{'text-muted': isDisabled}\">{{heading}}</span></a>\n" +
41448 " <div id=\"{{::panelId}}\" aria-labelledby=\"{{::headingId}}\" aria-hidden=\"{{!isOpen}}\" role=\"tabpanel\" class=\"panel-collapse collapse\" uib-collapse=\"!isOpen\">\n" +
41449 " <div class=\"panel-body\" ng-transclude></div>\n" +
41455 angular.module("uib/template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) {
41456 $templateCache.put("uib/template/accordion/accordion.html",
41457 "<div role=\"tablist\" class=\"panel-group\" ng-transclude></div>");
41460 angular.module("uib/template/alert/alert.html", []).run(["$templateCache", function($templateCache) {
41461 $templateCache.put("uib/template/alert/alert.html",
41462 "<div class=\"alert\" ng-class=\"['alert-' + (type || 'warning'), closeable ? 'alert-dismissible' : null]\" role=\"alert\">\n" +
41463 " <button ng-show=\"closeable\" type=\"button\" class=\"close\" ng-click=\"close({$event: $event})\">\n" +
41464 " <span aria-hidden=\"true\">×</span>\n" +
41465 " <span class=\"sr-only\">Close</span>\n" +
41467 " <div ng-transclude></div>\n" +
41472 angular.module("uib/template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) {
41473 $templateCache.put("uib/template/carousel/carousel.html",
41474 "<div ng-mouseenter=\"pause()\" ng-mouseleave=\"play()\" class=\"carousel\" ng-swipe-right=\"prev()\" ng-swipe-left=\"next()\">\n" +
41475 " <div class=\"carousel-inner\" ng-transclude></div>\n" +
41476 " <a role=\"button\" href class=\"left carousel-control\" ng-click=\"prev()\" ng-class=\"{ disabled: isPrevDisabled() }\" ng-show=\"slides.length > 1\">\n" +
41477 " <span aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-left\"></span>\n" +
41478 " <span class=\"sr-only\">previous</span>\n" +
41480 " <a role=\"button\" href class=\"right carousel-control\" ng-click=\"next()\" ng-class=\"{ disabled: isNextDisabled() }\" ng-show=\"slides.length > 1\">\n" +
41481 " <span aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-right\"></span>\n" +
41482 " <span class=\"sr-only\">next</span>\n" +
41484 " <ol class=\"carousel-indicators\" ng-show=\"slides.length > 1\">\n" +
41485 " <li ng-repeat=\"slide in slides | orderBy:indexOfSlide track by $index\" ng-class=\"{ active: isActive(slide) }\" ng-click=\"select(slide)\">\n" +
41486 " <span class=\"sr-only\">slide {{ $index + 1 }} of {{ slides.length }}<span ng-if=\"isActive(slide)\">, currently active</span></span>\n" +
41493 angular.module("uib/template/carousel/slide.html", []).run(["$templateCache", function($templateCache) {
41494 $templateCache.put("uib/template/carousel/slide.html",
41495 "<div ng-class=\"{\n" +
41496 " 'active': active\n" +
41497 " }\" class=\"item text-center\" ng-transclude></div>\n" +
41501 angular.module("uib/template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) {
41502 $templateCache.put("uib/template/datepicker/datepicker.html",
41503 "<div class=\"uib-datepicker\" ng-switch=\"datepickerMode\" role=\"application\" ng-keydown=\"keydown($event)\">\n" +
41504 " <uib-daypicker ng-switch-when=\"day\" tabindex=\"0\"></uib-daypicker>\n" +
41505 " <uib-monthpicker ng-switch-when=\"month\" tabindex=\"0\"></uib-monthpicker>\n" +
41506 " <uib-yearpicker ng-switch-when=\"year\" tabindex=\"0\"></uib-yearpicker>\n" +
41511 angular.module("uib/template/datepicker/day.html", []).run(["$templateCache", function($templateCache) {
41512 $templateCache.put("uib/template/datepicker/day.html",
41513 "<table class=\"uib-daypicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
41516 " <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" +
41517 " <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\"><strong>{{title}}</strong></button></th>\n" +
41518 " <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" +
41521 " <th ng-if=\"showWeeks\" class=\"text-center\"></th>\n" +
41522 " <th ng-repeat=\"label in ::labels track by $index\" class=\"text-center\"><small aria-label=\"{{::label.full}}\">{{::label.abbr}}</small></th>\n" +
41526 " <tr class=\"uib-weeks\" ng-repeat=\"row in rows track by $index\">\n" +
41527 " <td ng-if=\"showWeeks\" class=\"text-center h6\"><em>{{ weekNumbers[$index] }}</em></td>\n" +
41528 " <td ng-repeat=\"dt in row\" class=\"uib-day text-center\" role=\"gridcell\"\n" +
41529 " id=\"{{::dt.uid}}\"\n" +
41530 " ng-class=\"::dt.customClass\">\n" +
41531 " <button type=\"button\" class=\"btn btn-default btn-sm\"\n" +
41532 " uib-is-class=\"\n" +
41533 " 'btn-info' for selectedDt,\n" +
41534 " 'active' for activeDt\n" +
41536 " ng-click=\"select(dt.date)\"\n" +
41537 " ng-disabled=\"::dt.disabled\"\n" +
41538 " tabindex=\"-1\"><span ng-class=\"::{'text-muted': dt.secondary, 'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
41546 angular.module("uib/template/datepicker/month.html", []).run(["$templateCache", function($templateCache) {
41547 $templateCache.put("uib/template/datepicker/month.html",
41548 "<table class=\"uib-monthpicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
41551 " <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" +
41552 " <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\"><strong>{{title}}</strong></button></th>\n" +
41553 " <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" +
41557 " <tr class=\"uib-months\" ng-repeat=\"row in rows track by $index\">\n" +
41558 " <td ng-repeat=\"dt in row\" class=\"uib-month text-center\" role=\"gridcell\"\n" +
41559 " id=\"{{::dt.uid}}\"\n" +
41560 " ng-class=\"::dt.customClass\">\n" +
41561 " <button type=\"button\" class=\"btn btn-default\"\n" +
41562 " uib-is-class=\"\n" +
41563 " 'btn-info' for selectedDt,\n" +
41564 " 'active' for activeDt\n" +
41566 " ng-click=\"select(dt.date)\"\n" +
41567 " ng-disabled=\"::dt.disabled\"\n" +
41568 " tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
41576 angular.module("uib/template/datepicker/year.html", []).run(["$templateCache", function($templateCache) {
41577 $templateCache.put("uib/template/datepicker/year.html",
41578 "<table class=\"uib-yearpicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
41581 " <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" +
41582 " <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\"><strong>{{title}}</strong></button></th>\n" +
41583 " <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" +
41587 " <tr class=\"uib-years\" ng-repeat=\"row in rows track by $index\">\n" +
41588 " <td ng-repeat=\"dt in row\" class=\"uib-year text-center\" role=\"gridcell\"\n" +
41589 " id=\"{{::dt.uid}}\"\n" +
41590 " ng-class=\"::dt.customClass\">\n" +
41591 " <button type=\"button\" class=\"btn btn-default\"\n" +
41592 " uib-is-class=\"\n" +
41593 " 'btn-info' for selectedDt,\n" +
41594 " 'active' for activeDt\n" +
41596 " ng-click=\"select(dt.date)\"\n" +
41597 " ng-disabled=\"::dt.disabled\"\n" +
41598 " tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
41606 angular.module("uib/template/datepickerPopup/popup.html", []).run(["$templateCache", function($templateCache) {
41607 $templateCache.put("uib/template/datepickerPopup/popup.html",
41609 " <ul class=\"uib-datepicker-popup dropdown-menu uib-position-measure\" dropdown-nested ng-if=\"isOpen\" ng-keydown=\"keydown($event)\" ng-click=\"$event.stopPropagation()\">\n" +
41610 " <li ng-transclude></li>\n" +
41611 " <li ng-if=\"showButtonBar\" class=\"uib-button-bar\">\n" +
41612 " <span class=\"btn-group pull-left\">\n" +
41613 " <button type=\"button\" class=\"btn btn-sm btn-info uib-datepicker-current\" ng-click=\"select('today', $event)\" ng-disabled=\"isDisabled('today')\">{{ getText('current') }}</button>\n" +
41614 " <button type=\"button\" class=\"btn btn-sm btn-danger uib-clear\" ng-click=\"select(null, $event)\">{{ getText('clear') }}</button>\n" +
41616 " <button type=\"button\" class=\"btn btn-sm btn-success pull-right uib-close\" ng-click=\"close($event)\">{{ getText('close') }}</button>\n" +
41623 angular.module("uib/template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) {
41624 $templateCache.put("uib/template/modal/backdrop.html",
41625 "<div class=\"modal-backdrop\"\n" +
41626 " uib-modal-animation-class=\"fade\"\n" +
41627 " modal-in-class=\"in\"\n" +
41628 " ng-style=\"{'z-index': 1040 + (index && 1 || 0) + index*10}\"\n" +
41633 angular.module("uib/template/modal/window.html", []).run(["$templateCache", function($templateCache) {
41634 $templateCache.put("uib/template/modal/window.html",
41635 "<div modal-render=\"{{$isRendered}}\" tabindex=\"-1\" role=\"dialog\" class=\"modal\"\n" +
41636 " uib-modal-animation-class=\"fade\"\n" +
41637 " modal-in-class=\"in\"\n" +
41638 " ng-style=\"{'z-index': 1050 + index*10, display: 'block'}\">\n" +
41639 " <div class=\"modal-dialog {{size ? 'modal-' + size : ''}}\"><div class=\"modal-content\" uib-modal-transclude></div></div>\n" +
41644 angular.module("uib/template/pager/pager.html", []).run(["$templateCache", function($templateCache) {
41645 $templateCache.put("uib/template/pager/pager.html",
41646 "<ul class=\"pager\">\n" +
41647 " <li ng-class=\"{disabled: noPrevious()||ngDisabled, previous: align}\"><a href ng-click=\"selectPage(page - 1, $event)\">{{::getText('previous')}}</a></li>\n" +
41648 " <li ng-class=\"{disabled: noNext()||ngDisabled, next: align}\"><a href ng-click=\"selectPage(page + 1, $event)\">{{::getText('next')}}</a></li>\n" +
41653 angular.module("uib/template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) {
41654 $templateCache.put("uib/template/pagination/pagination.html",
41655 "<ul class=\"pagination\">\n" +
41656 " <li ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noPrevious()||ngDisabled}\" class=\"pagination-first\"><a href ng-click=\"selectPage(1, $event)\">{{::getText('first')}}</a></li>\n" +
41657 " <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" +
41658 " <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" +
41659 " <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" +
41660 " <li ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noNext()||ngDisabled}\" class=\"pagination-last\"><a href ng-click=\"selectPage(totalPages, $event)\">{{::getText('last')}}</a></li>\n" +
41665 angular.module("uib/template/tooltip/tooltip-html-popup.html", []).run(["$templateCache", function($templateCache) {
41666 $templateCache.put("uib/template/tooltip/tooltip-html-popup.html",
41667 "<div class=\"tooltip\"\n" +
41668 " tooltip-animation-class=\"fade\"\n" +
41669 " uib-tooltip-classes\n" +
41670 " ng-class=\"{ in: isOpen() }\">\n" +
41671 " <div class=\"tooltip-arrow\"></div>\n" +
41672 " <div class=\"tooltip-inner\" ng-bind-html=\"contentExp()\"></div>\n" +
41677 angular.module("uib/template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) {
41678 $templateCache.put("uib/template/tooltip/tooltip-popup.html",
41679 "<div class=\"tooltip\"\n" +
41680 " tooltip-animation-class=\"fade\"\n" +
41681 " uib-tooltip-classes\n" +
41682 " ng-class=\"{ in: isOpen() }\">\n" +
41683 " <div class=\"tooltip-arrow\"></div>\n" +
41684 " <div class=\"tooltip-inner\" ng-bind=\"content\"></div>\n" +
41689 angular.module("uib/template/tooltip/tooltip-template-popup.html", []).run(["$templateCache", function($templateCache) {
41690 $templateCache.put("uib/template/tooltip/tooltip-template-popup.html",
41691 "<div class=\"tooltip\"\n" +
41692 " tooltip-animation-class=\"fade\"\n" +
41693 " uib-tooltip-classes\n" +
41694 " ng-class=\"{ in: isOpen() }\">\n" +
41695 " <div class=\"tooltip-arrow\"></div>\n" +
41696 " <div class=\"tooltip-inner\"\n" +
41697 " uib-tooltip-template-transclude=\"contentExp()\"\n" +
41698 " tooltip-template-transclude-scope=\"originScope()\"></div>\n" +
41703 angular.module("uib/template/popover/popover-html.html", []).run(["$templateCache", function($templateCache) {
41704 $templateCache.put("uib/template/popover/popover-html.html",
41705 "<div class=\"popover\"\n" +
41706 " tooltip-animation-class=\"fade\"\n" +
41707 " uib-tooltip-classes\n" +
41708 " ng-class=\"{ in: isOpen() }\">\n" +
41709 " <div class=\"arrow\"></div>\n" +
41711 " <div class=\"popover-inner\">\n" +
41712 " <h3 class=\"popover-title\" ng-bind=\"uibTitle\" ng-if=\"uibTitle\"></h3>\n" +
41713 " <div class=\"popover-content\" ng-bind-html=\"contentExp()\"></div>\n" +
41719 angular.module("uib/template/popover/popover-template.html", []).run(["$templateCache", function($templateCache) {
41720 $templateCache.put("uib/template/popover/popover-template.html",
41721 "<div class=\"popover\"\n" +
41722 " tooltip-animation-class=\"fade\"\n" +
41723 " uib-tooltip-classes\n" +
41724 " ng-class=\"{ in: isOpen() }\">\n" +
41725 " <div class=\"arrow\"></div>\n" +
41727 " <div class=\"popover-inner\">\n" +
41728 " <h3 class=\"popover-title\" ng-bind=\"uibTitle\" ng-if=\"uibTitle\"></h3>\n" +
41729 " <div class=\"popover-content\"\n" +
41730 " uib-tooltip-template-transclude=\"contentExp()\"\n" +
41731 " tooltip-template-transclude-scope=\"originScope()\"></div>\n" +
41737 angular.module("uib/template/popover/popover.html", []).run(["$templateCache", function($templateCache) {
41738 $templateCache.put("uib/template/popover/popover.html",
41739 "<div class=\"popover\"\n" +
41740 " tooltip-animation-class=\"fade\"\n" +
41741 " uib-tooltip-classes\n" +
41742 " ng-class=\"{ in: isOpen() }\">\n" +
41743 " <div class=\"arrow\"></div>\n" +
41745 " <div class=\"popover-inner\">\n" +
41746 " <h3 class=\"popover-title\" ng-bind=\"uibTitle\" ng-if=\"uibTitle\"></h3>\n" +
41747 " <div class=\"popover-content\" ng-bind=\"content\"></div>\n" +
41753 angular.module("uib/template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) {
41754 $templateCache.put("uib/template/progressbar/bar.html",
41755 "<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" +
41759 angular.module("uib/template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) {
41760 $templateCache.put("uib/template/progressbar/progress.html",
41761 "<div class=\"progress\" ng-transclude aria-labelledby=\"{{::title}}\"></div>");
41764 angular.module("uib/template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) {
41765 $templateCache.put("uib/template/progressbar/progressbar.html",
41766 "<div class=\"progress\">\n" +
41767 " <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" +
41772 angular.module("uib/template/rating/rating.html", []).run(["$templateCache", function($templateCache) {
41773 $templateCache.put("uib/template/rating/rating.html",
41774 "<span ng-mouseleave=\"reset()\" ng-keydown=\"onKeydown($event)\" tabindex=\"0\" role=\"slider\" aria-valuemin=\"0\" aria-valuemax=\"{{range.length}}\" aria-valuenow=\"{{value}}\" aria-valuetext=\"{{title}}\">\n" +
41775 " <span ng-repeat-start=\"r in range track by $index\" class=\"sr-only\">({{ $index < value ? '*' : ' ' }})</span>\n" +
41776 " <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}}\"></i>\n" +
41781 angular.module("uib/template/tabs/tab.html", []).run(["$templateCache", function($templateCache) {
41782 $templateCache.put("uib/template/tabs/tab.html",
41783 "<li ng-class=\"[{active: active, disabled: disabled}, classes]\" class=\"uib-tab nav-item\">\n" +
41784 " <a href ng-click=\"select($event)\" class=\"nav-link\" uib-tab-heading-transclude>{{heading}}</a>\n" +
41789 angular.module("uib/template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) {
41790 $templateCache.put("uib/template/tabs/tabset.html",
41792 " <ul class=\"nav nav-{{tabset.type || 'tabs'}}\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" +
41793 " <div class=\"tab-content\">\n" +
41794 " <div class=\"tab-pane\"\n" +
41795 " ng-repeat=\"tab in tabset.tabs\"\n" +
41796 " ng-class=\"{active: tabset.active === tab.index}\"\n" +
41797 " uib-tab-content-transclude=\"tab\">\n" +
41804 angular.module("uib/template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) {
41805 $templateCache.put("uib/template/timepicker/timepicker.html",
41806 "<table class=\"uib-timepicker\">\n" +
41808 " <tr class=\"text-center\" ng-show=\"::showSpinners\">\n" +
41809 " <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" +
41810 " <td> </td>\n" +
41811 " <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" +
41812 " <td ng-show=\"showSeconds\"> </td>\n" +
41813 " <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" +
41814 " <td ng-show=\"showMeridian\"></td>\n" +
41817 " <td class=\"form-group uib-time hours\" ng-class=\"{'has-error': invalidHours}\">\n" +
41818 " <input type=\"text\" placeholder=\"HH\" ng-model=\"hours\" ng-change=\"updateHours()\" class=\"form-control text-center\" ng-readonly=\"::readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\" ng-disabled=\"noIncrementHours()\" ng-blur=\"blur()\">\n" +
41820 " <td class=\"uib-separator\">:</td>\n" +
41821 " <td class=\"form-group uib-time minutes\" ng-class=\"{'has-error': invalidMinutes}\">\n" +
41822 " <input type=\"text\" placeholder=\"MM\" ng-model=\"minutes\" ng-change=\"updateMinutes()\" class=\"form-control text-center\" ng-readonly=\"::readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\" ng-disabled=\"noIncrementMinutes()\" ng-blur=\"blur()\">\n" +
41824 " <td ng-show=\"showSeconds\" class=\"uib-separator\">:</td>\n" +
41825 " <td class=\"form-group uib-time seconds\" ng-class=\"{'has-error': invalidSeconds}\" ng-show=\"showSeconds\">\n" +
41826 " <input type=\"text\" placeholder=\"SS\" ng-model=\"seconds\" ng-change=\"updateSeconds()\" class=\"form-control text-center\" ng-readonly=\"readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\" ng-disabled=\"noIncrementSeconds()\" ng-blur=\"blur()\">\n" +
41828 " <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" +
41830 " <tr class=\"text-center\" ng-show=\"::showSpinners\">\n" +
41831 " <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" +
41832 " <td> </td>\n" +
41833 " <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" +
41834 " <td ng-show=\"showSeconds\"> </td>\n" +
41835 " <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" +
41836 " <td ng-show=\"showMeridian\"></td>\n" +
41843 angular.module("uib/template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) {
41844 $templateCache.put("uib/template/typeahead/typeahead-match.html",
41846 " tabindex=\"-1\"\n" +
41847 " ng-bind-html=\"match.label | uibTypeaheadHighlight:query\"\n" +
41848 " ng-attr-title=\"{{match.label}}\"></a>\n" +
41852 angular.module("uib/template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) {
41853 $templateCache.put("uib/template/typeahead/typeahead-popup.html",
41854 "<ul class=\"dropdown-menu\" ng-show=\"isOpen() && !moveInProgress\" ng-style=\"{top: position().top+'px', left: position().left+'px'}\" role=\"listbox\" aria-hidden=\"{{!isOpen()}}\">\n" +
41855 " <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" +
41856 " <div uib-typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>\n" +
41861 angular.module('ui.bootstrap.carousel').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibCarouselCss && 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>'); angular.$$uibCarouselCss = true; });
41862 angular.module('ui.bootstrap.datepicker').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibDatepickerCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-datepicker .uib-title{width:100%;}.uib-day button,.uib-month button,.uib-year button{min-width:100%;}.uib-left,.uib-right{width:100%}</style>'); angular.$$uibDatepickerCss = true; });
41863 angular.module('ui.bootstrap.position').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibPositionCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-position-measure{display:block !important;visibility:hidden !important;position:absolute !important;top:-9999px !important;left:-9999px !important;}.uib-position-scrollbar-measure{position:absolute !important;top:-9999px !important;width:50px !important;height:50px !important;overflow:scroll !important;}.uib-position-body-scrollbar-measure{overflow:scroll !important;}</style>'); angular.$$uibPositionCss = true; });
41864 angular.module('ui.bootstrap.datepickerPopup').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibDatepickerpopupCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-datepicker-popup.dropdown-menu{display:block;float:none;margin:0;}.uib-button-bar{padding:10px 9px 2px;}</style>'); angular.$$uibDatepickerpopupCss = true; });
41865 angular.module('ui.bootstrap.tooltip').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTooltipCss && angular.element(document).find('head').prepend('<style type="text/css">[uib-tooltip-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-popup].tooltip.right-bottom > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.right-bottom > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.right-bottom > .tooltip-arrow,[uib-popover-popup].popover.top-left > .arrow,[uib-popover-popup].popover.top-right > .arrow,[uib-popover-popup].popover.bottom-left > .arrow,[uib-popover-popup].popover.bottom-right > .arrow,[uib-popover-popup].popover.left-top > .arrow,[uib-popover-popup].popover.left-bottom > .arrow,[uib-popover-popup].popover.right-top > .arrow,[uib-popover-popup].popover.right-bottom > .arrow,[uib-popover-html-popup].popover.top-left > .arrow,[uib-popover-html-popup].popover.top-right > .arrow,[uib-popover-html-popup].popover.bottom-left > .arrow,[uib-popover-html-popup].popover.bottom-right > .arrow,[uib-popover-html-popup].popover.left-top > .arrow,[uib-popover-html-popup].popover.left-bottom > .arrow,[uib-popover-html-popup].popover.right-top > .arrow,[uib-popover-html-popup].popover.right-bottom > .arrow,[uib-popover-template-popup].popover.top-left > .arrow,[uib-popover-template-popup].popover.top-right > .arrow,[uib-popover-template-popup].popover.bottom-left > .arrow,[uib-popover-template-popup].popover.bottom-right > .arrow,[uib-popover-template-popup].popover.left-top > .arrow,[uib-popover-template-popup].popover.left-bottom > .arrow,[uib-popover-template-popup].popover.right-top > .arrow,[uib-popover-template-popup].popover.right-bottom > .arrow{top:auto;bottom:auto;left:auto;right:auto;margin:0;}[uib-popover-popup].popover,[uib-popover-html-popup].popover,[uib-popover-template-popup].popover{display:block !important;}</style>'); angular.$$uibTooltipCss = true; });
41866 angular.module('ui.bootstrap.timepicker').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTimepickerCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-time input{width:50px;}</style>'); angular.$$uibTimepickerCss = true; });
41867 angular.module('ui.bootstrap.typeahead').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTypeaheadCss && angular.element(document).find('head').prepend('<style type="text/css">[uib-typeahead-popup].dropdown-menu{display:block;}</style>'); angular.$$uibTypeaheadCss = true; });
41871 /***/ function(module, exports) {
41876 (function (declares) {
41877 var CommandInfo = (function () {
41878 function CommandInfo(name) {
41881 return CommandInfo;
41883 declares.CommandInfo = CommandInfo;
41884 })(declares = app.declares || (app.declares = {}));
41885 })(app || (app = {}));
41889 (function (services) {
41890 var APIEndPoint = (function () {
41891 function APIEndPoint($resource, $http) {
41892 this.$resource = $resource;
41893 this.$http = $http;
41895 APIEndPoint.prototype.resource = function (endPoint, data) {
41896 var customAction = {
41902 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
41904 return this.$resource(endPoint, {}, { execute: execute });
41906 APIEndPoint.prototype.getOptionControlFile = function (command) {
41907 var endPoint = '/api/v1/optionControlFile/' + command;
41908 return this.resource(endPoint, {}).get();
41910 APIEndPoint.prototype.getFiles = function (fileId) {
41911 var endPoint = '/api/v1/workspace';
41913 endPoint += '/' + fileId;
41915 return this.resource(endPoint, {}).get();
41917 APIEndPoint.prototype.getDirectories = function () {
41918 var endPoint = '/api/v1/all/workspace/directory';
41919 return this.resource(endPoint, {}).get();
41921 APIEndPoint.prototype.getTags = function () {
41922 var endPoint = '/api/v1/tagList';
41923 return this.resource(endPoint, {}).get();
41925 APIEndPoint.prototype.getCommands = function () {
41926 var endPoint = '/api/v1/commandList';
41927 return this.resource(endPoint, {}).get();
41929 APIEndPoint.prototype.execute = function (data) {
41930 var endPoint = '/api/v1/execution';
41931 var fd = new FormData();
41932 fd.append('data', data);
41933 return this.$http.post(endPoint, fd, {
41934 headers: { 'Content-Type': undefined },
41935 transformRequest: angular.identity
41938 APIEndPoint.prototype.debug = function () {
41939 var endPoint = '/api/v1/debug';
41940 return this.$http.get(endPoint);
41942 APIEndPoint.prototype.upload = function () {
41943 var endPoint = '/api/v1/upload';
41944 return this.$http.get(endPoint);
41946 APIEndPoint.prototype.help = function (command) {
41947 var endPoint = '/api/v1/help/' + command;
41948 return this.$http.get(endPoint);
41950 return APIEndPoint;
41952 services.APIEndPoint = APIEndPoint;
41953 })(services = app.services || (app.services = {}));
41954 })(app || (app = {}));
41958 (function (services) {
41959 var MyModal = (function () {
41960 function MyModal($uibModal) {
41961 this.$uibModal = $uibModal;
41962 this.modalOption = {
41969 MyModal.prototype.open = function (modalName) {
41970 if (modalName === 'SelectCommand') {
41971 this.modalOption.templateUrl = 'templates/select-command.html';
41972 this.modalOption.size = 'lg';
41974 return this.$uibModal.open(this.modalOption);
41976 MyModal.prototype.selectCommand = function () {
41977 this.modalOption.templateUrl = 'templates/select-command.html';
41978 this.modalOption.controller = 'selectCommandController';
41979 this.modalOption.controllerAs = 'c';
41980 this.modalOption.size = 'lg';
41981 return this.$uibModal.open(this.modalOption);
41983 MyModal.prototype.preview = function () {
41984 this.modalOption.templateUrl = 'templates/preview.html';
41985 this.modalOption.controller = 'previewController';
41986 this.modalOption.controllerAs = 'c';
41987 this.modalOption.size = 'lg';
41988 return this.$uibModal.open(this.modalOption);
41990 MyModal.prototype.upload = function () {
41991 this.modalOption.templateUrl = 'templates/upload.html';
41992 this.modalOption.controller = 'uploadController';
41993 this.modalOption.controllerAs = 'c';
41994 this.modalOption.size = 'lg';
41995 return this.$uibModal.open(this.modalOption);
41997 MyModal.$inject = ['$uibModal'];
42000 services.MyModal = MyModal;
42001 })(services = app.services || (app.services = {}));
42002 })(app || (app = {}));
42006 (function (services) {
42007 var WebSocket = (function () {
42008 function WebSocket($rootScope) {
42009 this.$rootScope = $rootScope;
42010 this.socket = io.connect();
42012 WebSocket.prototype.on = function (eventName, callback) {
42013 var socket = this.socket;
42014 var rootScope = this.$rootScope;
42015 socket.on(eventName, function () {
42016 var args = arguments;
42017 rootScope.$apply(function () {
42018 callback.apply(socket, args);
42022 WebSocket.prototype.emit = function (eventName, data, callback) {
42023 var socket = this.socket;
42024 var rootScope = this.$rootScope;
42025 this.socket.emit(eventName, data, function () {
42026 var args = arguments;
42027 rootScope.$apply(function () {
42029 callback.apply(socket, args);
42035 services.WebSocket = WebSocket;
42036 })(services = app.services || (app.services = {}));
42037 })(app || (app = {}));
42041 (function (services) {
42042 var Console = (function () {
42043 function Console(WebSocket, $rootScope) {
42044 this.WebSocket = WebSocket;
42045 this.$rootScope = $rootScope;
42046 this.WebSocket = WebSocket;
42047 this.$rootScope = $rootScope;
42048 this.directiveIDs = [];
42049 var directiveIDs = this.directiveIDs;
42050 this.WebSocket.on('console', function (d) {
42052 var message = d.message;
42053 if (directiveIDs.indexOf(id) > -1) {
42054 $rootScope.$emit(id, message);
42058 Console.prototype.addDirective = function (id) {
42059 if (!(this.directiveIDs.indexOf(id) > -1)) {
42060 this.directiveIDs.push(id);
42063 Console.prototype.removeDirective = function (id) {
42064 var i = this.directiveIDs.indexOf(id);
42066 this.directiveIDs.splice(i, 1);
42069 Console.prototype.showIDs = function () {
42070 console.log(this.directiveIDs);
42074 services.Console = Console;
42075 })(services = app.services || (app.services = {}));
42076 })(app || (app = {}));
42080 (function (directives) {
42081 var Command = (function () {
42082 function Command() {
42083 this.restrict = 'E';
42084 this.replace = true;
42086 this.controller = 'commandController';
42087 this.controllerAs = 'ctrl';
42088 this.bindToController = {
42094 this.templateUrl = 'templates/command.html';
42096 Command.Factory = function () {
42097 var directive = function () {
42098 return new Command();
42100 directive.$inject = [];
42105 directives.Command = Command;
42106 var CommandController = (function () {
42107 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
42108 this.APIEndPoint = APIEndPoint;
42109 this.$scope = $scope;
42110 this.MyModal = MyModal;
42111 this.WebSocket = WebSocket;
42112 this.$window = $window;
42113 this.$rootScope = $rootScope;
42114 this.Console = Console;
42115 var controller = this;
42117 .getOptionControlFile(this.name)
42119 .then(function (result) {
42120 controller.options = result.info;
42125 .then(function (result) {
42126 controller.dirs = result.info;
42128 this.heading = "[" + this.index + "]: dcdFilePrint";
42129 this.isOpen = true;
42130 this.$scope.$on('close', function () {
42131 controller.isOpen = false;
42135 return Math.floor((1 + Math.random()) * 0x10000)
42139 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
42140 s4() + '-' + s4() + s4() + s4();
42142 this.uuid = guid();
42143 this.Console.addDirective(this.uuid);
42144 this.Console.showIDs();
42146 CommandController.prototype.submit = function () {
42148 angular.forEach(this.options, function (option) {
42150 name: option.option,
42153 angular.forEach(option.arg, function (arg) {
42155 if (typeof arg.input === 'object') {
42156 obj.arguments.push(arg.input.name);
42159 obj.arguments.push(arg.input);
42163 if (obj.arguments.length > 0) {
42168 command: this.name,
42169 workspace: this.workspace.fileId,
42173 .execute(JSON.stringify(execObj))
42174 .then(function (result) {
42175 console.log(result);
42178 CommandController.prototype.removeMySelf = function (index) {
42179 this.$scope.$destroy();
42180 this.Console.removeDirective(this.uuid);
42181 this.remove()(index, this.list);
42182 this.Console.showIDs();
42184 CommandController.prototype.reloadFiles = function () {
42186 var fileId = this.workspace.fileId;
42190 .then(function (result) {
42191 var status = result.status;
42192 if (status === 'success') {
42193 _this.files = result.info;
42196 console.log(result.message);
42200 CommandController.prototype.debug = function () {
42201 var div = angular.element(this.$window.document).find("div");
42204 angular.forEach(div, function (v) {
42205 if (v.className === "panel-body console") {
42208 else if (v.className === "row parameters-console") {
42212 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
42213 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
42214 consoleTag.style.height = consoleHeight;
42215 consoleTag.style.width = consoleWidth;
42217 CommandController.prototype.help = function () {
42220 .then(function (result) {
42221 console.log(result);
42224 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
42225 return CommandController;
42227 directives.CommandController = CommandController;
42228 })(directives = app.directives || (app.directives = {}));
42229 })(app || (app = {}));
42233 (function (directives) {
42234 var HeaderMenu = (function () {
42235 function HeaderMenu() {
42236 this.restrict = 'E';
42237 this.replace = true;
42238 this.templateUrl = 'templates/header-menu.html';
42239 this.controller = 'HeaderMenuController';
42240 this.controllerAs = 'hmc';
42243 HeaderMenu.Factory = function () {
42244 var directive = function () {
42245 return new HeaderMenu();
42251 directives.HeaderMenu = HeaderMenu;
42252 var HeaderMenuController = (function () {
42253 function HeaderMenuController($state) {
42254 this.$state = $state;
42255 this.isExecution = this.$state.current.name === 'execution';
42256 this.isWorkspace = this.$state.current.name === 'workspace';
42257 this.isHistory = this.$state.current.name === 'history';
42259 HeaderMenuController.prototype.transit = function (state) {
42260 this.$state.go(state);
42262 HeaderMenuController.$inject = ['$state'];
42263 return HeaderMenuController;
42265 directives.HeaderMenuController = HeaderMenuController;
42266 })(directives = app.directives || (app.directives = {}));
42267 })(app || (app = {}));
42271 (function (directives) {
42272 var Option = (function () {
42273 function Option() {
42274 this.restrict = 'E';
42275 this.replace = true;
42276 this.controller = 'optionController';
42277 this.bindToController = {
42282 this.templateUrl = 'templates/option.html';
42283 this.controllerAs = 'ctrl';
42285 Option.Factory = function () {
42286 var directive = function () {
42287 return new Option();
42289 directive.$inject = [];
42294 directives.Option = Option;
42295 var OptionController = (function () {
42296 function OptionController() {
42297 var controller = this;
42298 angular.forEach(controller.info.arg, function (arg) {
42299 if (arg.initialValue) {
42300 if (arg.formType === 'number') {
42301 arg.input = parseInt(arg.initialValue);
42304 arg.input = arg.initialValue;
42309 OptionController.$inject = [];
42310 return OptionController;
42312 directives.OptionController = OptionController;
42313 })(directives = app.directives || (app.directives = {}));
42314 })(app || (app = {}));
42318 (function (directives) {
42319 var Directory = (function () {
42320 function Directory() {
42321 this.restrict = 'E';
42322 this.replace = true;
42323 this.controller = 'directoryController';
42324 this.controllerAs = 'ctrl';
42325 this.bindToController = {
42331 this.templateUrl = 'templates/directory.html';
42333 Directory.Factory = function () {
42334 var directive = function () {
42335 return new Directory();
42341 directives.Directory = Directory;
42342 var DirectoryController = (function () {
42343 function DirectoryController(APIEndPoint, $scope) {
42344 this.APIEndPoint = APIEndPoint;
42345 this.$scope = $scope;
42346 var controller = this;
42348 .getFiles(this.info.fileId)
42350 .then(function (result) {
42351 if (result.status === 'success') {
42352 controller.files = result.info;
42353 angular.forEach(result.info, function (file) {
42354 if (file.fileType === '0') {
42356 if (controller.info.path === '/') {
42357 o.path = '/' + file.name;
42360 o.path = controller.info.path + '/' + file.name;
42362 controller.add()(o, controller.list);
42369 DirectoryController.$inject = ['APIEndPoint', '$scope'];
42370 return DirectoryController;
42372 directives.DirectoryController = DirectoryController;
42373 })(directives = app.directives || (app.directives = {}));
42374 })(app || (app = {}));
42378 (function (directives) {
42379 var Upload = (function () {
42380 function Upload() {
42381 this.restrict = 'E';
42382 this.replace = true;
42384 this.controller = 'UploadController';
42385 this.controllerAs = 'ctrl';
42386 this.bindToController = {
42392 this.templateUrl = 'templates/upload.html';
42393 console.log("templates/upload.html-constructor");
42395 Upload.Factory = function () {
42396 var directive = function () {
42397 return new Upload();
42399 directive.$inject = [];
42404 directives.Upload = Upload;
42405 var UploadController = (function () {
42406 function UploadController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
42407 this.APIEndPoint = APIEndPoint;
42408 this.$scope = $scope;
42409 this.MyModal = MyModal;
42410 this.WebSocket = WebSocket;
42411 this.$window = $window;
42412 this.$rootScope = $rootScope;
42413 this.Console = Console;
42414 var controller = this;
42415 console.log("directive.upload-constructor");
42417 UploadController.prototype.submit = function () {
42418 console.log("submit: function not supported¥n");
42420 UploadController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
42421 return UploadController;
42423 directives.UploadController = UploadController;
42424 })(directives = app.directives || (app.directives = {}));
42425 })(app || (app = {}));
42429 (function (controllers) {
42430 var Execution = (function () {
42431 function Execution(MyModal, $scope) {
42432 this.MyModal = MyModal;
42433 this.$scope = $scope;
42434 this.commandInfoList = [];
42437 Execution.prototype.add = function () {
42438 this.$scope.$broadcast('close');
42439 var commandInfoList = this.commandInfoList;
42440 var commandInstance = this.MyModal.selectCommand();
42443 .then(function (command) {
42444 commandInfoList.push(new app.declares.CommandInfo(command));
42447 Execution.prototype.open = function () {
42448 var result = this.MyModal.open('SelectCommand');
42449 console.log(result);
42451 Execution.prototype.remove = function (index, list) {
42452 list.splice(index, 1);
42454 Execution.prototype.close = function () {
42455 console.log("close");
42457 Execution.$inject = ['MyModal', '$scope'];
42460 controllers.Execution = Execution;
42461 })(controllers = app.controllers || (app.controllers = {}));
42462 })(app || (app = {}));
42466 (function (controllers) {
42467 var Workspace = (function () {
42468 function Workspace($scope, APIEndPoint, MyModal) {
42469 this.$scope = $scope;
42470 this.APIEndPoint = APIEndPoint;
42471 this.MyModal = MyModal;
42472 this.directoryList = [];
42473 var controller = this;
42474 var directoryList = this.directoryList;
42476 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
42484 directoryList.push(o);
42486 Workspace.prototype.addDirectory = function (info, directoryList) {
42487 directoryList.push(info);
42489 Workspace.prototype.upload = function () {
42490 this.MyModal.upload();
42492 Workspace.prototype.debug = function () {
42493 this.MyModal.preview();
42495 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
42498 controllers.Workspace = Workspace;
42499 })(controllers = app.controllers || (app.controllers = {}));
42500 })(app || (app = {}));
42504 (function (controllers) {
42505 var History = (function () {
42506 function History($scope) {
42507 this.page = "History";
42509 History.$inject = ['$scope'];
42512 controllers.History = History;
42513 })(controllers = app.controllers || (app.controllers = {}));
42514 })(app || (app = {}));
42518 (function (controllers) {
42519 var SelectCommand = (function () {
42520 function SelectCommand($scope, APIEndPoint, $modalInstance) {
42521 this.APIEndPoint = APIEndPoint;
42522 this.$modalInstance = $modalInstance;
42523 var controller = this;
42526 .$promise.then(function (result) {
42527 controller.tags = result.info;
42531 .$promise.then(function (result) {
42532 controller.commands = result.info;
42534 this.currentTag = 'all';
42536 SelectCommand.prototype.changeTag = function (tag) {
42537 this.currentTag = tag;
42539 SelectCommand.prototype.selectCommand = function (command) {
42540 this.$modalInstance.close(command);
42542 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
42543 return SelectCommand;
42545 controllers.SelectCommand = SelectCommand;
42546 })(controllers = app.controllers || (app.controllers = {}));
42547 })(app || (app = {}));
42551 (function (controllers) {
42552 var Upload = (function () {
42553 function Upload($scope, APIEndPoint, $modalInstance) {
42554 this.APIEndPoint = APIEndPoint;
42555 this.$modalInstance = $modalInstance;
42556 var controller = this;
42557 console.log('controller.upload-controllers');
42559 Upload.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
42562 controllers.Upload = Upload;
42563 })(controllers = app.controllers || (app.controllers = {}));
42564 })(app || (app = {}));
42568 (function (controllers) {
42569 var Preview = (function () {
42570 function Preview($scope, APIEndPoint, $modalInstance) {
42571 this.APIEndPoint = APIEndPoint;
42572 this.$modalInstance = $modalInstance;
42573 var controller = this;
42574 console.log('preview');
42576 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
42579 controllers.Preview = Preview;
42580 })(controllers = app.controllers || (app.controllers = {}));
42581 })(app || (app = {}));
42583 (function (filters) {
42585 return function (commands, tag) {
42587 angular.forEach(commands, function (command) {
42589 angular.forEach(command.tags, function (value) {
42594 result.push(command);
42600 })(filters || (filters = {}));
42604 var appName = 'zephyr';
42605 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
42606 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
42607 $urlRouterProvider.otherwise('/execution');
42608 $locationProvider.html5Mode({
42613 .state('execution', {
42615 templateUrl: 'templates/execution.html',
42616 controller: 'executionController',
42619 .state('workspace', {
42621 templateUrl: 'templates/workspace.html',
42622 controller: 'workspaceController',
42625 .state('history', {
42627 templateUrl: 'templates/history.html',
42628 controller: 'historyController',
42632 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
42633 app.zephyr.service('MyModal', app.services.MyModal);
42634 app.zephyr.service('WebSocket', app.services.WebSocket);
42635 app.zephyr.service('Console', app.services.Console);
42636 app.zephyr.filter('Tag', filters.Tag);
42637 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
42638 app.zephyr.controller('previewController', app.controllers.Preview);
42639 app.zephyr.controller('uploadController', app.controllers.Upload);
42640 app.zephyr.controller('executionController', app.controllers.Execution);
42641 app.zephyr.controller('workspaceController', app.controllers.Workspace);
42642 app.zephyr.controller('historyController', app.controllers.History);
42643 app.zephyr.controller('commandController', app.directives.CommandController);
42644 app.zephyr.controller('optionController', app.directives.OptionController);
42645 app.zephyr.controller('directoryController', app.directives.DirectoryController);
42646 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
42647 app.zephyr.controller('uploadController', app.directives.UploadController);
42648 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
42649 app.zephyr.directive('command', app.directives.Command.Factory());
42650 app.zephyr.directive('option', app.directives.Option.Factory());
42651 app.zephyr.directive('directory', app.directives.Directory.Factory());
42652 })(app || (app = {}));
42657 /***/ function(module, exports) {
42662 (function (declares) {
42663 var CommandInfo = (function () {
42664 function CommandInfo(name) {
42667 return CommandInfo;
42669 declares.CommandInfo = CommandInfo;
42670 })(declares = app.declares || (app.declares = {}));
42671 })(app || (app = {}));
42675 (function (services) {
42676 var APIEndPoint = (function () {
42677 function APIEndPoint($resource, $http) {
42678 this.$resource = $resource;
42679 this.$http = $http;
42681 APIEndPoint.prototype.resource = function (endPoint, data) {
42682 var customAction = {
42688 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
42690 return this.$resource(endPoint, {}, { execute: execute });
42692 APIEndPoint.prototype.getOptionControlFile = function (command) {
42693 var endPoint = '/api/v1/optionControlFile/' + command;
42694 return this.resource(endPoint, {}).get();
42696 APIEndPoint.prototype.getFiles = function (fileId) {
42697 var endPoint = '/api/v1/workspace';
42699 endPoint += '/' + fileId;
42701 return this.resource(endPoint, {}).get();
42703 APIEndPoint.prototype.getDirectories = function () {
42704 var endPoint = '/api/v1/all/workspace/directory';
42705 return this.resource(endPoint, {}).get();
42707 APIEndPoint.prototype.getTags = function () {
42708 var endPoint = '/api/v1/tagList';
42709 return this.resource(endPoint, {}).get();
42711 APIEndPoint.prototype.getCommands = function () {
42712 var endPoint = '/api/v1/commandList';
42713 return this.resource(endPoint, {}).get();
42715 APIEndPoint.prototype.execute = function (data) {
42716 var endPoint = '/api/v1/execution';
42717 var fd = new FormData();
42718 fd.append('data', data);
42719 return this.$http.post(endPoint, fd, {
42720 headers: { 'Content-Type': undefined },
42721 transformRequest: angular.identity
42724 APIEndPoint.prototype.debug = function () {
42725 var endPoint = '/api/v1/debug';
42726 return this.$http.get(endPoint);
42728 APIEndPoint.prototype.upload = function () {
42729 var endPoint = '/api/v1/upload';
42730 return this.$http.get(endPoint);
42732 APIEndPoint.prototype.help = function (command) {
42733 var endPoint = '/api/v1/help/' + command;
42734 return this.$http.get(endPoint);
42736 return APIEndPoint;
42738 services.APIEndPoint = APIEndPoint;
42739 })(services = app.services || (app.services = {}));
42740 })(app || (app = {}));
42744 (function (services) {
42745 var MyModal = (function () {
42746 function MyModal($uibModal) {
42747 this.$uibModal = $uibModal;
42748 this.modalOption = {
42755 MyModal.prototype.open = function (modalName) {
42756 if (modalName === 'SelectCommand') {
42757 this.modalOption.templateUrl = 'templates/select-command.html';
42758 this.modalOption.size = 'lg';
42760 return this.$uibModal.open(this.modalOption);
42762 MyModal.prototype.selectCommand = function () {
42763 this.modalOption.templateUrl = 'templates/select-command.html';
42764 this.modalOption.controller = 'selectCommandController';
42765 this.modalOption.controllerAs = 'c';
42766 this.modalOption.size = 'lg';
42767 return this.$uibModal.open(this.modalOption);
42769 MyModal.prototype.preview = function () {
42770 this.modalOption.templateUrl = 'templates/preview.html';
42771 this.modalOption.controller = 'previewController';
42772 this.modalOption.controllerAs = 'c';
42773 this.modalOption.size = 'lg';
42774 return this.$uibModal.open(this.modalOption);
42776 MyModal.prototype.upload = function () {
42777 this.modalOption.templateUrl = 'templates/upload.html';
42778 this.modalOption.controller = 'uploadController';
42779 this.modalOption.controllerAs = 'c';
42780 this.modalOption.size = 'lg';
42781 return this.$uibModal.open(this.modalOption);
42783 MyModal.$inject = ['$uibModal'];
42786 services.MyModal = MyModal;
42787 })(services = app.services || (app.services = {}));
42788 })(app || (app = {}));
42792 (function (services) {
42793 var WebSocket = (function () {
42794 function WebSocket($rootScope) {
42795 this.$rootScope = $rootScope;
42796 this.socket = io.connect();
42798 WebSocket.prototype.on = function (eventName, callback) {
42799 var socket = this.socket;
42800 var rootScope = this.$rootScope;
42801 socket.on(eventName, function () {
42802 var args = arguments;
42803 rootScope.$apply(function () {
42804 callback.apply(socket, args);
42808 WebSocket.prototype.emit = function (eventName, data, callback) {
42809 var socket = this.socket;
42810 var rootScope = this.$rootScope;
42811 this.socket.emit(eventName, data, function () {
42812 var args = arguments;
42813 rootScope.$apply(function () {
42815 callback.apply(socket, args);
42821 services.WebSocket = WebSocket;
42822 })(services = app.services || (app.services = {}));
42823 })(app || (app = {}));
42827 (function (services) {
42828 var Console = (function () {
42829 function Console(WebSocket, $rootScope) {
42830 this.WebSocket = WebSocket;
42831 this.$rootScope = $rootScope;
42832 this.WebSocket = WebSocket;
42833 this.$rootScope = $rootScope;
42834 this.directiveIDs = [];
42835 var directiveIDs = this.directiveIDs;
42836 this.WebSocket.on('console', function (d) {
42838 var message = d.message;
42839 if (directiveIDs.indexOf(id) > -1) {
42840 $rootScope.$emit(id, message);
42844 Console.prototype.addDirective = function (id) {
42845 if (!(this.directiveIDs.indexOf(id) > -1)) {
42846 this.directiveIDs.push(id);
42849 Console.prototype.removeDirective = function (id) {
42850 var i = this.directiveIDs.indexOf(id);
42852 this.directiveIDs.splice(i, 1);
42855 Console.prototype.showIDs = function () {
42856 console.log(this.directiveIDs);
42860 services.Console = Console;
42861 })(services = app.services || (app.services = {}));
42862 })(app || (app = {}));
42866 (function (directives) {
42867 var Command = (function () {
42868 function Command() {
42869 this.restrict = 'E';
42870 this.replace = true;
42872 this.controller = 'commandController';
42873 this.controllerAs = 'ctrl';
42874 this.bindToController = {
42880 this.templateUrl = 'templates/command.html';
42882 Command.Factory = function () {
42883 var directive = function () {
42884 return new Command();
42886 directive.$inject = [];
42891 directives.Command = Command;
42892 var CommandController = (function () {
42893 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
42894 this.APIEndPoint = APIEndPoint;
42895 this.$scope = $scope;
42896 this.MyModal = MyModal;
42897 this.WebSocket = WebSocket;
42898 this.$window = $window;
42899 this.$rootScope = $rootScope;
42900 this.Console = Console;
42901 var controller = this;
42903 .getOptionControlFile(this.name)
42905 .then(function (result) {
42906 controller.options = result.info;
42911 .then(function (result) {
42912 controller.dirs = result.info;
42914 this.heading = "[" + this.index + "]: dcdFilePrint";
42915 this.isOpen = true;
42916 this.$scope.$on('close', function () {
42917 controller.isOpen = false;
42921 return Math.floor((1 + Math.random()) * 0x10000)
42925 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
42926 s4() + '-' + s4() + s4() + s4();
42928 this.uuid = guid();
42929 this.Console.addDirective(this.uuid);
42930 this.Console.showIDs();
42932 CommandController.prototype.submit = function () {
42934 angular.forEach(this.options, function (option) {
42936 name: option.option,
42939 angular.forEach(option.arg, function (arg) {
42941 if (typeof arg.input === 'object') {
42942 obj.arguments.push(arg.input.name);
42945 obj.arguments.push(arg.input);
42949 if (obj.arguments.length > 0) {
42954 command: this.name,
42955 workspace: this.workspace.fileId,
42959 .execute(JSON.stringify(execObj))
42960 .then(function (result) {
42961 console.log(result);
42964 CommandController.prototype.removeMySelf = function (index) {
42965 this.$scope.$destroy();
42966 this.Console.removeDirective(this.uuid);
42967 this.remove()(index, this.list);
42968 this.Console.showIDs();
42970 CommandController.prototype.reloadFiles = function () {
42972 var fileId = this.workspace.fileId;
42976 .then(function (result) {
42977 var status = result.status;
42978 if (status === 'success') {
42979 _this.files = result.info;
42982 console.log(result.message);
42986 CommandController.prototype.debug = function () {
42987 var div = angular.element(this.$window.document).find("div");
42990 angular.forEach(div, function (v) {
42991 if (v.className === "panel-body console") {
42994 else if (v.className === "row parameters-console") {
42998 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
42999 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
43000 consoleTag.style.height = consoleHeight;
43001 consoleTag.style.width = consoleWidth;
43003 CommandController.prototype.help = function () {
43006 .then(function (result) {
43007 console.log(result);
43010 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
43011 return CommandController;
43013 directives.CommandController = CommandController;
43014 })(directives = app.directives || (app.directives = {}));
43015 })(app || (app = {}));
43019 (function (directives) {
43020 var HeaderMenu = (function () {
43021 function HeaderMenu() {
43022 this.restrict = 'E';
43023 this.replace = true;
43024 this.templateUrl = 'templates/header-menu.html';
43025 this.controller = 'HeaderMenuController';
43026 this.controllerAs = 'hmc';
43029 HeaderMenu.Factory = function () {
43030 var directive = function () {
43031 return new HeaderMenu();
43037 directives.HeaderMenu = HeaderMenu;
43038 var HeaderMenuController = (function () {
43039 function HeaderMenuController($state) {
43040 this.$state = $state;
43041 this.isExecution = this.$state.current.name === 'execution';
43042 this.isWorkspace = this.$state.current.name === 'workspace';
43043 this.isHistory = this.$state.current.name === 'history';
43045 HeaderMenuController.prototype.transit = function (state) {
43046 this.$state.go(state);
43048 HeaderMenuController.$inject = ['$state'];
43049 return HeaderMenuController;
43051 directives.HeaderMenuController = HeaderMenuController;
43052 })(directives = app.directives || (app.directives = {}));
43053 })(app || (app = {}));
43057 (function (directives) {
43058 var Option = (function () {
43059 function Option() {
43060 this.restrict = 'E';
43061 this.replace = true;
43062 this.controller = 'optionController';
43063 this.bindToController = {
43068 this.templateUrl = 'templates/option.html';
43069 this.controllerAs = 'ctrl';
43071 Option.Factory = function () {
43072 var directive = function () {
43073 return new Option();
43075 directive.$inject = [];
43080 directives.Option = Option;
43081 var OptionController = (function () {
43082 function OptionController() {
43083 var controller = this;
43084 angular.forEach(controller.info.arg, function (arg) {
43085 if (arg.initialValue) {
43086 if (arg.formType === 'number') {
43087 arg.input = parseInt(arg.initialValue);
43090 arg.input = arg.initialValue;
43095 OptionController.$inject = [];
43096 return OptionController;
43098 directives.OptionController = OptionController;
43099 })(directives = app.directives || (app.directives = {}));
43100 })(app || (app = {}));
43104 (function (directives) {
43105 var Directory = (function () {
43106 function Directory() {
43107 this.restrict = 'E';
43108 this.replace = true;
43109 this.controller = 'directoryController';
43110 this.controllerAs = 'ctrl';
43111 this.bindToController = {
43117 this.templateUrl = 'templates/directory.html';
43119 Directory.Factory = function () {
43120 var directive = function () {
43121 return new Directory();
43127 directives.Directory = Directory;
43128 var DirectoryController = (function () {
43129 function DirectoryController(APIEndPoint, $scope) {
43130 this.APIEndPoint = APIEndPoint;
43131 this.$scope = $scope;
43132 var controller = this;
43134 .getFiles(this.info.fileId)
43136 .then(function (result) {
43137 if (result.status === 'success') {
43138 controller.files = result.info;
43139 angular.forEach(result.info, function (file) {
43140 if (file.fileType === '0') {
43142 if (controller.info.path === '/') {
43143 o.path = '/' + file.name;
43146 o.path = controller.info.path + '/' + file.name;
43148 controller.add()(o, controller.list);
43155 DirectoryController.$inject = ['APIEndPoint', '$scope'];
43156 return DirectoryController;
43158 directives.DirectoryController = DirectoryController;
43159 })(directives = app.directives || (app.directives = {}));
43160 })(app || (app = {}));
43164 (function (directives) {
43165 var Upload = (function () {
43166 function Upload() {
43167 this.restrict = 'E';
43168 this.replace = true;
43170 this.controller = 'UploadController';
43171 this.controllerAs = 'ctrl';
43172 this.bindToController = {
43178 this.templateUrl = 'templates/upload.html';
43179 console.log("templates/upload.html-constructor");
43181 Upload.Factory = function () {
43182 var directive = function () {
43183 return new Upload();
43185 directive.$inject = [];
43190 directives.Upload = Upload;
43191 var UploadController = (function () {
43192 function UploadController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
43193 this.APIEndPoint = APIEndPoint;
43194 this.$scope = $scope;
43195 this.MyModal = MyModal;
43196 this.WebSocket = WebSocket;
43197 this.$window = $window;
43198 this.$rootScope = $rootScope;
43199 this.Console = Console;
43200 var controller = this;
43201 console.log("directive.upload-constructor");
43203 UploadController.prototype.submit = function () {
43204 console.log("submit: function not supported¥n");
43206 UploadController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
43207 return UploadController;
43209 directives.UploadController = UploadController;
43210 })(directives = app.directives || (app.directives = {}));
43211 })(app || (app = {}));
43215 (function (controllers) {
43216 var Execution = (function () {
43217 function Execution(MyModal, $scope) {
43218 this.MyModal = MyModal;
43219 this.$scope = $scope;
43220 this.commandInfoList = [];
43223 Execution.prototype.add = function () {
43224 this.$scope.$broadcast('close');
43225 var commandInfoList = this.commandInfoList;
43226 var commandInstance = this.MyModal.selectCommand();
43229 .then(function (command) {
43230 commandInfoList.push(new app.declares.CommandInfo(command));
43233 Execution.prototype.open = function () {
43234 var result = this.MyModal.open('SelectCommand');
43235 console.log(result);
43237 Execution.prototype.remove = function (index, list) {
43238 list.splice(index, 1);
43240 Execution.prototype.close = function () {
43241 console.log("close");
43243 Execution.$inject = ['MyModal', '$scope'];
43246 controllers.Execution = Execution;
43247 })(controllers = app.controllers || (app.controllers = {}));
43248 })(app || (app = {}));
43252 (function (controllers) {
43253 var Workspace = (function () {
43254 function Workspace($scope, APIEndPoint, MyModal) {
43255 this.$scope = $scope;
43256 this.APIEndPoint = APIEndPoint;
43257 this.MyModal = MyModal;
43258 this.directoryList = [];
43259 var controller = this;
43260 var directoryList = this.directoryList;
43262 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
43270 directoryList.push(o);
43272 Workspace.prototype.addDirectory = function (info, directoryList) {
43273 directoryList.push(info);
43275 Workspace.prototype.upload = function () {
43276 this.MyModal.upload();
43278 Workspace.prototype.debug = function () {
43279 this.MyModal.preview();
43281 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
43284 controllers.Workspace = Workspace;
43285 })(controllers = app.controllers || (app.controllers = {}));
43286 })(app || (app = {}));
43290 (function (controllers) {
43291 var History = (function () {
43292 function History($scope) {
43293 this.page = "History";
43295 History.$inject = ['$scope'];
43298 controllers.History = History;
43299 })(controllers = app.controllers || (app.controllers = {}));
43300 })(app || (app = {}));
43304 (function (controllers) {
43305 var SelectCommand = (function () {
43306 function SelectCommand($scope, APIEndPoint, $modalInstance) {
43307 this.APIEndPoint = APIEndPoint;
43308 this.$modalInstance = $modalInstance;
43309 var controller = this;
43312 .$promise.then(function (result) {
43313 controller.tags = result.info;
43317 .$promise.then(function (result) {
43318 controller.commands = result.info;
43320 this.currentTag = 'all';
43322 SelectCommand.prototype.changeTag = function (tag) {
43323 this.currentTag = tag;
43325 SelectCommand.prototype.selectCommand = function (command) {
43326 this.$modalInstance.close(command);
43328 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
43329 return SelectCommand;
43331 controllers.SelectCommand = SelectCommand;
43332 })(controllers = app.controllers || (app.controllers = {}));
43333 })(app || (app = {}));
43337 (function (controllers) {
43338 var Upload = (function () {
43339 function Upload($scope, APIEndPoint, $modalInstance) {
43340 this.APIEndPoint = APIEndPoint;
43341 this.$modalInstance = $modalInstance;
43342 var controller = this;
43343 console.log('controller.upload-controllers');
43345 Upload.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
43348 controllers.Upload = Upload;
43349 })(controllers = app.controllers || (app.controllers = {}));
43350 })(app || (app = {}));
43354 (function (controllers) {
43355 var Preview = (function () {
43356 function Preview($scope, APIEndPoint, $modalInstance) {
43357 this.APIEndPoint = APIEndPoint;
43358 this.$modalInstance = $modalInstance;
43359 var controller = this;
43360 console.log('preview');
43362 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
43365 controllers.Preview = Preview;
43366 })(controllers = app.controllers || (app.controllers = {}));
43367 })(app || (app = {}));
43369 (function (filters) {
43371 return function (commands, tag) {
43373 angular.forEach(commands, function (command) {
43375 angular.forEach(command.tags, function (value) {
43380 result.push(command);
43386 })(filters || (filters = {}));
43390 var appName = 'zephyr';
43391 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
43392 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
43393 $urlRouterProvider.otherwise('/execution');
43394 $locationProvider.html5Mode({
43399 .state('execution', {
43401 templateUrl: 'templates/execution.html',
43402 controller: 'executionController',
43405 .state('workspace', {
43407 templateUrl: 'templates/workspace.html',
43408 controller: 'workspaceController',
43411 .state('history', {
43413 templateUrl: 'templates/history.html',
43414 controller: 'historyController',
43418 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
43419 app.zephyr.service('MyModal', app.services.MyModal);
43420 app.zephyr.service('WebSocket', app.services.WebSocket);
43421 app.zephyr.service('Console', app.services.Console);
43422 app.zephyr.filter('Tag', filters.Tag);
43423 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
43424 app.zephyr.controller('previewController', app.controllers.Preview);
43425 app.zephyr.controller('uploadController', app.controllers.Upload);
43426 app.zephyr.controller('executionController', app.controllers.Execution);
43427 app.zephyr.controller('workspaceController', app.controllers.Workspace);
43428 app.zephyr.controller('historyController', app.controllers.History);
43429 app.zephyr.controller('commandController', app.directives.CommandController);
43430 app.zephyr.controller('optionController', app.directives.OptionController);
43431 app.zephyr.controller('directoryController', app.directives.DirectoryController);
43432 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
43433 app.zephyr.controller('uploadController', app.directives.UploadController);
43434 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
43435 app.zephyr.directive('command', app.directives.Command.Factory());
43436 app.zephyr.directive('option', app.directives.Option.Factory());
43437 app.zephyr.directive('directory', app.directives.Directory.Factory());
43438 })(app || (app = {}));
43443 /***/ function(module, exports) {
43448 (function (declares) {
43449 var CommandInfo = (function () {
43450 function CommandInfo(name) {
43453 return CommandInfo;
43455 declares.CommandInfo = CommandInfo;
43456 })(declares = app.declares || (app.declares = {}));
43457 })(app || (app = {}));
43461 (function (services) {
43462 var APIEndPoint = (function () {
43463 function APIEndPoint($resource, $http) {
43464 this.$resource = $resource;
43465 this.$http = $http;
43467 APIEndPoint.prototype.resource = function (endPoint, data) {
43468 var customAction = {
43474 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
43476 return this.$resource(endPoint, {}, { execute: execute });
43478 APIEndPoint.prototype.getOptionControlFile = function (command) {
43479 var endPoint = '/api/v1/optionControlFile/' + command;
43480 return this.resource(endPoint, {}).get();
43482 APIEndPoint.prototype.getFiles = function (fileId) {
43483 var endPoint = '/api/v1/workspace';
43485 endPoint += '/' + fileId;
43487 return this.resource(endPoint, {}).get();
43489 APIEndPoint.prototype.getDirectories = function () {
43490 var endPoint = '/api/v1/all/workspace/directory';
43491 return this.resource(endPoint, {}).get();
43493 APIEndPoint.prototype.getTags = function () {
43494 var endPoint = '/api/v1/tagList';
43495 return this.resource(endPoint, {}).get();
43497 APIEndPoint.prototype.getCommands = function () {
43498 var endPoint = '/api/v1/commandList';
43499 return this.resource(endPoint, {}).get();
43501 APIEndPoint.prototype.execute = function (data) {
43502 var endPoint = '/api/v1/execution';
43503 var fd = new FormData();
43504 fd.append('data', data);
43505 return this.$http.post(endPoint, fd, {
43506 headers: { 'Content-Type': undefined },
43507 transformRequest: angular.identity
43510 APIEndPoint.prototype.debug = function () {
43511 var endPoint = '/api/v1/debug';
43512 return this.$http.get(endPoint);
43514 APIEndPoint.prototype.upload = function () {
43515 var endPoint = '/api/v1/upload';
43516 return this.$http.get(endPoint);
43518 APIEndPoint.prototype.help = function (command) {
43519 var endPoint = '/api/v1/help/' + command;
43520 return this.$http.get(endPoint);
43522 return APIEndPoint;
43524 services.APIEndPoint = APIEndPoint;
43525 })(services = app.services || (app.services = {}));
43526 })(app || (app = {}));
43530 (function (services) {
43531 var MyModal = (function () {
43532 function MyModal($uibModal) {
43533 this.$uibModal = $uibModal;
43534 this.modalOption = {
43541 MyModal.prototype.open = function (modalName) {
43542 if (modalName === 'SelectCommand') {
43543 this.modalOption.templateUrl = 'templates/select-command.html';
43544 this.modalOption.size = 'lg';
43546 return this.$uibModal.open(this.modalOption);
43548 MyModal.prototype.selectCommand = function () {
43549 this.modalOption.templateUrl = 'templates/select-command.html';
43550 this.modalOption.controller = 'selectCommandController';
43551 this.modalOption.controllerAs = 'c';
43552 this.modalOption.size = 'lg';
43553 return this.$uibModal.open(this.modalOption);
43555 MyModal.prototype.preview = function () {
43556 this.modalOption.templateUrl = 'templates/preview.html';
43557 this.modalOption.controller = 'previewController';
43558 this.modalOption.controllerAs = 'c';
43559 this.modalOption.size = 'lg';
43560 return this.$uibModal.open(this.modalOption);
43562 MyModal.prototype.upload = function () {
43563 this.modalOption.templateUrl = 'templates/upload.html';
43564 this.modalOption.controller = 'uploadController';
43565 this.modalOption.controllerAs = 'c';
43566 this.modalOption.size = 'lg';
43567 return this.$uibModal.open(this.modalOption);
43569 MyModal.$inject = ['$uibModal'];
43572 services.MyModal = MyModal;
43573 })(services = app.services || (app.services = {}));
43574 })(app || (app = {}));
43578 (function (services) {
43579 var WebSocket = (function () {
43580 function WebSocket($rootScope) {
43581 this.$rootScope = $rootScope;
43582 this.socket = io.connect();
43584 WebSocket.prototype.on = function (eventName, callback) {
43585 var socket = this.socket;
43586 var rootScope = this.$rootScope;
43587 socket.on(eventName, function () {
43588 var args = arguments;
43589 rootScope.$apply(function () {
43590 callback.apply(socket, args);
43594 WebSocket.prototype.emit = function (eventName, data, callback) {
43595 var socket = this.socket;
43596 var rootScope = this.$rootScope;
43597 this.socket.emit(eventName, data, function () {
43598 var args = arguments;
43599 rootScope.$apply(function () {
43601 callback.apply(socket, args);
43607 services.WebSocket = WebSocket;
43608 })(services = app.services || (app.services = {}));
43609 })(app || (app = {}));
43613 (function (services) {
43614 var Console = (function () {
43615 function Console(WebSocket, $rootScope) {
43616 this.WebSocket = WebSocket;
43617 this.$rootScope = $rootScope;
43618 this.WebSocket = WebSocket;
43619 this.$rootScope = $rootScope;
43620 this.directiveIDs = [];
43621 var directiveIDs = this.directiveIDs;
43622 this.WebSocket.on('console', function (d) {
43624 var message = d.message;
43625 if (directiveIDs.indexOf(id) > -1) {
43626 $rootScope.$emit(id, message);
43630 Console.prototype.addDirective = function (id) {
43631 if (!(this.directiveIDs.indexOf(id) > -1)) {
43632 this.directiveIDs.push(id);
43635 Console.prototype.removeDirective = function (id) {
43636 var i = this.directiveIDs.indexOf(id);
43638 this.directiveIDs.splice(i, 1);
43641 Console.prototype.showIDs = function () {
43642 console.log(this.directiveIDs);
43646 services.Console = Console;
43647 })(services = app.services || (app.services = {}));
43648 })(app || (app = {}));
43652 (function (directives) {
43653 var Command = (function () {
43654 function Command() {
43655 this.restrict = 'E';
43656 this.replace = true;
43658 this.controller = 'commandController';
43659 this.controllerAs = 'ctrl';
43660 this.bindToController = {
43666 this.templateUrl = 'templates/command.html';
43668 Command.Factory = function () {
43669 var directive = function () {
43670 return new Command();
43672 directive.$inject = [];
43677 directives.Command = Command;
43678 var CommandController = (function () {
43679 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
43680 this.APIEndPoint = APIEndPoint;
43681 this.$scope = $scope;
43682 this.MyModal = MyModal;
43683 this.WebSocket = WebSocket;
43684 this.$window = $window;
43685 this.$rootScope = $rootScope;
43686 this.Console = Console;
43687 var controller = this;
43689 .getOptionControlFile(this.name)
43691 .then(function (result) {
43692 controller.options = result.info;
43697 .then(function (result) {
43698 controller.dirs = result.info;
43700 this.heading = "[" + this.index + "]: dcdFilePrint";
43701 this.isOpen = true;
43702 this.$scope.$on('close', function () {
43703 controller.isOpen = false;
43707 return Math.floor((1 + Math.random()) * 0x10000)
43711 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
43712 s4() + '-' + s4() + s4() + s4();
43714 this.uuid = guid();
43715 this.Console.addDirective(this.uuid);
43716 this.Console.showIDs();
43718 CommandController.prototype.submit = function () {
43720 angular.forEach(this.options, function (option) {
43722 name: option.option,
43725 angular.forEach(option.arg, function (arg) {
43727 if (typeof arg.input === 'object') {
43728 obj.arguments.push(arg.input.name);
43731 obj.arguments.push(arg.input);
43735 if (obj.arguments.length > 0) {
43740 command: this.name,
43741 workspace: this.workspace.fileId,
43745 .execute(JSON.stringify(execObj))
43746 .then(function (result) {
43747 console.log(result);
43750 CommandController.prototype.removeMySelf = function (index) {
43751 this.$scope.$destroy();
43752 this.Console.removeDirective(this.uuid);
43753 this.remove()(index, this.list);
43754 this.Console.showIDs();
43756 CommandController.prototype.reloadFiles = function () {
43758 var fileId = this.workspace.fileId;
43762 .then(function (result) {
43763 var status = result.status;
43764 if (status === 'success') {
43765 _this.files = result.info;
43768 console.log(result.message);
43772 CommandController.prototype.debug = function () {
43773 var div = angular.element(this.$window.document).find("div");
43776 angular.forEach(div, function (v) {
43777 if (v.className === "panel-body console") {
43780 else if (v.className === "row parameters-console") {
43784 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
43785 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
43786 consoleTag.style.height = consoleHeight;
43787 consoleTag.style.width = consoleWidth;
43789 CommandController.prototype.help = function () {
43792 .then(function (result) {
43793 console.log(result);
43796 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
43797 return CommandController;
43799 directives.CommandController = CommandController;
43800 })(directives = app.directives || (app.directives = {}));
43801 })(app || (app = {}));
43805 (function (directives) {
43806 var HeaderMenu = (function () {
43807 function HeaderMenu() {
43808 this.restrict = 'E';
43809 this.replace = true;
43810 this.templateUrl = 'templates/header-menu.html';
43811 this.controller = 'HeaderMenuController';
43812 this.controllerAs = 'hmc';
43815 HeaderMenu.Factory = function () {
43816 var directive = function () {
43817 return new HeaderMenu();
43823 directives.HeaderMenu = HeaderMenu;
43824 var HeaderMenuController = (function () {
43825 function HeaderMenuController($state) {
43826 this.$state = $state;
43827 this.isExecution = this.$state.current.name === 'execution';
43828 this.isWorkspace = this.$state.current.name === 'workspace';
43829 this.isHistory = this.$state.current.name === 'history';
43831 HeaderMenuController.prototype.transit = function (state) {
43832 this.$state.go(state);
43834 HeaderMenuController.$inject = ['$state'];
43835 return HeaderMenuController;
43837 directives.HeaderMenuController = HeaderMenuController;
43838 })(directives = app.directives || (app.directives = {}));
43839 })(app || (app = {}));
43843 (function (directives) {
43844 var Option = (function () {
43845 function Option() {
43846 this.restrict = 'E';
43847 this.replace = true;
43848 this.controller = 'optionController';
43849 this.bindToController = {
43854 this.templateUrl = 'templates/option.html';
43855 this.controllerAs = 'ctrl';
43857 Option.Factory = function () {
43858 var directive = function () {
43859 return new Option();
43861 directive.$inject = [];
43866 directives.Option = Option;
43867 var OptionController = (function () {
43868 function OptionController() {
43869 var controller = this;
43870 angular.forEach(controller.info.arg, function (arg) {
43871 if (arg.initialValue) {
43872 if (arg.formType === 'number') {
43873 arg.input = parseInt(arg.initialValue);
43876 arg.input = arg.initialValue;
43881 OptionController.$inject = [];
43882 return OptionController;
43884 directives.OptionController = OptionController;
43885 })(directives = app.directives || (app.directives = {}));
43886 })(app || (app = {}));
43890 (function (directives) {
43891 var Directory = (function () {
43892 function Directory() {
43893 this.restrict = 'E';
43894 this.replace = true;
43895 this.controller = 'directoryController';
43896 this.controllerAs = 'ctrl';
43897 this.bindToController = {
43903 this.templateUrl = 'templates/directory.html';
43905 Directory.Factory = function () {
43906 var directive = function () {
43907 return new Directory();
43913 directives.Directory = Directory;
43914 var DirectoryController = (function () {
43915 function DirectoryController(APIEndPoint, $scope) {
43916 this.APIEndPoint = APIEndPoint;
43917 this.$scope = $scope;
43918 var controller = this;
43920 .getFiles(this.info.fileId)
43922 .then(function (result) {
43923 if (result.status === 'success') {
43924 controller.files = result.info;
43925 angular.forEach(result.info, function (file) {
43926 if (file.fileType === '0') {
43928 if (controller.info.path === '/') {
43929 o.path = '/' + file.name;
43932 o.path = controller.info.path + '/' + file.name;
43934 controller.add()(o, controller.list);
43941 DirectoryController.$inject = ['APIEndPoint', '$scope'];
43942 return DirectoryController;
43944 directives.DirectoryController = DirectoryController;
43945 })(directives = app.directives || (app.directives = {}));
43946 })(app || (app = {}));
43950 (function (directives) {
43951 var Upload = (function () {
43952 function Upload() {
43953 this.restrict = 'E';
43954 this.replace = true;
43956 this.controller = 'UploadController';
43957 this.controllerAs = 'ctrl';
43958 this.bindToController = {
43964 this.templateUrl = 'templates/upload.html';
43965 console.log("templates/upload.html-constructor");
43967 Upload.Factory = function () {
43968 var directive = function () {
43969 return new Upload();
43971 directive.$inject = [];
43976 directives.Upload = Upload;
43977 var UploadController = (function () {
43978 function UploadController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
43979 this.APIEndPoint = APIEndPoint;
43980 this.$scope = $scope;
43981 this.MyModal = MyModal;
43982 this.WebSocket = WebSocket;
43983 this.$window = $window;
43984 this.$rootScope = $rootScope;
43985 this.Console = Console;
43986 var controller = this;
43987 console.log("directive.upload-constructor");
43989 UploadController.prototype.submit = function () {
43990 console.log("submit: function not supported¥n");
43992 UploadController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
43993 return UploadController;
43995 directives.UploadController = UploadController;
43996 })(directives = app.directives || (app.directives = {}));
43997 })(app || (app = {}));
44001 (function (controllers) {
44002 var Execution = (function () {
44003 function Execution(MyModal, $scope) {
44004 this.MyModal = MyModal;
44005 this.$scope = $scope;
44006 this.commandInfoList = [];
44009 Execution.prototype.add = function () {
44010 this.$scope.$broadcast('close');
44011 var commandInfoList = this.commandInfoList;
44012 var commandInstance = this.MyModal.selectCommand();
44015 .then(function (command) {
44016 commandInfoList.push(new app.declares.CommandInfo(command));
44019 Execution.prototype.open = function () {
44020 var result = this.MyModal.open('SelectCommand');
44021 console.log(result);
44023 Execution.prototype.remove = function (index, list) {
44024 list.splice(index, 1);
44026 Execution.prototype.close = function () {
44027 console.log("close");
44029 Execution.$inject = ['MyModal', '$scope'];
44032 controllers.Execution = Execution;
44033 })(controllers = app.controllers || (app.controllers = {}));
44034 })(app || (app = {}));
44038 (function (controllers) {
44039 var Workspace = (function () {
44040 function Workspace($scope, APIEndPoint, MyModal) {
44041 this.$scope = $scope;
44042 this.APIEndPoint = APIEndPoint;
44043 this.MyModal = MyModal;
44044 this.directoryList = [];
44045 var controller = this;
44046 var directoryList = this.directoryList;
44048 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
44056 directoryList.push(o);
44058 Workspace.prototype.addDirectory = function (info, directoryList) {
44059 directoryList.push(info);
44061 Workspace.prototype.upload = function () {
44062 this.MyModal.upload();
44064 Workspace.prototype.debug = function () {
44065 this.MyModal.preview();
44067 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
44070 controllers.Workspace = Workspace;
44071 })(controllers = app.controllers || (app.controllers = {}));
44072 })(app || (app = {}));
44076 (function (controllers) {
44077 var History = (function () {
44078 function History($scope) {
44079 this.page = "History";
44081 History.$inject = ['$scope'];
44084 controllers.History = History;
44085 })(controllers = app.controllers || (app.controllers = {}));
44086 })(app || (app = {}));
44090 (function (controllers) {
44091 var SelectCommand = (function () {
44092 function SelectCommand($scope, APIEndPoint, $modalInstance) {
44093 this.APIEndPoint = APIEndPoint;
44094 this.$modalInstance = $modalInstance;
44095 var controller = this;
44098 .$promise.then(function (result) {
44099 controller.tags = result.info;
44103 .$promise.then(function (result) {
44104 controller.commands = result.info;
44106 this.currentTag = 'all';
44108 SelectCommand.prototype.changeTag = function (tag) {
44109 this.currentTag = tag;
44111 SelectCommand.prototype.selectCommand = function (command) {
44112 this.$modalInstance.close(command);
44114 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
44115 return SelectCommand;
44117 controllers.SelectCommand = SelectCommand;
44118 })(controllers = app.controllers || (app.controllers = {}));
44119 })(app || (app = {}));
44123 (function (controllers) {
44124 var Upload = (function () {
44125 function Upload($scope, APIEndPoint, $modalInstance) {
44126 this.APIEndPoint = APIEndPoint;
44127 this.$modalInstance = $modalInstance;
44128 var controller = this;
44129 console.log('controller.upload-controllers');
44131 Upload.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
44134 controllers.Upload = Upload;
44135 })(controllers = app.controllers || (app.controllers = {}));
44136 })(app || (app = {}));
44140 (function (controllers) {
44141 var Preview = (function () {
44142 function Preview($scope, APIEndPoint, $modalInstance) {
44143 this.APIEndPoint = APIEndPoint;
44144 this.$modalInstance = $modalInstance;
44145 var controller = this;
44146 console.log('preview');
44148 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
44151 controllers.Preview = Preview;
44152 })(controllers = app.controllers || (app.controllers = {}));
44153 })(app || (app = {}));
44155 (function (filters) {
44157 return function (commands, tag) {
44159 angular.forEach(commands, function (command) {
44161 angular.forEach(command.tags, function (value) {
44166 result.push(command);
44172 })(filters || (filters = {}));
44176 var appName = 'zephyr';
44177 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
44178 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
44179 $urlRouterProvider.otherwise('/execution');
44180 $locationProvider.html5Mode({
44185 .state('execution', {
44187 templateUrl: 'templates/execution.html',
44188 controller: 'executionController',
44191 .state('workspace', {
44193 templateUrl: 'templates/workspace.html',
44194 controller: 'workspaceController',
44197 .state('history', {
44199 templateUrl: 'templates/history.html',
44200 controller: 'historyController',
44204 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
44205 app.zephyr.service('MyModal', app.services.MyModal);
44206 app.zephyr.service('WebSocket', app.services.WebSocket);
44207 app.zephyr.service('Console', app.services.Console);
44208 app.zephyr.filter('Tag', filters.Tag);
44209 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
44210 app.zephyr.controller('previewController', app.controllers.Preview);
44211 app.zephyr.controller('uploadController', app.controllers.Upload);
44212 app.zephyr.controller('executionController', app.controllers.Execution);
44213 app.zephyr.controller('workspaceController', app.controllers.Workspace);
44214 app.zephyr.controller('historyController', app.controllers.History);
44215 app.zephyr.controller('commandController', app.directives.CommandController);
44216 app.zephyr.controller('optionController', app.directives.OptionController);
44217 app.zephyr.controller('directoryController', app.directives.DirectoryController);
44218 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
44219 app.zephyr.controller('uploadController', app.directives.UploadController);
44220 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
44221 app.zephyr.directive('command', app.directives.Command.Factory());
44222 app.zephyr.directive('option', app.directives.Option.Factory());
44223 app.zephyr.directive('directory', app.directives.Directory.Factory());
44224 })(app || (app = {}));
44229 /***/ function(module, exports) {
44234 (function (declares) {
44235 var CommandInfo = (function () {
44236 function CommandInfo(name) {
44239 return CommandInfo;
44241 declares.CommandInfo = CommandInfo;
44242 })(declares = app.declares || (app.declares = {}));
44243 })(app || (app = {}));
44247 (function (services) {
44248 var APIEndPoint = (function () {
44249 function APIEndPoint($resource, $http) {
44250 this.$resource = $resource;
44251 this.$http = $http;
44253 APIEndPoint.prototype.resource = function (endPoint, data) {
44254 var customAction = {
44260 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
44262 return this.$resource(endPoint, {}, { execute: execute });
44264 APIEndPoint.prototype.getOptionControlFile = function (command) {
44265 var endPoint = '/api/v1/optionControlFile/' + command;
44266 return this.resource(endPoint, {}).get();
44268 APIEndPoint.prototype.getFiles = function (fileId) {
44269 var endPoint = '/api/v1/workspace';
44271 endPoint += '/' + fileId;
44273 return this.resource(endPoint, {}).get();
44275 APIEndPoint.prototype.getDirectories = function () {
44276 var endPoint = '/api/v1/all/workspace/directory';
44277 return this.resource(endPoint, {}).get();
44279 APIEndPoint.prototype.getTags = function () {
44280 var endPoint = '/api/v1/tagList';
44281 return this.resource(endPoint, {}).get();
44283 APIEndPoint.prototype.getCommands = function () {
44284 var endPoint = '/api/v1/commandList';
44285 return this.resource(endPoint, {}).get();
44287 APIEndPoint.prototype.execute = function (data) {
44288 var endPoint = '/api/v1/execution';
44289 var fd = new FormData();
44290 fd.append('data', data);
44291 return this.$http.post(endPoint, fd, {
44292 headers: { 'Content-Type': undefined },
44293 transformRequest: angular.identity
44296 APIEndPoint.prototype.debug = function () {
44297 var endPoint = '/api/v1/debug';
44298 return this.$http.get(endPoint);
44300 APIEndPoint.prototype.upload = function () {
44301 var endPoint = '/api/v1/upload';
44302 return this.$http.get(endPoint);
44304 APIEndPoint.prototype.help = function (command) {
44305 var endPoint = '/api/v1/help/' + command;
44306 return this.$http.get(endPoint);
44308 return APIEndPoint;
44310 services.APIEndPoint = APIEndPoint;
44311 })(services = app.services || (app.services = {}));
44312 })(app || (app = {}));
44316 (function (services) {
44317 var MyModal = (function () {
44318 function MyModal($uibModal) {
44319 this.$uibModal = $uibModal;
44320 this.modalOption = {
44327 MyModal.prototype.open = function (modalName) {
44328 if (modalName === 'SelectCommand') {
44329 this.modalOption.templateUrl = 'templates/select-command.html';
44330 this.modalOption.size = 'lg';
44332 return this.$uibModal.open(this.modalOption);
44334 MyModal.prototype.selectCommand = function () {
44335 this.modalOption.templateUrl = 'templates/select-command.html';
44336 this.modalOption.controller = 'selectCommandController';
44337 this.modalOption.controllerAs = 'c';
44338 this.modalOption.size = 'lg';
44339 return this.$uibModal.open(this.modalOption);
44341 MyModal.prototype.preview = function () {
44342 this.modalOption.templateUrl = 'templates/preview.html';
44343 this.modalOption.controller = 'previewController';
44344 this.modalOption.controllerAs = 'c';
44345 this.modalOption.size = 'lg';
44346 return this.$uibModal.open(this.modalOption);
44348 MyModal.prototype.upload = function () {
44349 this.modalOption.templateUrl = 'templates/upload.html';
44350 this.modalOption.controller = 'uploadController';
44351 this.modalOption.controllerAs = 'c';
44352 this.modalOption.size = 'lg';
44353 return this.$uibModal.open(this.modalOption);
44355 MyModal.$inject = ['$uibModal'];
44358 services.MyModal = MyModal;
44359 })(services = app.services || (app.services = {}));
44360 })(app || (app = {}));
44364 (function (services) {
44365 var WebSocket = (function () {
44366 function WebSocket($rootScope) {
44367 this.$rootScope = $rootScope;
44368 this.socket = io.connect();
44370 WebSocket.prototype.on = function (eventName, callback) {
44371 var socket = this.socket;
44372 var rootScope = this.$rootScope;
44373 socket.on(eventName, function () {
44374 var args = arguments;
44375 rootScope.$apply(function () {
44376 callback.apply(socket, args);
44380 WebSocket.prototype.emit = function (eventName, data, callback) {
44381 var socket = this.socket;
44382 var rootScope = this.$rootScope;
44383 this.socket.emit(eventName, data, function () {
44384 var args = arguments;
44385 rootScope.$apply(function () {
44387 callback.apply(socket, args);
44393 services.WebSocket = WebSocket;
44394 })(services = app.services || (app.services = {}));
44395 })(app || (app = {}));
44399 (function (services) {
44400 var Console = (function () {
44401 function Console(WebSocket, $rootScope) {
44402 this.WebSocket = WebSocket;
44403 this.$rootScope = $rootScope;
44404 this.WebSocket = WebSocket;
44405 this.$rootScope = $rootScope;
44406 this.directiveIDs = [];
44407 var directiveIDs = this.directiveIDs;
44408 this.WebSocket.on('console', function (d) {
44410 var message = d.message;
44411 if (directiveIDs.indexOf(id) > -1) {
44412 $rootScope.$emit(id, message);
44416 Console.prototype.addDirective = function (id) {
44417 if (!(this.directiveIDs.indexOf(id) > -1)) {
44418 this.directiveIDs.push(id);
44421 Console.prototype.removeDirective = function (id) {
44422 var i = this.directiveIDs.indexOf(id);
44424 this.directiveIDs.splice(i, 1);
44427 Console.prototype.showIDs = function () {
44428 console.log(this.directiveIDs);
44432 services.Console = Console;
44433 })(services = app.services || (app.services = {}));
44434 })(app || (app = {}));
44438 (function (directives) {
44439 var Command = (function () {
44440 function Command() {
44441 this.restrict = 'E';
44442 this.replace = true;
44444 this.controller = 'commandController';
44445 this.controllerAs = 'ctrl';
44446 this.bindToController = {
44452 this.templateUrl = 'templates/command.html';
44454 Command.Factory = function () {
44455 var directive = function () {
44456 return new Command();
44458 directive.$inject = [];
44463 directives.Command = Command;
44464 var CommandController = (function () {
44465 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
44466 this.APIEndPoint = APIEndPoint;
44467 this.$scope = $scope;
44468 this.MyModal = MyModal;
44469 this.WebSocket = WebSocket;
44470 this.$window = $window;
44471 this.$rootScope = $rootScope;
44472 this.Console = Console;
44473 var controller = this;
44475 .getOptionControlFile(this.name)
44477 .then(function (result) {
44478 controller.options = result.info;
44483 .then(function (result) {
44484 controller.dirs = result.info;
44486 this.heading = "[" + this.index + "]: dcdFilePrint";
44487 this.isOpen = true;
44488 this.$scope.$on('close', function () {
44489 controller.isOpen = false;
44493 return Math.floor((1 + Math.random()) * 0x10000)
44497 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
44498 s4() + '-' + s4() + s4() + s4();
44500 this.uuid = guid();
44501 this.Console.addDirective(this.uuid);
44502 this.Console.showIDs();
44504 CommandController.prototype.submit = function () {
44506 angular.forEach(this.options, function (option) {
44508 name: option.option,
44511 angular.forEach(option.arg, function (arg) {
44513 if (typeof arg.input === 'object') {
44514 obj.arguments.push(arg.input.name);
44517 obj.arguments.push(arg.input);
44521 if (obj.arguments.length > 0) {
44526 command: this.name,
44527 workspace: this.workspace.fileId,
44531 .execute(JSON.stringify(execObj))
44532 .then(function (result) {
44533 console.log(result);
44536 CommandController.prototype.removeMySelf = function (index) {
44537 this.$scope.$destroy();
44538 this.Console.removeDirective(this.uuid);
44539 this.remove()(index, this.list);
44540 this.Console.showIDs();
44542 CommandController.prototype.reloadFiles = function () {
44544 var fileId = this.workspace.fileId;
44548 .then(function (result) {
44549 var status = result.status;
44550 if (status === 'success') {
44551 _this.files = result.info;
44554 console.log(result.message);
44558 CommandController.prototype.debug = function () {
44559 var div = angular.element(this.$window.document).find("div");
44562 angular.forEach(div, function (v) {
44563 if (v.className === "panel-body console") {
44566 else if (v.className === "row parameters-console") {
44570 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
44571 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
44572 consoleTag.style.height = consoleHeight;
44573 consoleTag.style.width = consoleWidth;
44575 CommandController.prototype.help = function () {
44578 .then(function (result) {
44579 console.log(result);
44582 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
44583 return CommandController;
44585 directives.CommandController = CommandController;
44586 })(directives = app.directives || (app.directives = {}));
44587 })(app || (app = {}));
44591 (function (directives) {
44592 var HeaderMenu = (function () {
44593 function HeaderMenu() {
44594 this.restrict = 'E';
44595 this.replace = true;
44596 this.templateUrl = 'templates/header-menu.html';
44597 this.controller = 'HeaderMenuController';
44598 this.controllerAs = 'hmc';
44601 HeaderMenu.Factory = function () {
44602 var directive = function () {
44603 return new HeaderMenu();
44609 directives.HeaderMenu = HeaderMenu;
44610 var HeaderMenuController = (function () {
44611 function HeaderMenuController($state) {
44612 this.$state = $state;
44613 this.isExecution = this.$state.current.name === 'execution';
44614 this.isWorkspace = this.$state.current.name === 'workspace';
44615 this.isHistory = this.$state.current.name === 'history';
44617 HeaderMenuController.prototype.transit = function (state) {
44618 this.$state.go(state);
44620 HeaderMenuController.$inject = ['$state'];
44621 return HeaderMenuController;
44623 directives.HeaderMenuController = HeaderMenuController;
44624 })(directives = app.directives || (app.directives = {}));
44625 })(app || (app = {}));
44629 (function (directives) {
44630 var Option = (function () {
44631 function Option() {
44632 this.restrict = 'E';
44633 this.replace = true;
44634 this.controller = 'optionController';
44635 this.bindToController = {
44640 this.templateUrl = 'templates/option.html';
44641 this.controllerAs = 'ctrl';
44643 Option.Factory = function () {
44644 var directive = function () {
44645 return new Option();
44647 directive.$inject = [];
44652 directives.Option = Option;
44653 var OptionController = (function () {
44654 function OptionController() {
44655 var controller = this;
44656 angular.forEach(controller.info.arg, function (arg) {
44657 if (arg.initialValue) {
44658 if (arg.formType === 'number') {
44659 arg.input = parseInt(arg.initialValue);
44662 arg.input = arg.initialValue;
44667 OptionController.$inject = [];
44668 return OptionController;
44670 directives.OptionController = OptionController;
44671 })(directives = app.directives || (app.directives = {}));
44672 })(app || (app = {}));
44676 (function (directives) {
44677 var Directory = (function () {
44678 function Directory() {
44679 this.restrict = 'E';
44680 this.replace = true;
44681 this.controller = 'directoryController';
44682 this.controllerAs = 'ctrl';
44683 this.bindToController = {
44689 this.templateUrl = 'templates/directory.html';
44691 Directory.Factory = function () {
44692 var directive = function () {
44693 return new Directory();
44699 directives.Directory = Directory;
44700 var DirectoryController = (function () {
44701 function DirectoryController(APIEndPoint, $scope) {
44702 this.APIEndPoint = APIEndPoint;
44703 this.$scope = $scope;
44704 var controller = this;
44706 .getFiles(this.info.fileId)
44708 .then(function (result) {
44709 if (result.status === 'success') {
44710 controller.files = result.info;
44711 angular.forEach(result.info, function (file) {
44712 if (file.fileType === '0') {
44714 if (controller.info.path === '/') {
44715 o.path = '/' + file.name;
44718 o.path = controller.info.path + '/' + file.name;
44720 controller.add()(o, controller.list);
44727 DirectoryController.$inject = ['APIEndPoint', '$scope'];
44728 return DirectoryController;
44730 directives.DirectoryController = DirectoryController;
44731 })(directives = app.directives || (app.directives = {}));
44732 })(app || (app = {}));
44736 (function (directives) {
44737 var Upload = (function () {
44738 function Upload() {
44739 this.restrict = 'E';
44740 this.replace = true;
44742 this.controller = 'UploadController';
44743 this.controllerAs = 'ctrl';
44744 this.bindToController = {
44750 this.templateUrl = 'templates/upload.html';
44751 console.log("templates/upload.html-constructor");
44753 Upload.Factory = function () {
44754 var directive = function () {
44755 return new Upload();
44757 directive.$inject = [];
44762 directives.Upload = Upload;
44763 var UploadController = (function () {
44764 function UploadController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
44765 this.APIEndPoint = APIEndPoint;
44766 this.$scope = $scope;
44767 this.MyModal = MyModal;
44768 this.WebSocket = WebSocket;
44769 this.$window = $window;
44770 this.$rootScope = $rootScope;
44771 this.Console = Console;
44772 var controller = this;
44773 console.log("directive.upload-constructor");
44775 UploadController.prototype.submit = function () {
44776 console.log("submit: function not supported¥n");
44778 UploadController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
44779 return UploadController;
44781 directives.UploadController = UploadController;
44782 })(directives = app.directives || (app.directives = {}));
44783 })(app || (app = {}));
44787 (function (controllers) {
44788 var Execution = (function () {
44789 function Execution(MyModal, $scope) {
44790 this.MyModal = MyModal;
44791 this.$scope = $scope;
44792 this.commandInfoList = [];
44795 Execution.prototype.add = function () {
44796 this.$scope.$broadcast('close');
44797 var commandInfoList = this.commandInfoList;
44798 var commandInstance = this.MyModal.selectCommand();
44801 .then(function (command) {
44802 commandInfoList.push(new app.declares.CommandInfo(command));
44805 Execution.prototype.open = function () {
44806 var result = this.MyModal.open('SelectCommand');
44807 console.log(result);
44809 Execution.prototype.remove = function (index, list) {
44810 list.splice(index, 1);
44812 Execution.prototype.close = function () {
44813 console.log("close");
44815 Execution.$inject = ['MyModal', '$scope'];
44818 controllers.Execution = Execution;
44819 })(controllers = app.controllers || (app.controllers = {}));
44820 })(app || (app = {}));
44824 (function (controllers) {
44825 var Workspace = (function () {
44826 function Workspace($scope, APIEndPoint, MyModal) {
44827 this.$scope = $scope;
44828 this.APIEndPoint = APIEndPoint;
44829 this.MyModal = MyModal;
44830 this.directoryList = [];
44831 var controller = this;
44832 var directoryList = this.directoryList;
44834 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
44842 directoryList.push(o);
44844 Workspace.prototype.addDirectory = function (info, directoryList) {
44845 directoryList.push(info);
44847 Workspace.prototype.upload = function () {
44848 this.MyModal.upload();
44850 Workspace.prototype.debug = function () {
44851 this.MyModal.preview();
44853 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
44856 controllers.Workspace = Workspace;
44857 })(controllers = app.controllers || (app.controllers = {}));
44858 })(app || (app = {}));
44862 (function (controllers) {
44863 var History = (function () {
44864 function History($scope) {
44865 this.page = "History";
44867 History.$inject = ['$scope'];
44870 controllers.History = History;
44871 })(controllers = app.controllers || (app.controllers = {}));
44872 })(app || (app = {}));
44876 (function (controllers) {
44877 var SelectCommand = (function () {
44878 function SelectCommand($scope, APIEndPoint, $modalInstance) {
44879 this.APIEndPoint = APIEndPoint;
44880 this.$modalInstance = $modalInstance;
44881 var controller = this;
44884 .$promise.then(function (result) {
44885 controller.tags = result.info;
44889 .$promise.then(function (result) {
44890 controller.commands = result.info;
44892 this.currentTag = 'all';
44894 SelectCommand.prototype.changeTag = function (tag) {
44895 this.currentTag = tag;
44897 SelectCommand.prototype.selectCommand = function (command) {
44898 this.$modalInstance.close(command);
44900 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
44901 return SelectCommand;
44903 controllers.SelectCommand = SelectCommand;
44904 })(controllers = app.controllers || (app.controllers = {}));
44905 })(app || (app = {}));
44909 (function (controllers) {
44910 var Upload = (function () {
44911 function Upload($scope, APIEndPoint, $modalInstance) {
44912 this.APIEndPoint = APIEndPoint;
44913 this.$modalInstance = $modalInstance;
44914 var controller = this;
44915 console.log('controller.upload-controllers');
44917 Upload.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
44920 controllers.Upload = Upload;
44921 })(controllers = app.controllers || (app.controllers = {}));
44922 })(app || (app = {}));
44926 (function (controllers) {
44927 var Preview = (function () {
44928 function Preview($scope, APIEndPoint, $modalInstance) {
44929 this.APIEndPoint = APIEndPoint;
44930 this.$modalInstance = $modalInstance;
44931 var controller = this;
44932 console.log('preview');
44934 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
44937 controllers.Preview = Preview;
44938 })(controllers = app.controllers || (app.controllers = {}));
44939 })(app || (app = {}));
44941 (function (filters) {
44943 return function (commands, tag) {
44945 angular.forEach(commands, function (command) {
44947 angular.forEach(command.tags, function (value) {
44952 result.push(command);
44958 })(filters || (filters = {}));
44962 var appName = 'zephyr';
44963 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
44964 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
44965 $urlRouterProvider.otherwise('/execution');
44966 $locationProvider.html5Mode({
44971 .state('execution', {
44973 templateUrl: 'templates/execution.html',
44974 controller: 'executionController',
44977 .state('workspace', {
44979 templateUrl: 'templates/workspace.html',
44980 controller: 'workspaceController',
44983 .state('history', {
44985 templateUrl: 'templates/history.html',
44986 controller: 'historyController',
44990 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
44991 app.zephyr.service('MyModal', app.services.MyModal);
44992 app.zephyr.service('WebSocket', app.services.WebSocket);
44993 app.zephyr.service('Console', app.services.Console);
44994 app.zephyr.filter('Tag', filters.Tag);
44995 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
44996 app.zephyr.controller('previewController', app.controllers.Preview);
44997 app.zephyr.controller('uploadController', app.controllers.Upload);
44998 app.zephyr.controller('executionController', app.controllers.Execution);
44999 app.zephyr.controller('workspaceController', app.controllers.Workspace);
45000 app.zephyr.controller('historyController', app.controllers.History);
45001 app.zephyr.controller('commandController', app.directives.CommandController);
45002 app.zephyr.controller('optionController', app.directives.OptionController);
45003 app.zephyr.controller('directoryController', app.directives.DirectoryController);
45004 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
45005 app.zephyr.controller('uploadController', app.directives.UploadController);
45006 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
45007 app.zephyr.directive('command', app.directives.Command.Factory());
45008 app.zephyr.directive('option', app.directives.Option.Factory());
45009 app.zephyr.directive('directory', app.directives.Directory.Factory());
45010 })(app || (app = {}));
45015 /***/ function(module, exports) {
45020 (function (declares) {
45021 var CommandInfo = (function () {
45022 function CommandInfo(name) {
45025 return CommandInfo;
45027 declares.CommandInfo = CommandInfo;
45028 })(declares = app.declares || (app.declares = {}));
45029 })(app || (app = {}));
45033 (function (services) {
45034 var APIEndPoint = (function () {
45035 function APIEndPoint($resource, $http) {
45036 this.$resource = $resource;
45037 this.$http = $http;
45039 APIEndPoint.prototype.resource = function (endPoint, data) {
45040 var customAction = {
45046 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
45048 return this.$resource(endPoint, {}, { execute: execute });
45050 APIEndPoint.prototype.getOptionControlFile = function (command) {
45051 var endPoint = '/api/v1/optionControlFile/' + command;
45052 return this.resource(endPoint, {}).get();
45054 APIEndPoint.prototype.getFiles = function (fileId) {
45055 var endPoint = '/api/v1/workspace';
45057 endPoint += '/' + fileId;
45059 return this.resource(endPoint, {}).get();
45061 APIEndPoint.prototype.getDirectories = function () {
45062 var endPoint = '/api/v1/all/workspace/directory';
45063 return this.resource(endPoint, {}).get();
45065 APIEndPoint.prototype.getTags = function () {
45066 var endPoint = '/api/v1/tagList';
45067 return this.resource(endPoint, {}).get();
45069 APIEndPoint.prototype.getCommands = function () {
45070 var endPoint = '/api/v1/commandList';
45071 return this.resource(endPoint, {}).get();
45073 APIEndPoint.prototype.execute = function (data) {
45074 var endPoint = '/api/v1/execution';
45075 var fd = new FormData();
45076 fd.append('data', data);
45077 return this.$http.post(endPoint, fd, {
45078 headers: { 'Content-Type': undefined },
45079 transformRequest: angular.identity
45082 APIEndPoint.prototype.debug = function () {
45083 var endPoint = '/api/v1/debug';
45084 return this.$http.get(endPoint);
45086 APIEndPoint.prototype.upload = function () {
45087 var endPoint = '/api/v1/upload';
45088 return this.$http.get(endPoint);
45090 APIEndPoint.prototype.help = function (command) {
45091 var endPoint = '/api/v1/help/' + command;
45092 return this.$http.get(endPoint);
45094 return APIEndPoint;
45096 services.APIEndPoint = APIEndPoint;
45097 })(services = app.services || (app.services = {}));
45098 })(app || (app = {}));
45102 (function (services) {
45103 var MyModal = (function () {
45104 function MyModal($uibModal) {
45105 this.$uibModal = $uibModal;
45106 this.modalOption = {
45113 MyModal.prototype.open = function (modalName) {
45114 if (modalName === 'SelectCommand') {
45115 this.modalOption.templateUrl = 'templates/select-command.html';
45116 this.modalOption.size = 'lg';
45118 return this.$uibModal.open(this.modalOption);
45120 MyModal.prototype.selectCommand = function () {
45121 this.modalOption.templateUrl = 'templates/select-command.html';
45122 this.modalOption.controller = 'selectCommandController';
45123 this.modalOption.controllerAs = 'c';
45124 this.modalOption.size = 'lg';
45125 return this.$uibModal.open(this.modalOption);
45127 MyModal.prototype.preview = function () {
45128 this.modalOption.templateUrl = 'templates/preview.html';
45129 this.modalOption.controller = 'previewController';
45130 this.modalOption.controllerAs = 'c';
45131 this.modalOption.size = 'lg';
45132 return this.$uibModal.open(this.modalOption);
45134 MyModal.prototype.upload = function () {
45135 this.modalOption.templateUrl = 'templates/upload.html';
45136 this.modalOption.controller = 'uploadController';
45137 this.modalOption.controllerAs = 'c';
45138 this.modalOption.size = 'lg';
45139 return this.$uibModal.open(this.modalOption);
45141 MyModal.$inject = ['$uibModal'];
45144 services.MyModal = MyModal;
45145 })(services = app.services || (app.services = {}));
45146 })(app || (app = {}));
45150 (function (services) {
45151 var WebSocket = (function () {
45152 function WebSocket($rootScope) {
45153 this.$rootScope = $rootScope;
45154 this.socket = io.connect();
45156 WebSocket.prototype.on = function (eventName, callback) {
45157 var socket = this.socket;
45158 var rootScope = this.$rootScope;
45159 socket.on(eventName, function () {
45160 var args = arguments;
45161 rootScope.$apply(function () {
45162 callback.apply(socket, args);
45166 WebSocket.prototype.emit = function (eventName, data, callback) {
45167 var socket = this.socket;
45168 var rootScope = this.$rootScope;
45169 this.socket.emit(eventName, data, function () {
45170 var args = arguments;
45171 rootScope.$apply(function () {
45173 callback.apply(socket, args);
45179 services.WebSocket = WebSocket;
45180 })(services = app.services || (app.services = {}));
45181 })(app || (app = {}));
45185 (function (services) {
45186 var Console = (function () {
45187 function Console(WebSocket, $rootScope) {
45188 this.WebSocket = WebSocket;
45189 this.$rootScope = $rootScope;
45190 this.WebSocket = WebSocket;
45191 this.$rootScope = $rootScope;
45192 this.directiveIDs = [];
45193 var directiveIDs = this.directiveIDs;
45194 this.WebSocket.on('console', function (d) {
45196 var message = d.message;
45197 if (directiveIDs.indexOf(id) > -1) {
45198 $rootScope.$emit(id, message);
45202 Console.prototype.addDirective = function (id) {
45203 if (!(this.directiveIDs.indexOf(id) > -1)) {
45204 this.directiveIDs.push(id);
45207 Console.prototype.removeDirective = function (id) {
45208 var i = this.directiveIDs.indexOf(id);
45210 this.directiveIDs.splice(i, 1);
45213 Console.prototype.showIDs = function () {
45214 console.log(this.directiveIDs);
45218 services.Console = Console;
45219 })(services = app.services || (app.services = {}));
45220 })(app || (app = {}));
45224 (function (directives) {
45225 var Command = (function () {
45226 function Command() {
45227 this.restrict = 'E';
45228 this.replace = true;
45230 this.controller = 'commandController';
45231 this.controllerAs = 'ctrl';
45232 this.bindToController = {
45238 this.templateUrl = 'templates/command.html';
45240 Command.Factory = function () {
45241 var directive = function () {
45242 return new Command();
45244 directive.$inject = [];
45249 directives.Command = Command;
45250 var CommandController = (function () {
45251 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
45252 this.APIEndPoint = APIEndPoint;
45253 this.$scope = $scope;
45254 this.MyModal = MyModal;
45255 this.WebSocket = WebSocket;
45256 this.$window = $window;
45257 this.$rootScope = $rootScope;
45258 this.Console = Console;
45259 var controller = this;
45261 .getOptionControlFile(this.name)
45263 .then(function (result) {
45264 controller.options = result.info;
45269 .then(function (result) {
45270 controller.dirs = result.info;
45272 this.heading = "[" + this.index + "]: dcdFilePrint";
45273 this.isOpen = true;
45274 this.$scope.$on('close', function () {
45275 controller.isOpen = false;
45279 return Math.floor((1 + Math.random()) * 0x10000)
45283 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
45284 s4() + '-' + s4() + s4() + s4();
45286 this.uuid = guid();
45287 this.Console.addDirective(this.uuid);
45288 this.Console.showIDs();
45290 CommandController.prototype.submit = function () {
45292 angular.forEach(this.options, function (option) {
45294 name: option.option,
45297 angular.forEach(option.arg, function (arg) {
45299 if (typeof arg.input === 'object') {
45300 obj.arguments.push(arg.input.name);
45303 obj.arguments.push(arg.input);
45307 if (obj.arguments.length > 0) {
45312 command: this.name,
45313 workspace: this.workspace.fileId,
45317 .execute(JSON.stringify(execObj))
45318 .then(function (result) {
45319 console.log(result);
45322 CommandController.prototype.removeMySelf = function (index) {
45323 this.$scope.$destroy();
45324 this.Console.removeDirective(this.uuid);
45325 this.remove()(index, this.list);
45326 this.Console.showIDs();
45328 CommandController.prototype.reloadFiles = function () {
45330 var fileId = this.workspace.fileId;
45334 .then(function (result) {
45335 var status = result.status;
45336 if (status === 'success') {
45337 _this.files = result.info;
45340 console.log(result.message);
45344 CommandController.prototype.debug = function () {
45345 var div = angular.element(this.$window.document).find("div");
45348 angular.forEach(div, function (v) {
45349 if (v.className === "panel-body console") {
45352 else if (v.className === "row parameters-console") {
45356 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
45357 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
45358 consoleTag.style.height = consoleHeight;
45359 consoleTag.style.width = consoleWidth;
45361 CommandController.prototype.help = function () {
45364 .then(function (result) {
45365 console.log(result);
45368 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
45369 return CommandController;
45371 directives.CommandController = CommandController;
45372 })(directives = app.directives || (app.directives = {}));
45373 })(app || (app = {}));
45377 (function (directives) {
45378 var HeaderMenu = (function () {
45379 function HeaderMenu() {
45380 this.restrict = 'E';
45381 this.replace = true;
45382 this.templateUrl = 'templates/header-menu.html';
45383 this.controller = 'HeaderMenuController';
45384 this.controllerAs = 'hmc';
45387 HeaderMenu.Factory = function () {
45388 var directive = function () {
45389 return new HeaderMenu();
45395 directives.HeaderMenu = HeaderMenu;
45396 var HeaderMenuController = (function () {
45397 function HeaderMenuController($state) {
45398 this.$state = $state;
45399 this.isExecution = this.$state.current.name === 'execution';
45400 this.isWorkspace = this.$state.current.name === 'workspace';
45401 this.isHistory = this.$state.current.name === 'history';
45403 HeaderMenuController.prototype.transit = function (state) {
45404 this.$state.go(state);
45406 HeaderMenuController.$inject = ['$state'];
45407 return HeaderMenuController;
45409 directives.HeaderMenuController = HeaderMenuController;
45410 })(directives = app.directives || (app.directives = {}));
45411 })(app || (app = {}));
45415 (function (directives) {
45416 var Option = (function () {
45417 function Option() {
45418 this.restrict = 'E';
45419 this.replace = true;
45420 this.controller = 'optionController';
45421 this.bindToController = {
45426 this.templateUrl = 'templates/option.html';
45427 this.controllerAs = 'ctrl';
45429 Option.Factory = function () {
45430 var directive = function () {
45431 return new Option();
45433 directive.$inject = [];
45438 directives.Option = Option;
45439 var OptionController = (function () {
45440 function OptionController() {
45441 var controller = this;
45442 angular.forEach(controller.info.arg, function (arg) {
45443 if (arg.initialValue) {
45444 if (arg.formType === 'number') {
45445 arg.input = parseInt(arg.initialValue);
45448 arg.input = arg.initialValue;
45453 OptionController.$inject = [];
45454 return OptionController;
45456 directives.OptionController = OptionController;
45457 })(directives = app.directives || (app.directives = {}));
45458 })(app || (app = {}));
45462 (function (directives) {
45463 var Directory = (function () {
45464 function Directory() {
45465 this.restrict = 'E';
45466 this.replace = true;
45467 this.controller = 'directoryController';
45468 this.controllerAs = 'ctrl';
45469 this.bindToController = {
45475 this.templateUrl = 'templates/directory.html';
45477 Directory.Factory = function () {
45478 var directive = function () {
45479 return new Directory();
45485 directives.Directory = Directory;
45486 var DirectoryController = (function () {
45487 function DirectoryController(APIEndPoint, $scope) {
45488 this.APIEndPoint = APIEndPoint;
45489 this.$scope = $scope;
45490 var controller = this;
45492 .getFiles(this.info.fileId)
45494 .then(function (result) {
45495 if (result.status === 'success') {
45496 controller.files = result.info;
45497 angular.forEach(result.info, function (file) {
45498 if (file.fileType === '0') {
45500 if (controller.info.path === '/') {
45501 o.path = '/' + file.name;
45504 o.path = controller.info.path + '/' + file.name;
45506 controller.add()(o, controller.list);
45513 DirectoryController.$inject = ['APIEndPoint', '$scope'];
45514 return DirectoryController;
45516 directives.DirectoryController = DirectoryController;
45517 })(directives = app.directives || (app.directives = {}));
45518 })(app || (app = {}));
45522 (function (directives) {
45523 var Upload = (function () {
45524 function Upload() {
45525 this.restrict = 'E';
45526 this.replace = true;
45528 this.controller = 'UploadController';
45529 this.controllerAs = 'ctrl';
45530 this.bindToController = {
45536 this.templateUrl = 'templates/upload.html';
45537 console.log("templates/upload.html-constructor");
45539 Upload.Factory = function () {
45540 var directive = function () {
45541 return new Upload();
45543 directive.$inject = [];
45548 directives.Upload = Upload;
45549 var UploadController = (function () {
45550 function UploadController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
45551 this.APIEndPoint = APIEndPoint;
45552 this.$scope = $scope;
45553 this.MyModal = MyModal;
45554 this.WebSocket = WebSocket;
45555 this.$window = $window;
45556 this.$rootScope = $rootScope;
45557 this.Console = Console;
45558 var controller = this;
45559 console.log("directive.upload-constructor");
45561 UploadController.prototype.submit = function () {
45562 console.log("submit: function not supported¥n");
45564 UploadController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
45565 return UploadController;
45567 directives.UploadController = UploadController;
45568 })(directives = app.directives || (app.directives = {}));
45569 })(app || (app = {}));
45573 (function (controllers) {
45574 var Execution = (function () {
45575 function Execution(MyModal, $scope) {
45576 this.MyModal = MyModal;
45577 this.$scope = $scope;
45578 this.commandInfoList = [];
45581 Execution.prototype.add = function () {
45582 this.$scope.$broadcast('close');
45583 var commandInfoList = this.commandInfoList;
45584 var commandInstance = this.MyModal.selectCommand();
45587 .then(function (command) {
45588 commandInfoList.push(new app.declares.CommandInfo(command));
45591 Execution.prototype.open = function () {
45592 var result = this.MyModal.open('SelectCommand');
45593 console.log(result);
45595 Execution.prototype.remove = function (index, list) {
45596 list.splice(index, 1);
45598 Execution.prototype.close = function () {
45599 console.log("close");
45601 Execution.$inject = ['MyModal', '$scope'];
45604 controllers.Execution = Execution;
45605 })(controllers = app.controllers || (app.controllers = {}));
45606 })(app || (app = {}));
45610 (function (controllers) {
45611 var Workspace = (function () {
45612 function Workspace($scope, APIEndPoint, MyModal) {
45613 this.$scope = $scope;
45614 this.APIEndPoint = APIEndPoint;
45615 this.MyModal = MyModal;
45616 this.directoryList = [];
45617 var controller = this;
45618 var directoryList = this.directoryList;
45620 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
45628 directoryList.push(o);
45630 Workspace.prototype.addDirectory = function (info, directoryList) {
45631 directoryList.push(info);
45633 Workspace.prototype.upload = function () {
45634 this.MyModal.upload();
45636 Workspace.prototype.debug = function () {
45637 this.MyModal.preview();
45639 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
45642 controllers.Workspace = Workspace;
45643 })(controllers = app.controllers || (app.controllers = {}));
45644 })(app || (app = {}));
45648 (function (controllers) {
45649 var History = (function () {
45650 function History($scope) {
45651 this.page = "History";
45653 History.$inject = ['$scope'];
45656 controllers.History = History;
45657 })(controllers = app.controllers || (app.controllers = {}));
45658 })(app || (app = {}));
45662 (function (controllers) {
45663 var SelectCommand = (function () {
45664 function SelectCommand($scope, APIEndPoint, $modalInstance) {
45665 this.APIEndPoint = APIEndPoint;
45666 this.$modalInstance = $modalInstance;
45667 var controller = this;
45670 .$promise.then(function (result) {
45671 controller.tags = result.info;
45675 .$promise.then(function (result) {
45676 controller.commands = result.info;
45678 this.currentTag = 'all';
45680 SelectCommand.prototype.changeTag = function (tag) {
45681 this.currentTag = tag;
45683 SelectCommand.prototype.selectCommand = function (command) {
45684 this.$modalInstance.close(command);
45686 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
45687 return SelectCommand;
45689 controllers.SelectCommand = SelectCommand;
45690 })(controllers = app.controllers || (app.controllers = {}));
45691 })(app || (app = {}));
45695 (function (controllers) {
45696 var Upload = (function () {
45697 function Upload($scope, APIEndPoint, $modalInstance) {
45698 this.APIEndPoint = APIEndPoint;
45699 this.$modalInstance = $modalInstance;
45700 var controller = this;
45701 console.log('controller.upload-controllers');
45703 Upload.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
45706 controllers.Upload = Upload;
45707 })(controllers = app.controllers || (app.controllers = {}));
45708 })(app || (app = {}));
45712 (function (controllers) {
45713 var Preview = (function () {
45714 function Preview($scope, APIEndPoint, $modalInstance) {
45715 this.APIEndPoint = APIEndPoint;
45716 this.$modalInstance = $modalInstance;
45717 var controller = this;
45718 console.log('preview');
45720 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
45723 controllers.Preview = Preview;
45724 })(controllers = app.controllers || (app.controllers = {}));
45725 })(app || (app = {}));
45727 (function (filters) {
45729 return function (commands, tag) {
45731 angular.forEach(commands, function (command) {
45733 angular.forEach(command.tags, function (value) {
45738 result.push(command);
45744 })(filters || (filters = {}));
45748 var appName = 'zephyr';
45749 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
45750 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
45751 $urlRouterProvider.otherwise('/execution');
45752 $locationProvider.html5Mode({
45757 .state('execution', {
45759 templateUrl: 'templates/execution.html',
45760 controller: 'executionController',
45763 .state('workspace', {
45765 templateUrl: 'templates/workspace.html',
45766 controller: 'workspaceController',
45769 .state('history', {
45771 templateUrl: 'templates/history.html',
45772 controller: 'historyController',
45776 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
45777 app.zephyr.service('MyModal', app.services.MyModal);
45778 app.zephyr.service('WebSocket', app.services.WebSocket);
45779 app.zephyr.service('Console', app.services.Console);
45780 app.zephyr.filter('Tag', filters.Tag);
45781 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
45782 app.zephyr.controller('previewController', app.controllers.Preview);
45783 app.zephyr.controller('uploadController', app.controllers.Upload);
45784 app.zephyr.controller('executionController', app.controllers.Execution);
45785 app.zephyr.controller('workspaceController', app.controllers.Workspace);
45786 app.zephyr.controller('historyController', app.controllers.History);
45787 app.zephyr.controller('commandController', app.directives.CommandController);
45788 app.zephyr.controller('optionController', app.directives.OptionController);
45789 app.zephyr.controller('directoryController', app.directives.DirectoryController);
45790 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
45791 app.zephyr.controller('uploadController', app.directives.UploadController);
45792 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
45793 app.zephyr.directive('command', app.directives.Command.Factory());
45794 app.zephyr.directive('option', app.directives.Option.Factory());
45795 app.zephyr.directive('directory', app.directives.Directory.Factory());
45796 })(app || (app = {}));
45801 /***/ function(module, exports) {
45806 (function (declares) {
45807 var CommandInfo = (function () {
45808 function CommandInfo(name) {
45811 return CommandInfo;
45813 declares.CommandInfo = CommandInfo;
45814 })(declares = app.declares || (app.declares = {}));
45815 })(app || (app = {}));
45819 (function (services) {
45820 var APIEndPoint = (function () {
45821 function APIEndPoint($resource, $http) {
45822 this.$resource = $resource;
45823 this.$http = $http;
45825 APIEndPoint.prototype.resource = function (endPoint, data) {
45826 var customAction = {
45832 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
45834 return this.$resource(endPoint, {}, { execute: execute });
45836 APIEndPoint.prototype.getOptionControlFile = function (command) {
45837 var endPoint = '/api/v1/optionControlFile/' + command;
45838 return this.resource(endPoint, {}).get();
45840 APIEndPoint.prototype.getFiles = function (fileId) {
45841 var endPoint = '/api/v1/workspace';
45843 endPoint += '/' + fileId;
45845 return this.resource(endPoint, {}).get();
45847 APIEndPoint.prototype.getDirectories = function () {
45848 var endPoint = '/api/v1/all/workspace/directory';
45849 return this.resource(endPoint, {}).get();
45851 APIEndPoint.prototype.getTags = function () {
45852 var endPoint = '/api/v1/tagList';
45853 return this.resource(endPoint, {}).get();
45855 APIEndPoint.prototype.getCommands = function () {
45856 var endPoint = '/api/v1/commandList';
45857 return this.resource(endPoint, {}).get();
45859 APIEndPoint.prototype.execute = function (data) {
45860 var endPoint = '/api/v1/execution';
45861 var fd = new FormData();
45862 fd.append('data', data);
45863 return this.$http.post(endPoint, fd, {
45864 headers: { 'Content-Type': undefined },
45865 transformRequest: angular.identity
45868 APIEndPoint.prototype.debug = function () {
45869 var endPoint = '/api/v1/debug';
45870 return this.$http.get(endPoint);
45872 APIEndPoint.prototype.upload = function () {
45873 var endPoint = '/api/v1/upload';
45874 return this.$http.get(endPoint);
45876 APIEndPoint.prototype.help = function (command) {
45877 var endPoint = '/api/v1/help/' + command;
45878 return this.$http.get(endPoint);
45880 return APIEndPoint;
45882 services.APIEndPoint = APIEndPoint;
45883 })(services = app.services || (app.services = {}));
45884 })(app || (app = {}));
45888 (function (services) {
45889 var MyModal = (function () {
45890 function MyModal($uibModal) {
45891 this.$uibModal = $uibModal;
45892 this.modalOption = {
45899 MyModal.prototype.open = function (modalName) {
45900 if (modalName === 'SelectCommand') {
45901 this.modalOption.templateUrl = 'templates/select-command.html';
45902 this.modalOption.size = 'lg';
45904 return this.$uibModal.open(this.modalOption);
45906 MyModal.prototype.selectCommand = function () {
45907 this.modalOption.templateUrl = 'templates/select-command.html';
45908 this.modalOption.controller = 'selectCommandController';
45909 this.modalOption.controllerAs = 'c';
45910 this.modalOption.size = 'lg';
45911 return this.$uibModal.open(this.modalOption);
45913 MyModal.prototype.preview = function () {
45914 this.modalOption.templateUrl = 'templates/preview.html';
45915 this.modalOption.controller = 'previewController';
45916 this.modalOption.controllerAs = 'c';
45917 this.modalOption.size = 'lg';
45918 return this.$uibModal.open(this.modalOption);
45920 MyModal.prototype.upload = function () {
45921 this.modalOption.templateUrl = 'templates/upload.html';
45922 this.modalOption.controller = 'uploadController';
45923 this.modalOption.controllerAs = 'c';
45924 this.modalOption.size = 'lg';
45925 return this.$uibModal.open(this.modalOption);
45927 MyModal.$inject = ['$uibModal'];
45930 services.MyModal = MyModal;
45931 })(services = app.services || (app.services = {}));
45932 })(app || (app = {}));
45936 (function (services) {
45937 var WebSocket = (function () {
45938 function WebSocket($rootScope) {
45939 this.$rootScope = $rootScope;
45940 this.socket = io.connect();
45942 WebSocket.prototype.on = function (eventName, callback) {
45943 var socket = this.socket;
45944 var rootScope = this.$rootScope;
45945 socket.on(eventName, function () {
45946 var args = arguments;
45947 rootScope.$apply(function () {
45948 callback.apply(socket, args);
45952 WebSocket.prototype.emit = function (eventName, data, callback) {
45953 var socket = this.socket;
45954 var rootScope = this.$rootScope;
45955 this.socket.emit(eventName, data, function () {
45956 var args = arguments;
45957 rootScope.$apply(function () {
45959 callback.apply(socket, args);
45965 services.WebSocket = WebSocket;
45966 })(services = app.services || (app.services = {}));
45967 })(app || (app = {}));
45971 (function (services) {
45972 var Console = (function () {
45973 function Console(WebSocket, $rootScope) {
45974 this.WebSocket = WebSocket;
45975 this.$rootScope = $rootScope;
45976 this.WebSocket = WebSocket;
45977 this.$rootScope = $rootScope;
45978 this.directiveIDs = [];
45979 var directiveIDs = this.directiveIDs;
45980 this.WebSocket.on('console', function (d) {
45982 var message = d.message;
45983 if (directiveIDs.indexOf(id) > -1) {
45984 $rootScope.$emit(id, message);
45988 Console.prototype.addDirective = function (id) {
45989 if (!(this.directiveIDs.indexOf(id) > -1)) {
45990 this.directiveIDs.push(id);
45993 Console.prototype.removeDirective = function (id) {
45994 var i = this.directiveIDs.indexOf(id);
45996 this.directiveIDs.splice(i, 1);
45999 Console.prototype.showIDs = function () {
46000 console.log(this.directiveIDs);
46004 services.Console = Console;
46005 })(services = app.services || (app.services = {}));
46006 })(app || (app = {}));
46010 (function (directives) {
46011 var Command = (function () {
46012 function Command() {
46013 this.restrict = 'E';
46014 this.replace = true;
46016 this.controller = 'commandController';
46017 this.controllerAs = 'ctrl';
46018 this.bindToController = {
46024 this.templateUrl = 'templates/command.html';
46026 Command.Factory = function () {
46027 var directive = function () {
46028 return new Command();
46030 directive.$inject = [];
46035 directives.Command = Command;
46036 var CommandController = (function () {
46037 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
46038 this.APIEndPoint = APIEndPoint;
46039 this.$scope = $scope;
46040 this.MyModal = MyModal;
46041 this.WebSocket = WebSocket;
46042 this.$window = $window;
46043 this.$rootScope = $rootScope;
46044 this.Console = Console;
46045 var controller = this;
46047 .getOptionControlFile(this.name)
46049 .then(function (result) {
46050 controller.options = result.info;
46055 .then(function (result) {
46056 controller.dirs = result.info;
46058 this.heading = "[" + this.index + "]: dcdFilePrint";
46059 this.isOpen = true;
46060 this.$scope.$on('close', function () {
46061 controller.isOpen = false;
46065 return Math.floor((1 + Math.random()) * 0x10000)
46069 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
46070 s4() + '-' + s4() + s4() + s4();
46072 this.uuid = guid();
46073 this.Console.addDirective(this.uuid);
46074 this.Console.showIDs();
46076 CommandController.prototype.submit = function () {
46078 angular.forEach(this.options, function (option) {
46080 name: option.option,
46083 angular.forEach(option.arg, function (arg) {
46085 if (typeof arg.input === 'object') {
46086 obj.arguments.push(arg.input.name);
46089 obj.arguments.push(arg.input);
46093 if (obj.arguments.length > 0) {
46098 command: this.name,
46099 workspace: this.workspace.fileId,
46103 .execute(JSON.stringify(execObj))
46104 .then(function (result) {
46105 console.log(result);
46108 CommandController.prototype.removeMySelf = function (index) {
46109 this.$scope.$destroy();
46110 this.Console.removeDirective(this.uuid);
46111 this.remove()(index, this.list);
46112 this.Console.showIDs();
46114 CommandController.prototype.reloadFiles = function () {
46116 var fileId = this.workspace.fileId;
46120 .then(function (result) {
46121 var status = result.status;
46122 if (status === 'success') {
46123 _this.files = result.info;
46126 console.log(result.message);
46130 CommandController.prototype.debug = function () {
46131 var div = angular.element(this.$window.document).find("div");
46134 angular.forEach(div, function (v) {
46135 if (v.className === "panel-body console") {
46138 else if (v.className === "row parameters-console") {
46142 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
46143 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
46144 consoleTag.style.height = consoleHeight;
46145 consoleTag.style.width = consoleWidth;
46147 CommandController.prototype.help = function () {
46150 .then(function (result) {
46151 console.log(result);
46154 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
46155 return CommandController;
46157 directives.CommandController = CommandController;
46158 })(directives = app.directives || (app.directives = {}));
46159 })(app || (app = {}));
46163 (function (directives) {
46164 var HeaderMenu = (function () {
46165 function HeaderMenu() {
46166 this.restrict = 'E';
46167 this.replace = true;
46168 this.templateUrl = 'templates/header-menu.html';
46169 this.controller = 'HeaderMenuController';
46170 this.controllerAs = 'hmc';
46173 HeaderMenu.Factory = function () {
46174 var directive = function () {
46175 return new HeaderMenu();
46181 directives.HeaderMenu = HeaderMenu;
46182 var HeaderMenuController = (function () {
46183 function HeaderMenuController($state) {
46184 this.$state = $state;
46185 this.isExecution = this.$state.current.name === 'execution';
46186 this.isWorkspace = this.$state.current.name === 'workspace';
46187 this.isHistory = this.$state.current.name === 'history';
46189 HeaderMenuController.prototype.transit = function (state) {
46190 this.$state.go(state);
46192 HeaderMenuController.$inject = ['$state'];
46193 return HeaderMenuController;
46195 directives.HeaderMenuController = HeaderMenuController;
46196 })(directives = app.directives || (app.directives = {}));
46197 })(app || (app = {}));
46201 (function (directives) {
46202 var Option = (function () {
46203 function Option() {
46204 this.restrict = 'E';
46205 this.replace = true;
46206 this.controller = 'optionController';
46207 this.bindToController = {
46212 this.templateUrl = 'templates/option.html';
46213 this.controllerAs = 'ctrl';
46215 Option.Factory = function () {
46216 var directive = function () {
46217 return new Option();
46219 directive.$inject = [];
46224 directives.Option = Option;
46225 var OptionController = (function () {
46226 function OptionController() {
46227 var controller = this;
46228 angular.forEach(controller.info.arg, function (arg) {
46229 if (arg.initialValue) {
46230 if (arg.formType === 'number') {
46231 arg.input = parseInt(arg.initialValue);
46234 arg.input = arg.initialValue;
46239 OptionController.$inject = [];
46240 return OptionController;
46242 directives.OptionController = OptionController;
46243 })(directives = app.directives || (app.directives = {}));
46244 })(app || (app = {}));
46248 (function (directives) {
46249 var Directory = (function () {
46250 function Directory() {
46251 this.restrict = 'E';
46252 this.replace = true;
46253 this.controller = 'directoryController';
46254 this.controllerAs = 'ctrl';
46255 this.bindToController = {
46261 this.templateUrl = 'templates/directory.html';
46263 Directory.Factory = function () {
46264 var directive = function () {
46265 return new Directory();
46271 directives.Directory = Directory;
46272 var DirectoryController = (function () {
46273 function DirectoryController(APIEndPoint, $scope) {
46274 this.APIEndPoint = APIEndPoint;
46275 this.$scope = $scope;
46276 var controller = this;
46278 .getFiles(this.info.fileId)
46280 .then(function (result) {
46281 if (result.status === 'success') {
46282 controller.files = result.info;
46283 angular.forEach(result.info, function (file) {
46284 if (file.fileType === '0') {
46286 if (controller.info.path === '/') {
46287 o.path = '/' + file.name;
46290 o.path = controller.info.path + '/' + file.name;
46292 controller.add()(o, controller.list);
46299 DirectoryController.$inject = ['APIEndPoint', '$scope'];
46300 return DirectoryController;
46302 directives.DirectoryController = DirectoryController;
46303 })(directives = app.directives || (app.directives = {}));
46304 })(app || (app = {}));
46308 (function (directives) {
46309 var Upload = (function () {
46310 function Upload() {
46311 this.restrict = 'E';
46312 this.replace = true;
46314 this.controller = 'UploadController';
46315 this.controllerAs = 'ctrl';
46316 this.bindToController = {
46322 this.templateUrl = 'templates/upload.html';
46323 console.log("templates/upload.html-constructor");
46325 Upload.Factory = function () {
46326 var directive = function () {
46327 return new Upload();
46329 directive.$inject = [];
46334 directives.Upload = Upload;
46335 var UploadController = (function () {
46336 function UploadController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
46337 this.APIEndPoint = APIEndPoint;
46338 this.$scope = $scope;
46339 this.MyModal = MyModal;
46340 this.WebSocket = WebSocket;
46341 this.$window = $window;
46342 this.$rootScope = $rootScope;
46343 this.Console = Console;
46344 var controller = this;
46345 console.log("directive.upload-constructor");
46347 UploadController.prototype.submit = function () {
46348 console.log("submit: function not supported¥n");
46350 UploadController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
46351 return UploadController;
46353 directives.UploadController = UploadController;
46354 })(directives = app.directives || (app.directives = {}));
46355 })(app || (app = {}));
46359 (function (controllers) {
46360 var Execution = (function () {
46361 function Execution(MyModal, $scope) {
46362 this.MyModal = MyModal;
46363 this.$scope = $scope;
46364 this.commandInfoList = [];
46367 Execution.prototype.add = function () {
46368 this.$scope.$broadcast('close');
46369 var commandInfoList = this.commandInfoList;
46370 var commandInstance = this.MyModal.selectCommand();
46373 .then(function (command) {
46374 commandInfoList.push(new app.declares.CommandInfo(command));
46377 Execution.prototype.open = function () {
46378 var result = this.MyModal.open('SelectCommand');
46379 console.log(result);
46381 Execution.prototype.remove = function (index, list) {
46382 list.splice(index, 1);
46384 Execution.prototype.close = function () {
46385 console.log("close");
46387 Execution.$inject = ['MyModal', '$scope'];
46390 controllers.Execution = Execution;
46391 })(controllers = app.controllers || (app.controllers = {}));
46392 })(app || (app = {}));
46396 (function (controllers) {
46397 var Workspace = (function () {
46398 function Workspace($scope, APIEndPoint, MyModal) {
46399 this.$scope = $scope;
46400 this.APIEndPoint = APIEndPoint;
46401 this.MyModal = MyModal;
46402 this.directoryList = [];
46403 var controller = this;
46404 var directoryList = this.directoryList;
46406 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
46414 directoryList.push(o);
46416 Workspace.prototype.addDirectory = function (info, directoryList) {
46417 directoryList.push(info);
46419 Workspace.prototype.upload = function () {
46420 this.MyModal.upload();
46422 Workspace.prototype.debug = function () {
46423 this.MyModal.preview();
46425 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
46428 controllers.Workspace = Workspace;
46429 })(controllers = app.controllers || (app.controllers = {}));
46430 })(app || (app = {}));
46434 (function (controllers) {
46435 var History = (function () {
46436 function History($scope) {
46437 this.page = "History";
46439 History.$inject = ['$scope'];
46442 controllers.History = History;
46443 })(controllers = app.controllers || (app.controllers = {}));
46444 })(app || (app = {}));
46448 (function (controllers) {
46449 var SelectCommand = (function () {
46450 function SelectCommand($scope, APIEndPoint, $modalInstance) {
46451 this.APIEndPoint = APIEndPoint;
46452 this.$modalInstance = $modalInstance;
46453 var controller = this;
46456 .$promise.then(function (result) {
46457 controller.tags = result.info;
46461 .$promise.then(function (result) {
46462 controller.commands = result.info;
46464 this.currentTag = 'all';
46466 SelectCommand.prototype.changeTag = function (tag) {
46467 this.currentTag = tag;
46469 SelectCommand.prototype.selectCommand = function (command) {
46470 this.$modalInstance.close(command);
46472 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
46473 return SelectCommand;
46475 controllers.SelectCommand = SelectCommand;
46476 })(controllers = app.controllers || (app.controllers = {}));
46477 })(app || (app = {}));
46481 (function (controllers) {
46482 var Upload = (function () {
46483 function Upload($scope, APIEndPoint, $modalInstance) {
46484 this.APIEndPoint = APIEndPoint;
46485 this.$modalInstance = $modalInstance;
46486 var controller = this;
46487 console.log('controller.upload-controllers');
46489 Upload.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
46492 controllers.Upload = Upload;
46493 })(controllers = app.controllers || (app.controllers = {}));
46494 })(app || (app = {}));
46498 (function (controllers) {
46499 var Preview = (function () {
46500 function Preview($scope, APIEndPoint, $modalInstance) {
46501 this.APIEndPoint = APIEndPoint;
46502 this.$modalInstance = $modalInstance;
46503 var controller = this;
46504 console.log('preview');
46506 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
46509 controllers.Preview = Preview;
46510 })(controllers = app.controllers || (app.controllers = {}));
46511 })(app || (app = {}));
46513 (function (filters) {
46515 return function (commands, tag) {
46517 angular.forEach(commands, function (command) {
46519 angular.forEach(command.tags, function (value) {
46524 result.push(command);
46530 })(filters || (filters = {}));
46534 var appName = 'zephyr';
46535 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
46536 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
46537 $urlRouterProvider.otherwise('/execution');
46538 $locationProvider.html5Mode({
46543 .state('execution', {
46545 templateUrl: 'templates/execution.html',
46546 controller: 'executionController',
46549 .state('workspace', {
46551 templateUrl: 'templates/workspace.html',
46552 controller: 'workspaceController',
46555 .state('history', {
46557 templateUrl: 'templates/history.html',
46558 controller: 'historyController',
46562 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
46563 app.zephyr.service('MyModal', app.services.MyModal);
46564 app.zephyr.service('WebSocket', app.services.WebSocket);
46565 app.zephyr.service('Console', app.services.Console);
46566 app.zephyr.filter('Tag', filters.Tag);
46567 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
46568 app.zephyr.controller('previewController', app.controllers.Preview);
46569 app.zephyr.controller('uploadController', app.controllers.Upload);
46570 app.zephyr.controller('executionController', app.controllers.Execution);
46571 app.zephyr.controller('workspaceController', app.controllers.Workspace);
46572 app.zephyr.controller('historyController', app.controllers.History);
46573 app.zephyr.controller('commandController', app.directives.CommandController);
46574 app.zephyr.controller('optionController', app.directives.OptionController);
46575 app.zephyr.controller('directoryController', app.directives.DirectoryController);
46576 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
46577 app.zephyr.controller('uploadController', app.directives.UploadController);
46578 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
46579 app.zephyr.directive('command', app.directives.Command.Factory());
46580 app.zephyr.directive('option', app.directives.Option.Factory());
46581 app.zephyr.directive('directory', app.directives.Directory.Factory());
46582 })(app || (app = {}));
46587 /***/ function(module, exports) {
46592 (function (declares) {
46593 var CommandInfo = (function () {
46594 function CommandInfo(name) {
46597 return CommandInfo;
46599 declares.CommandInfo = CommandInfo;
46600 })(declares = app.declares || (app.declares = {}));
46601 })(app || (app = {}));
46605 (function (services) {
46606 var APIEndPoint = (function () {
46607 function APIEndPoint($resource, $http) {
46608 this.$resource = $resource;
46609 this.$http = $http;
46611 APIEndPoint.prototype.resource = function (endPoint, data) {
46612 var customAction = {
46618 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
46620 return this.$resource(endPoint, {}, { execute: execute });
46622 APIEndPoint.prototype.getOptionControlFile = function (command) {
46623 var endPoint = '/api/v1/optionControlFile/' + command;
46624 return this.resource(endPoint, {}).get();
46626 APIEndPoint.prototype.getFiles = function (fileId) {
46627 var endPoint = '/api/v1/workspace';
46629 endPoint += '/' + fileId;
46631 return this.resource(endPoint, {}).get();
46633 APIEndPoint.prototype.getDirectories = function () {
46634 var endPoint = '/api/v1/all/workspace/directory';
46635 return this.resource(endPoint, {}).get();
46637 APIEndPoint.prototype.getTags = function () {
46638 var endPoint = '/api/v1/tagList';
46639 return this.resource(endPoint, {}).get();
46641 APIEndPoint.prototype.getCommands = function () {
46642 var endPoint = '/api/v1/commandList';
46643 return this.resource(endPoint, {}).get();
46645 APIEndPoint.prototype.execute = function (data) {
46646 var endPoint = '/api/v1/execution';
46647 var fd = new FormData();
46648 fd.append('data', data);
46649 return this.$http.post(endPoint, fd, {
46650 headers: { 'Content-Type': undefined },
46651 transformRequest: angular.identity
46654 APIEndPoint.prototype.debug = function () {
46655 var endPoint = '/api/v1/debug';
46656 return this.$http.get(endPoint);
46658 APIEndPoint.prototype.upload = function () {
46659 var endPoint = '/api/v1/upload';
46660 return this.$http.get(endPoint);
46662 APIEndPoint.prototype.help = function (command) {
46663 var endPoint = '/api/v1/help/' + command;
46664 return this.$http.get(endPoint);
46666 return APIEndPoint;
46668 services.APIEndPoint = APIEndPoint;
46669 })(services = app.services || (app.services = {}));
46670 })(app || (app = {}));
46674 (function (services) {
46675 var MyModal = (function () {
46676 function MyModal($uibModal) {
46677 this.$uibModal = $uibModal;
46678 this.modalOption = {
46685 MyModal.prototype.open = function (modalName) {
46686 if (modalName === 'SelectCommand') {
46687 this.modalOption.templateUrl = 'templates/select-command.html';
46688 this.modalOption.size = 'lg';
46690 return this.$uibModal.open(this.modalOption);
46692 MyModal.prototype.selectCommand = function () {
46693 this.modalOption.templateUrl = 'templates/select-command.html';
46694 this.modalOption.controller = 'selectCommandController';
46695 this.modalOption.controllerAs = 'c';
46696 this.modalOption.size = 'lg';
46697 return this.$uibModal.open(this.modalOption);
46699 MyModal.prototype.preview = function () {
46700 this.modalOption.templateUrl = 'templates/preview.html';
46701 this.modalOption.controller = 'previewController';
46702 this.modalOption.controllerAs = 'c';
46703 this.modalOption.size = 'lg';
46704 return this.$uibModal.open(this.modalOption);
46706 MyModal.prototype.upload = function () {
46707 this.modalOption.templateUrl = 'templates/upload.html';
46708 this.modalOption.controller = 'uploadController';
46709 this.modalOption.controllerAs = 'c';
46710 this.modalOption.size = 'lg';
46711 return this.$uibModal.open(this.modalOption);
46713 MyModal.$inject = ['$uibModal'];
46716 services.MyModal = MyModal;
46717 })(services = app.services || (app.services = {}));
46718 })(app || (app = {}));
46722 (function (services) {
46723 var WebSocket = (function () {
46724 function WebSocket($rootScope) {
46725 this.$rootScope = $rootScope;
46726 this.socket = io.connect();
46728 WebSocket.prototype.on = function (eventName, callback) {
46729 var socket = this.socket;
46730 var rootScope = this.$rootScope;
46731 socket.on(eventName, function () {
46732 var args = arguments;
46733 rootScope.$apply(function () {
46734 callback.apply(socket, args);
46738 WebSocket.prototype.emit = function (eventName, data, callback) {
46739 var socket = this.socket;
46740 var rootScope = this.$rootScope;
46741 this.socket.emit(eventName, data, function () {
46742 var args = arguments;
46743 rootScope.$apply(function () {
46745 callback.apply(socket, args);
46751 services.WebSocket = WebSocket;
46752 })(services = app.services || (app.services = {}));
46753 })(app || (app = {}));
46757 (function (services) {
46758 var Console = (function () {
46759 function Console(WebSocket, $rootScope) {
46760 this.WebSocket = WebSocket;
46761 this.$rootScope = $rootScope;
46762 this.WebSocket = WebSocket;
46763 this.$rootScope = $rootScope;
46764 this.directiveIDs = [];
46765 var directiveIDs = this.directiveIDs;
46766 this.WebSocket.on('console', function (d) {
46768 var message = d.message;
46769 if (directiveIDs.indexOf(id) > -1) {
46770 $rootScope.$emit(id, message);
46774 Console.prototype.addDirective = function (id) {
46775 if (!(this.directiveIDs.indexOf(id) > -1)) {
46776 this.directiveIDs.push(id);
46779 Console.prototype.removeDirective = function (id) {
46780 var i = this.directiveIDs.indexOf(id);
46782 this.directiveIDs.splice(i, 1);
46785 Console.prototype.showIDs = function () {
46786 console.log(this.directiveIDs);
46790 services.Console = Console;
46791 })(services = app.services || (app.services = {}));
46792 })(app || (app = {}));
46796 (function (directives) {
46797 var Command = (function () {
46798 function Command() {
46799 this.restrict = 'E';
46800 this.replace = true;
46802 this.controller = 'commandController';
46803 this.controllerAs = 'ctrl';
46804 this.bindToController = {
46810 this.templateUrl = 'templates/command.html';
46812 Command.Factory = function () {
46813 var directive = function () {
46814 return new Command();
46816 directive.$inject = [];
46821 directives.Command = Command;
46822 var CommandController = (function () {
46823 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
46824 this.APIEndPoint = APIEndPoint;
46825 this.$scope = $scope;
46826 this.MyModal = MyModal;
46827 this.WebSocket = WebSocket;
46828 this.$window = $window;
46829 this.$rootScope = $rootScope;
46830 this.Console = Console;
46831 var controller = this;
46833 .getOptionControlFile(this.name)
46835 .then(function (result) {
46836 controller.options = result.info;
46841 .then(function (result) {
46842 controller.dirs = result.info;
46844 this.heading = "[" + this.index + "]: dcdFilePrint";
46845 this.isOpen = true;
46846 this.$scope.$on('close', function () {
46847 controller.isOpen = false;
46851 return Math.floor((1 + Math.random()) * 0x10000)
46855 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
46856 s4() + '-' + s4() + s4() + s4();
46858 this.uuid = guid();
46859 this.Console.addDirective(this.uuid);
46860 this.Console.showIDs();
46862 CommandController.prototype.submit = function () {
46864 angular.forEach(this.options, function (option) {
46866 name: option.option,
46869 angular.forEach(option.arg, function (arg) {
46871 if (typeof arg.input === 'object') {
46872 obj.arguments.push(arg.input.name);
46875 obj.arguments.push(arg.input);
46879 if (obj.arguments.length > 0) {
46884 command: this.name,
46885 workspace: this.workspace.fileId,
46889 .execute(JSON.stringify(execObj))
46890 .then(function (result) {
46891 console.log(result);
46894 CommandController.prototype.removeMySelf = function (index) {
46895 this.$scope.$destroy();
46896 this.Console.removeDirective(this.uuid);
46897 this.remove()(index, this.list);
46898 this.Console.showIDs();
46900 CommandController.prototype.reloadFiles = function () {
46902 var fileId = this.workspace.fileId;
46906 .then(function (result) {
46907 var status = result.status;
46908 if (status === 'success') {
46909 _this.files = result.info;
46912 console.log(result.message);
46916 CommandController.prototype.debug = function () {
46917 var div = angular.element(this.$window.document).find("div");
46920 angular.forEach(div, function (v) {
46921 if (v.className === "panel-body console") {
46924 else if (v.className === "row parameters-console") {
46928 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
46929 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
46930 consoleTag.style.height = consoleHeight;
46931 consoleTag.style.width = consoleWidth;
46933 CommandController.prototype.help = function () {
46936 .then(function (result) {
46937 console.log(result);
46940 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
46941 return CommandController;
46943 directives.CommandController = CommandController;
46944 })(directives = app.directives || (app.directives = {}));
46945 })(app || (app = {}));
46949 (function (directives) {
46950 var HeaderMenu = (function () {
46951 function HeaderMenu() {
46952 this.restrict = 'E';
46953 this.replace = true;
46954 this.templateUrl = 'templates/header-menu.html';
46955 this.controller = 'HeaderMenuController';
46956 this.controllerAs = 'hmc';
46959 HeaderMenu.Factory = function () {
46960 var directive = function () {
46961 return new HeaderMenu();
46967 directives.HeaderMenu = HeaderMenu;
46968 var HeaderMenuController = (function () {
46969 function HeaderMenuController($state) {
46970 this.$state = $state;
46971 this.isExecution = this.$state.current.name === 'execution';
46972 this.isWorkspace = this.$state.current.name === 'workspace';
46973 this.isHistory = this.$state.current.name === 'history';
46975 HeaderMenuController.prototype.transit = function (state) {
46976 this.$state.go(state);
46978 HeaderMenuController.$inject = ['$state'];
46979 return HeaderMenuController;
46981 directives.HeaderMenuController = HeaderMenuController;
46982 })(directives = app.directives || (app.directives = {}));
46983 })(app || (app = {}));
46987 (function (directives) {
46988 var Option = (function () {
46989 function Option() {
46990 this.restrict = 'E';
46991 this.replace = true;
46992 this.controller = 'optionController';
46993 this.bindToController = {
46998 this.templateUrl = 'templates/option.html';
46999 this.controllerAs = 'ctrl';
47001 Option.Factory = function () {
47002 var directive = function () {
47003 return new Option();
47005 directive.$inject = [];
47010 directives.Option = Option;
47011 var OptionController = (function () {
47012 function OptionController() {
47013 var controller = this;
47014 angular.forEach(controller.info.arg, function (arg) {
47015 if (arg.initialValue) {
47016 if (arg.formType === 'number') {
47017 arg.input = parseInt(arg.initialValue);
47020 arg.input = arg.initialValue;
47025 OptionController.$inject = [];
47026 return OptionController;
47028 directives.OptionController = OptionController;
47029 })(directives = app.directives || (app.directives = {}));
47030 })(app || (app = {}));
47034 (function (directives) {
47035 var Directory = (function () {
47036 function Directory() {
47037 this.restrict = 'E';
47038 this.replace = true;
47039 this.controller = 'directoryController';
47040 this.controllerAs = 'ctrl';
47041 this.bindToController = {
47047 this.templateUrl = 'templates/directory.html';
47049 Directory.Factory = function () {
47050 var directive = function () {
47051 return new Directory();
47057 directives.Directory = Directory;
47058 var DirectoryController = (function () {
47059 function DirectoryController(APIEndPoint, $scope) {
47060 this.APIEndPoint = APIEndPoint;
47061 this.$scope = $scope;
47062 var controller = this;
47064 .getFiles(this.info.fileId)
47066 .then(function (result) {
47067 if (result.status === 'success') {
47068 controller.files = result.info;
47069 angular.forEach(result.info, function (file) {
47070 if (file.fileType === '0') {
47072 if (controller.info.path === '/') {
47073 o.path = '/' + file.name;
47076 o.path = controller.info.path + '/' + file.name;
47078 controller.add()(o, controller.list);
47085 DirectoryController.$inject = ['APIEndPoint', '$scope'];
47086 return DirectoryController;
47088 directives.DirectoryController = DirectoryController;
47089 })(directives = app.directives || (app.directives = {}));
47090 })(app || (app = {}));
47094 (function (directives) {
47095 var Upload = (function () {
47096 function Upload() {
47097 this.restrict = 'E';
47098 this.replace = true;
47100 this.controller = 'UploadController';
47101 this.controllerAs = 'ctrl';
47102 this.bindToController = {
47108 this.templateUrl = 'templates/upload.html';
47109 console.log("templates/upload.html-constructor");
47111 Upload.Factory = function () {
47112 var directive = function () {
47113 return new Upload();
47115 directive.$inject = [];
47120 directives.Upload = Upload;
47121 var UploadController = (function () {
47122 function UploadController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
47123 this.APIEndPoint = APIEndPoint;
47124 this.$scope = $scope;
47125 this.MyModal = MyModal;
47126 this.WebSocket = WebSocket;
47127 this.$window = $window;
47128 this.$rootScope = $rootScope;
47129 this.Console = Console;
47130 var controller = this;
47131 console.log("directive.upload-constructor");
47133 UploadController.prototype.submit = function () {
47134 console.log("submit: function not supported¥n");
47136 UploadController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
47137 return UploadController;
47139 directives.UploadController = UploadController;
47140 })(directives = app.directives || (app.directives = {}));
47141 })(app || (app = {}));
47145 (function (controllers) {
47146 var Execution = (function () {
47147 function Execution(MyModal, $scope) {
47148 this.MyModal = MyModal;
47149 this.$scope = $scope;
47150 this.commandInfoList = [];
47153 Execution.prototype.add = function () {
47154 this.$scope.$broadcast('close');
47155 var commandInfoList = this.commandInfoList;
47156 var commandInstance = this.MyModal.selectCommand();
47159 .then(function (command) {
47160 commandInfoList.push(new app.declares.CommandInfo(command));
47163 Execution.prototype.open = function () {
47164 var result = this.MyModal.open('SelectCommand');
47165 console.log(result);
47167 Execution.prototype.remove = function (index, list) {
47168 list.splice(index, 1);
47170 Execution.prototype.close = function () {
47171 console.log("close");
47173 Execution.$inject = ['MyModal', '$scope'];
47176 controllers.Execution = Execution;
47177 })(controllers = app.controllers || (app.controllers = {}));
47178 })(app || (app = {}));
47182 (function (controllers) {
47183 var Workspace = (function () {
47184 function Workspace($scope, APIEndPoint, MyModal) {
47185 this.$scope = $scope;
47186 this.APIEndPoint = APIEndPoint;
47187 this.MyModal = MyModal;
47188 this.directoryList = [];
47189 var controller = this;
47190 var directoryList = this.directoryList;
47192 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
47200 directoryList.push(o);
47202 Workspace.prototype.addDirectory = function (info, directoryList) {
47203 directoryList.push(info);
47205 Workspace.prototype.upload = function () {
47206 this.MyModal.upload();
47208 Workspace.prototype.debug = function () {
47209 this.MyModal.preview();
47211 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
47214 controllers.Workspace = Workspace;
47215 })(controllers = app.controllers || (app.controllers = {}));
47216 })(app || (app = {}));
47220 (function (controllers) {
47221 var History = (function () {
47222 function History($scope) {
47223 this.page = "History";
47225 History.$inject = ['$scope'];
47228 controllers.History = History;
47229 })(controllers = app.controllers || (app.controllers = {}));
47230 })(app || (app = {}));
47234 (function (controllers) {
47235 var SelectCommand = (function () {
47236 function SelectCommand($scope, APIEndPoint, $modalInstance) {
47237 this.APIEndPoint = APIEndPoint;
47238 this.$modalInstance = $modalInstance;
47239 var controller = this;
47242 .$promise.then(function (result) {
47243 controller.tags = result.info;
47247 .$promise.then(function (result) {
47248 controller.commands = result.info;
47250 this.currentTag = 'all';
47252 SelectCommand.prototype.changeTag = function (tag) {
47253 this.currentTag = tag;
47255 SelectCommand.prototype.selectCommand = function (command) {
47256 this.$modalInstance.close(command);
47258 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
47259 return SelectCommand;
47261 controllers.SelectCommand = SelectCommand;
47262 })(controllers = app.controllers || (app.controllers = {}));
47263 })(app || (app = {}));
47267 (function (controllers) {
47268 var Upload = (function () {
47269 function Upload($scope, APIEndPoint, $modalInstance) {
47270 this.APIEndPoint = APIEndPoint;
47271 this.$modalInstance = $modalInstance;
47272 var controller = this;
47273 console.log('controller.upload-controllers');
47275 Upload.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
47278 controllers.Upload = Upload;
47279 })(controllers = app.controllers || (app.controllers = {}));
47280 })(app || (app = {}));
47284 (function (controllers) {
47285 var Preview = (function () {
47286 function Preview($scope, APIEndPoint, $modalInstance) {
47287 this.APIEndPoint = APIEndPoint;
47288 this.$modalInstance = $modalInstance;
47289 var controller = this;
47290 console.log('preview');
47292 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
47295 controllers.Preview = Preview;
47296 })(controllers = app.controllers || (app.controllers = {}));
47297 })(app || (app = {}));
47299 (function (filters) {
47301 return function (commands, tag) {
47303 angular.forEach(commands, function (command) {
47305 angular.forEach(command.tags, function (value) {
47310 result.push(command);
47316 })(filters || (filters = {}));
47320 var appName = 'zephyr';
47321 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
47322 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
47323 $urlRouterProvider.otherwise('/execution');
47324 $locationProvider.html5Mode({
47329 .state('execution', {
47331 templateUrl: 'templates/execution.html',
47332 controller: 'executionController',
47335 .state('workspace', {
47337 templateUrl: 'templates/workspace.html',
47338 controller: 'workspaceController',
47341 .state('history', {
47343 templateUrl: 'templates/history.html',
47344 controller: 'historyController',
47348 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
47349 app.zephyr.service('MyModal', app.services.MyModal);
47350 app.zephyr.service('WebSocket', app.services.WebSocket);
47351 app.zephyr.service('Console', app.services.Console);
47352 app.zephyr.filter('Tag', filters.Tag);
47353 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
47354 app.zephyr.controller('previewController', app.controllers.Preview);
47355 app.zephyr.controller('uploadController', app.controllers.Upload);
47356 app.zephyr.controller('executionController', app.controllers.Execution);
47357 app.zephyr.controller('workspaceController', app.controllers.Workspace);
47358 app.zephyr.controller('historyController', app.controllers.History);
47359 app.zephyr.controller('commandController', app.directives.CommandController);
47360 app.zephyr.controller('optionController', app.directives.OptionController);
47361 app.zephyr.controller('directoryController', app.directives.DirectoryController);
47362 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
47363 app.zephyr.controller('uploadController', app.directives.UploadController);
47364 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
47365 app.zephyr.directive('command', app.directives.Command.Factory());
47366 app.zephyr.directive('option', app.directives.Option.Factory());
47367 app.zephyr.directive('directory', app.directives.Directory.Factory());
47368 })(app || (app = {}));
47373 /***/ function(module, exports) {
47378 (function (declares) {
47379 var CommandInfo = (function () {
47380 function CommandInfo(name) {
47383 return CommandInfo;
47385 declares.CommandInfo = CommandInfo;
47386 })(declares = app.declares || (app.declares = {}));
47387 })(app || (app = {}));
47391 (function (services) {
47392 var APIEndPoint = (function () {
47393 function APIEndPoint($resource, $http) {
47394 this.$resource = $resource;
47395 this.$http = $http;
47397 APIEndPoint.prototype.resource = function (endPoint, data) {
47398 var customAction = {
47404 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
47406 return this.$resource(endPoint, {}, { execute: execute });
47408 APIEndPoint.prototype.getOptionControlFile = function (command) {
47409 var endPoint = '/api/v1/optionControlFile/' + command;
47410 return this.resource(endPoint, {}).get();
47412 APIEndPoint.prototype.getFiles = function (fileId) {
47413 var endPoint = '/api/v1/workspace';
47415 endPoint += '/' + fileId;
47417 return this.resource(endPoint, {}).get();
47419 APIEndPoint.prototype.getDirectories = function () {
47420 var endPoint = '/api/v1/all/workspace/directory';
47421 return this.resource(endPoint, {}).get();
47423 APIEndPoint.prototype.getTags = function () {
47424 var endPoint = '/api/v1/tagList';
47425 return this.resource(endPoint, {}).get();
47427 APIEndPoint.prototype.getCommands = function () {
47428 var endPoint = '/api/v1/commandList';
47429 return this.resource(endPoint, {}).get();
47431 APIEndPoint.prototype.execute = function (data) {
47432 var endPoint = '/api/v1/execution';
47433 var fd = new FormData();
47434 fd.append('data', data);
47435 return this.$http.post(endPoint, fd, {
47436 headers: { 'Content-Type': undefined },
47437 transformRequest: angular.identity
47440 APIEndPoint.prototype.debug = function () {
47441 var endPoint = '/api/v1/debug';
47442 return this.$http.get(endPoint);
47444 APIEndPoint.prototype.upload = function () {
47445 var endPoint = '/api/v1/upload';
47446 return this.$http.get(endPoint);
47448 APIEndPoint.prototype.help = function (command) {
47449 var endPoint = '/api/v1/help/' + command;
47450 return this.$http.get(endPoint);
47452 return APIEndPoint;
47454 services.APIEndPoint = APIEndPoint;
47455 })(services = app.services || (app.services = {}));
47456 })(app || (app = {}));
47460 (function (services) {
47461 var MyModal = (function () {
47462 function MyModal($uibModal) {
47463 this.$uibModal = $uibModal;
47464 this.modalOption = {
47471 MyModal.prototype.open = function (modalName) {
47472 if (modalName === 'SelectCommand') {
47473 this.modalOption.templateUrl = 'templates/select-command.html';
47474 this.modalOption.size = 'lg';
47476 return this.$uibModal.open(this.modalOption);
47478 MyModal.prototype.selectCommand = function () {
47479 this.modalOption.templateUrl = 'templates/select-command.html';
47480 this.modalOption.controller = 'selectCommandController';
47481 this.modalOption.controllerAs = 'c';
47482 this.modalOption.size = 'lg';
47483 return this.$uibModal.open(this.modalOption);
47485 MyModal.prototype.preview = function () {
47486 this.modalOption.templateUrl = 'templates/preview.html';
47487 this.modalOption.controller = 'previewController';
47488 this.modalOption.controllerAs = 'c';
47489 this.modalOption.size = 'lg';
47490 return this.$uibModal.open(this.modalOption);
47492 MyModal.prototype.upload = function () {
47493 this.modalOption.templateUrl = 'templates/upload.html';
47494 this.modalOption.controller = 'uploadController';
47495 this.modalOption.controllerAs = 'c';
47496 this.modalOption.size = 'lg';
47497 return this.$uibModal.open(this.modalOption);
47499 MyModal.$inject = ['$uibModal'];
47502 services.MyModal = MyModal;
47503 })(services = app.services || (app.services = {}));
47504 })(app || (app = {}));
47508 (function (services) {
47509 var WebSocket = (function () {
47510 function WebSocket($rootScope) {
47511 this.$rootScope = $rootScope;
47512 this.socket = io.connect();
47514 WebSocket.prototype.on = function (eventName, callback) {
47515 var socket = this.socket;
47516 var rootScope = this.$rootScope;
47517 socket.on(eventName, function () {
47518 var args = arguments;
47519 rootScope.$apply(function () {
47520 callback.apply(socket, args);
47524 WebSocket.prototype.emit = function (eventName, data, callback) {
47525 var socket = this.socket;
47526 var rootScope = this.$rootScope;
47527 this.socket.emit(eventName, data, function () {
47528 var args = arguments;
47529 rootScope.$apply(function () {
47531 callback.apply(socket, args);
47537 services.WebSocket = WebSocket;
47538 })(services = app.services || (app.services = {}));
47539 })(app || (app = {}));
47543 (function (services) {
47544 var Console = (function () {
47545 function Console(WebSocket, $rootScope) {
47546 this.WebSocket = WebSocket;
47547 this.$rootScope = $rootScope;
47548 this.WebSocket = WebSocket;
47549 this.$rootScope = $rootScope;
47550 this.directiveIDs = [];
47551 var directiveIDs = this.directiveIDs;
47552 this.WebSocket.on('console', function (d) {
47554 var message = d.message;
47555 if (directiveIDs.indexOf(id) > -1) {
47556 $rootScope.$emit(id, message);
47560 Console.prototype.addDirective = function (id) {
47561 if (!(this.directiveIDs.indexOf(id) > -1)) {
47562 this.directiveIDs.push(id);
47565 Console.prototype.removeDirective = function (id) {
47566 var i = this.directiveIDs.indexOf(id);
47568 this.directiveIDs.splice(i, 1);
47571 Console.prototype.showIDs = function () {
47572 console.log(this.directiveIDs);
47576 services.Console = Console;
47577 })(services = app.services || (app.services = {}));
47578 })(app || (app = {}));
47582 (function (directives) {
47583 var Command = (function () {
47584 function Command() {
47585 this.restrict = 'E';
47586 this.replace = true;
47588 this.controller = 'commandController';
47589 this.controllerAs = 'ctrl';
47590 this.bindToController = {
47596 this.templateUrl = 'templates/command.html';
47598 Command.Factory = function () {
47599 var directive = function () {
47600 return new Command();
47602 directive.$inject = [];
47607 directives.Command = Command;
47608 var CommandController = (function () {
47609 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
47610 this.APIEndPoint = APIEndPoint;
47611 this.$scope = $scope;
47612 this.MyModal = MyModal;
47613 this.WebSocket = WebSocket;
47614 this.$window = $window;
47615 this.$rootScope = $rootScope;
47616 this.Console = Console;
47617 var controller = this;
47619 .getOptionControlFile(this.name)
47621 .then(function (result) {
47622 controller.options = result.info;
47627 .then(function (result) {
47628 controller.dirs = result.info;
47630 this.heading = "[" + this.index + "]: dcdFilePrint";
47631 this.isOpen = true;
47632 this.$scope.$on('close', function () {
47633 controller.isOpen = false;
47637 return Math.floor((1 + Math.random()) * 0x10000)
47641 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
47642 s4() + '-' + s4() + s4() + s4();
47644 this.uuid = guid();
47645 this.Console.addDirective(this.uuid);
47646 this.Console.showIDs();
47648 CommandController.prototype.submit = function () {
47650 angular.forEach(this.options, function (option) {
47652 name: option.option,
47655 angular.forEach(option.arg, function (arg) {
47657 if (typeof arg.input === 'object') {
47658 obj.arguments.push(arg.input.name);
47661 obj.arguments.push(arg.input);
47665 if (obj.arguments.length > 0) {
47670 command: this.name,
47671 workspace: this.workspace.fileId,
47675 .execute(JSON.stringify(execObj))
47676 .then(function (result) {
47677 console.log(result);
47680 CommandController.prototype.removeMySelf = function (index) {
47681 this.$scope.$destroy();
47682 this.Console.removeDirective(this.uuid);
47683 this.remove()(index, this.list);
47684 this.Console.showIDs();
47686 CommandController.prototype.reloadFiles = function () {
47688 var fileId = this.workspace.fileId;
47692 .then(function (result) {
47693 var status = result.status;
47694 if (status === 'success') {
47695 _this.files = result.info;
47698 console.log(result.message);
47702 CommandController.prototype.debug = function () {
47703 var div = angular.element(this.$window.document).find("div");
47706 angular.forEach(div, function (v) {
47707 if (v.className === "panel-body console") {
47710 else if (v.className === "row parameters-console") {
47714 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
47715 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
47716 consoleTag.style.height = consoleHeight;
47717 consoleTag.style.width = consoleWidth;
47719 CommandController.prototype.help = function () {
47722 .then(function (result) {
47723 console.log(result);
47726 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
47727 return CommandController;
47729 directives.CommandController = CommandController;
47730 })(directives = app.directives || (app.directives = {}));
47731 })(app || (app = {}));
47735 (function (directives) {
47736 var HeaderMenu = (function () {
47737 function HeaderMenu() {
47738 this.restrict = 'E';
47739 this.replace = true;
47740 this.templateUrl = 'templates/header-menu.html';
47741 this.controller = 'HeaderMenuController';
47742 this.controllerAs = 'hmc';
47745 HeaderMenu.Factory = function () {
47746 var directive = function () {
47747 return new HeaderMenu();
47753 directives.HeaderMenu = HeaderMenu;
47754 var HeaderMenuController = (function () {
47755 function HeaderMenuController($state) {
47756 this.$state = $state;
47757 this.isExecution = this.$state.current.name === 'execution';
47758 this.isWorkspace = this.$state.current.name === 'workspace';
47759 this.isHistory = this.$state.current.name === 'history';
47761 HeaderMenuController.prototype.transit = function (state) {
47762 this.$state.go(state);
47764 HeaderMenuController.$inject = ['$state'];
47765 return HeaderMenuController;
47767 directives.HeaderMenuController = HeaderMenuController;
47768 })(directives = app.directives || (app.directives = {}));
47769 })(app || (app = {}));
47773 (function (directives) {
47774 var Option = (function () {
47775 function Option() {
47776 this.restrict = 'E';
47777 this.replace = true;
47778 this.controller = 'optionController';
47779 this.bindToController = {
47784 this.templateUrl = 'templates/option.html';
47785 this.controllerAs = 'ctrl';
47787 Option.Factory = function () {
47788 var directive = function () {
47789 return new Option();
47791 directive.$inject = [];
47796 directives.Option = Option;
47797 var OptionController = (function () {
47798 function OptionController() {
47799 var controller = this;
47800 angular.forEach(controller.info.arg, function (arg) {
47801 if (arg.initialValue) {
47802 if (arg.formType === 'number') {
47803 arg.input = parseInt(arg.initialValue);
47806 arg.input = arg.initialValue;
47811 OptionController.$inject = [];
47812 return OptionController;
47814 directives.OptionController = OptionController;
47815 })(directives = app.directives || (app.directives = {}));
47816 })(app || (app = {}));
47820 (function (directives) {
47821 var Directory = (function () {
47822 function Directory() {
47823 this.restrict = 'E';
47824 this.replace = true;
47825 this.controller = 'directoryController';
47826 this.controllerAs = 'ctrl';
47827 this.bindToController = {
47833 this.templateUrl = 'templates/directory.html';
47835 Directory.Factory = function () {
47836 var directive = function () {
47837 return new Directory();
47843 directives.Directory = Directory;
47844 var DirectoryController = (function () {
47845 function DirectoryController(APIEndPoint, $scope) {
47846 this.APIEndPoint = APIEndPoint;
47847 this.$scope = $scope;
47848 var controller = this;
47850 .getFiles(this.info.fileId)
47852 .then(function (result) {
47853 if (result.status === 'success') {
47854 controller.files = result.info;
47855 angular.forEach(result.info, function (file) {
47856 if (file.fileType === '0') {
47858 if (controller.info.path === '/') {
47859 o.path = '/' + file.name;
47862 o.path = controller.info.path + '/' + file.name;
47864 controller.add()(o, controller.list);
47871 DirectoryController.$inject = ['APIEndPoint', '$scope'];
47872 return DirectoryController;
47874 directives.DirectoryController = DirectoryController;
47875 })(directives = app.directives || (app.directives = {}));
47876 })(app || (app = {}));
47880 (function (directives) {
47881 var Upload = (function () {
47882 function Upload() {
47883 this.restrict = 'E';
47884 this.replace = true;
47886 this.controller = 'UploadController';
47887 this.controllerAs = 'ctrl';
47888 this.bindToController = {
47894 this.templateUrl = 'templates/upload.html';
47895 console.log("templates/upload.html-constructor");
47897 Upload.Factory = function () {
47898 var directive = function () {
47899 return new Upload();
47901 directive.$inject = [];
47906 directives.Upload = Upload;
47907 var UploadController = (function () {
47908 function UploadController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
47909 this.APIEndPoint = APIEndPoint;
47910 this.$scope = $scope;
47911 this.MyModal = MyModal;
47912 this.WebSocket = WebSocket;
47913 this.$window = $window;
47914 this.$rootScope = $rootScope;
47915 this.Console = Console;
47916 var controller = this;
47917 console.log("directive.upload-constructor");
47919 UploadController.prototype.submit = function () {
47920 console.log("submit: function not supported¥n");
47922 UploadController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
47923 return UploadController;
47925 directives.UploadController = UploadController;
47926 })(directives = app.directives || (app.directives = {}));
47927 })(app || (app = {}));
47931 (function (controllers) {
47932 var Execution = (function () {
47933 function Execution(MyModal, $scope) {
47934 this.MyModal = MyModal;
47935 this.$scope = $scope;
47936 this.commandInfoList = [];
47939 Execution.prototype.add = function () {
47940 this.$scope.$broadcast('close');
47941 var commandInfoList = this.commandInfoList;
47942 var commandInstance = this.MyModal.selectCommand();
47945 .then(function (command) {
47946 commandInfoList.push(new app.declares.CommandInfo(command));
47949 Execution.prototype.open = function () {
47950 var result = this.MyModal.open('SelectCommand');
47951 console.log(result);
47953 Execution.prototype.remove = function (index, list) {
47954 list.splice(index, 1);
47956 Execution.prototype.close = function () {
47957 console.log("close");
47959 Execution.$inject = ['MyModal', '$scope'];
47962 controllers.Execution = Execution;
47963 })(controllers = app.controllers || (app.controllers = {}));
47964 })(app || (app = {}));
47968 (function (controllers) {
47969 var Workspace = (function () {
47970 function Workspace($scope, APIEndPoint, MyModal) {
47971 this.$scope = $scope;
47972 this.APIEndPoint = APIEndPoint;
47973 this.MyModal = MyModal;
47974 this.directoryList = [];
47975 var controller = this;
47976 var directoryList = this.directoryList;
47978 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
47986 directoryList.push(o);
47988 Workspace.prototype.addDirectory = function (info, directoryList) {
47989 directoryList.push(info);
47991 Workspace.prototype.upload = function () {
47992 this.MyModal.upload();
47994 Workspace.prototype.debug = function () {
47995 this.MyModal.preview();
47997 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
48000 controllers.Workspace = Workspace;
48001 })(controllers = app.controllers || (app.controllers = {}));
48002 })(app || (app = {}));
48006 (function (controllers) {
48007 var History = (function () {
48008 function History($scope) {
48009 this.page = "History";
48011 History.$inject = ['$scope'];
48014 controllers.History = History;
48015 })(controllers = app.controllers || (app.controllers = {}));
48016 })(app || (app = {}));
48020 (function (controllers) {
48021 var SelectCommand = (function () {
48022 function SelectCommand($scope, APIEndPoint, $modalInstance) {
48023 this.APIEndPoint = APIEndPoint;
48024 this.$modalInstance = $modalInstance;
48025 var controller = this;
48028 .$promise.then(function (result) {
48029 controller.tags = result.info;
48033 .$promise.then(function (result) {
48034 controller.commands = result.info;
48036 this.currentTag = 'all';
48038 SelectCommand.prototype.changeTag = function (tag) {
48039 this.currentTag = tag;
48041 SelectCommand.prototype.selectCommand = function (command) {
48042 this.$modalInstance.close(command);
48044 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
48045 return SelectCommand;
48047 controllers.SelectCommand = SelectCommand;
48048 })(controllers = app.controllers || (app.controllers = {}));
48049 })(app || (app = {}));
48053 (function (controllers) {
48054 var Upload = (function () {
48055 function Upload($scope, APIEndPoint, $modalInstance) {
48056 this.APIEndPoint = APIEndPoint;
48057 this.$modalInstance = $modalInstance;
48058 var controller = this;
48059 console.log('controller.upload-controllers');
48061 Upload.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
48064 controllers.Upload = Upload;
48065 })(controllers = app.controllers || (app.controllers = {}));
48066 })(app || (app = {}));
48070 (function (controllers) {
48071 var Preview = (function () {
48072 function Preview($scope, APIEndPoint, $modalInstance) {
48073 this.APIEndPoint = APIEndPoint;
48074 this.$modalInstance = $modalInstance;
48075 var controller = this;
48076 console.log('preview');
48078 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
48081 controllers.Preview = Preview;
48082 })(controllers = app.controllers || (app.controllers = {}));
48083 })(app || (app = {}));
48085 (function (filters) {
48087 return function (commands, tag) {
48089 angular.forEach(commands, function (command) {
48091 angular.forEach(command.tags, function (value) {
48096 result.push(command);
48102 })(filters || (filters = {}));
48106 var appName = 'zephyr';
48107 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
48108 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
48109 $urlRouterProvider.otherwise('/execution');
48110 $locationProvider.html5Mode({
48115 .state('execution', {
48117 templateUrl: 'templates/execution.html',
48118 controller: 'executionController',
48121 .state('workspace', {
48123 templateUrl: 'templates/workspace.html',
48124 controller: 'workspaceController',
48127 .state('history', {
48129 templateUrl: 'templates/history.html',
48130 controller: 'historyController',
48134 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
48135 app.zephyr.service('MyModal', app.services.MyModal);
48136 app.zephyr.service('WebSocket', app.services.WebSocket);
48137 app.zephyr.service('Console', app.services.Console);
48138 app.zephyr.filter('Tag', filters.Tag);
48139 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
48140 app.zephyr.controller('previewController', app.controllers.Preview);
48141 app.zephyr.controller('uploadController', app.controllers.Upload);
48142 app.zephyr.controller('executionController', app.controllers.Execution);
48143 app.zephyr.controller('workspaceController', app.controllers.Workspace);
48144 app.zephyr.controller('historyController', app.controllers.History);
48145 app.zephyr.controller('commandController', app.directives.CommandController);
48146 app.zephyr.controller('optionController', app.directives.OptionController);
48147 app.zephyr.controller('directoryController', app.directives.DirectoryController);
48148 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
48149 app.zephyr.controller('uploadController', app.directives.UploadController);
48150 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
48151 app.zephyr.directive('command', app.directives.Command.Factory());
48152 app.zephyr.directive('option', app.directives.Option.Factory());
48153 app.zephyr.directive('directory', app.directives.Directory.Factory());
48154 })(app || (app = {}));
48159 /***/ function(module, exports) {
48164 (function (declares) {
48165 var CommandInfo = (function () {
48166 function CommandInfo(name) {
48169 return CommandInfo;
48171 declares.CommandInfo = CommandInfo;
48172 })(declares = app.declares || (app.declares = {}));
48173 })(app || (app = {}));
48177 (function (services) {
48178 var APIEndPoint = (function () {
48179 function APIEndPoint($resource, $http) {
48180 this.$resource = $resource;
48181 this.$http = $http;
48183 APIEndPoint.prototype.resource = function (endPoint, data) {
48184 var customAction = {
48190 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
48192 return this.$resource(endPoint, {}, { execute: execute });
48194 APIEndPoint.prototype.getOptionControlFile = function (command) {
48195 var endPoint = '/api/v1/optionControlFile/' + command;
48196 return this.resource(endPoint, {}).get();
48198 APIEndPoint.prototype.getFiles = function (fileId) {
48199 var endPoint = '/api/v1/workspace';
48201 endPoint += '/' + fileId;
48203 return this.resource(endPoint, {}).get();
48205 APIEndPoint.prototype.getDirectories = function () {
48206 var endPoint = '/api/v1/all/workspace/directory';
48207 return this.resource(endPoint, {}).get();
48209 APIEndPoint.prototype.getTags = function () {
48210 var endPoint = '/api/v1/tagList';
48211 return this.resource(endPoint, {}).get();
48213 APIEndPoint.prototype.getCommands = function () {
48214 var endPoint = '/api/v1/commandList';
48215 return this.resource(endPoint, {}).get();
48217 APIEndPoint.prototype.execute = function (data) {
48218 var endPoint = '/api/v1/execution';
48219 var fd = new FormData();
48220 fd.append('data', data);
48221 return this.$http.post(endPoint, fd, {
48222 headers: { 'Content-Type': undefined },
48223 transformRequest: angular.identity
48226 APIEndPoint.prototype.debug = function () {
48227 var endPoint = '/api/v1/debug';
48228 return this.$http.get(endPoint);
48230 APIEndPoint.prototype.upload = function () {
48231 var endPoint = '/api/v1/upload';
48232 return this.$http.get(endPoint);
48234 APIEndPoint.prototype.help = function (command) {
48235 var endPoint = '/api/v1/help/' + command;
48236 return this.$http.get(endPoint);
48238 return APIEndPoint;
48240 services.APIEndPoint = APIEndPoint;
48241 })(services = app.services || (app.services = {}));
48242 })(app || (app = {}));
48246 (function (services) {
48247 var MyModal = (function () {
48248 function MyModal($uibModal) {
48249 this.$uibModal = $uibModal;
48250 this.modalOption = {
48257 MyModal.prototype.open = function (modalName) {
48258 if (modalName === 'SelectCommand') {
48259 this.modalOption.templateUrl = 'templates/select-command.html';
48260 this.modalOption.size = 'lg';
48262 return this.$uibModal.open(this.modalOption);
48264 MyModal.prototype.selectCommand = function () {
48265 this.modalOption.templateUrl = 'templates/select-command.html';
48266 this.modalOption.controller = 'selectCommandController';
48267 this.modalOption.controllerAs = 'c';
48268 this.modalOption.size = 'lg';
48269 return this.$uibModal.open(this.modalOption);
48271 MyModal.prototype.preview = function () {
48272 this.modalOption.templateUrl = 'templates/preview.html';
48273 this.modalOption.controller = 'previewController';
48274 this.modalOption.controllerAs = 'c';
48275 this.modalOption.size = 'lg';
48276 return this.$uibModal.open(this.modalOption);
48278 MyModal.prototype.upload = function () {
48279 this.modalOption.templateUrl = 'templates/upload.html';
48280 this.modalOption.controller = 'uploadController';
48281 this.modalOption.controllerAs = 'c';
48282 this.modalOption.size = 'lg';
48283 return this.$uibModal.open(this.modalOption);
48285 MyModal.$inject = ['$uibModal'];
48288 services.MyModal = MyModal;
48289 })(services = app.services || (app.services = {}));
48290 })(app || (app = {}));
48294 (function (services) {
48295 var WebSocket = (function () {
48296 function WebSocket($rootScope) {
48297 this.$rootScope = $rootScope;
48298 this.socket = io.connect();
48300 WebSocket.prototype.on = function (eventName, callback) {
48301 var socket = this.socket;
48302 var rootScope = this.$rootScope;
48303 socket.on(eventName, function () {
48304 var args = arguments;
48305 rootScope.$apply(function () {
48306 callback.apply(socket, args);
48310 WebSocket.prototype.emit = function (eventName, data, callback) {
48311 var socket = this.socket;
48312 var rootScope = this.$rootScope;
48313 this.socket.emit(eventName, data, function () {
48314 var args = arguments;
48315 rootScope.$apply(function () {
48317 callback.apply(socket, args);
48323 services.WebSocket = WebSocket;
48324 })(services = app.services || (app.services = {}));
48325 })(app || (app = {}));
48329 (function (services) {
48330 var Console = (function () {
48331 function Console(WebSocket, $rootScope) {
48332 this.WebSocket = WebSocket;
48333 this.$rootScope = $rootScope;
48334 this.WebSocket = WebSocket;
48335 this.$rootScope = $rootScope;
48336 this.directiveIDs = [];
48337 var directiveIDs = this.directiveIDs;
48338 this.WebSocket.on('console', function (d) {
48340 var message = d.message;
48341 if (directiveIDs.indexOf(id) > -1) {
48342 $rootScope.$emit(id, message);
48346 Console.prototype.addDirective = function (id) {
48347 if (!(this.directiveIDs.indexOf(id) > -1)) {
48348 this.directiveIDs.push(id);
48351 Console.prototype.removeDirective = function (id) {
48352 var i = this.directiveIDs.indexOf(id);
48354 this.directiveIDs.splice(i, 1);
48357 Console.prototype.showIDs = function () {
48358 console.log(this.directiveIDs);
48362 services.Console = Console;
48363 })(services = app.services || (app.services = {}));
48364 })(app || (app = {}));
48368 (function (directives) {
48369 var Command = (function () {
48370 function Command() {
48371 this.restrict = 'E';
48372 this.replace = true;
48374 this.controller = 'commandController';
48375 this.controllerAs = 'ctrl';
48376 this.bindToController = {
48382 this.templateUrl = 'templates/command.html';
48384 Command.Factory = function () {
48385 var directive = function () {
48386 return new Command();
48388 directive.$inject = [];
48393 directives.Command = Command;
48394 var CommandController = (function () {
48395 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
48396 this.APIEndPoint = APIEndPoint;
48397 this.$scope = $scope;
48398 this.MyModal = MyModal;
48399 this.WebSocket = WebSocket;
48400 this.$window = $window;
48401 this.$rootScope = $rootScope;
48402 this.Console = Console;
48403 var controller = this;
48405 .getOptionControlFile(this.name)
48407 .then(function (result) {
48408 controller.options = result.info;
48413 .then(function (result) {
48414 controller.dirs = result.info;
48416 this.heading = "[" + this.index + "]: dcdFilePrint";
48417 this.isOpen = true;
48418 this.$scope.$on('close', function () {
48419 controller.isOpen = false;
48423 return Math.floor((1 + Math.random()) * 0x10000)
48427 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
48428 s4() + '-' + s4() + s4() + s4();
48430 this.uuid = guid();
48431 this.Console.addDirective(this.uuid);
48432 this.Console.showIDs();
48434 CommandController.prototype.submit = function () {
48436 angular.forEach(this.options, function (option) {
48438 name: option.option,
48441 angular.forEach(option.arg, function (arg) {
48443 if (typeof arg.input === 'object') {
48444 obj.arguments.push(arg.input.name);
48447 obj.arguments.push(arg.input);
48451 if (obj.arguments.length > 0) {
48456 command: this.name,
48457 workspace: this.workspace.fileId,
48461 .execute(JSON.stringify(execObj))
48462 .then(function (result) {
48463 console.log(result);
48466 CommandController.prototype.removeMySelf = function (index) {
48467 this.$scope.$destroy();
48468 this.Console.removeDirective(this.uuid);
48469 this.remove()(index, this.list);
48470 this.Console.showIDs();
48472 CommandController.prototype.reloadFiles = function () {
48474 var fileId = this.workspace.fileId;
48478 .then(function (result) {
48479 var status = result.status;
48480 if (status === 'success') {
48481 _this.files = result.info;
48484 console.log(result.message);
48488 CommandController.prototype.debug = function () {
48489 var div = angular.element(this.$window.document).find("div");
48492 angular.forEach(div, function (v) {
48493 if (v.className === "panel-body console") {
48496 else if (v.className === "row parameters-console") {
48500 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
48501 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
48502 consoleTag.style.height = consoleHeight;
48503 consoleTag.style.width = consoleWidth;
48505 CommandController.prototype.help = function () {
48508 .then(function (result) {
48509 console.log(result);
48512 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
48513 return CommandController;
48515 directives.CommandController = CommandController;
48516 })(directives = app.directives || (app.directives = {}));
48517 })(app || (app = {}));
48521 (function (directives) {
48522 var HeaderMenu = (function () {
48523 function HeaderMenu() {
48524 this.restrict = 'E';
48525 this.replace = true;
48526 this.templateUrl = 'templates/header-menu.html';
48527 this.controller = 'HeaderMenuController';
48528 this.controllerAs = 'hmc';
48531 HeaderMenu.Factory = function () {
48532 var directive = function () {
48533 return new HeaderMenu();
48539 directives.HeaderMenu = HeaderMenu;
48540 var HeaderMenuController = (function () {
48541 function HeaderMenuController($state) {
48542 this.$state = $state;
48543 this.isExecution = this.$state.current.name === 'execution';
48544 this.isWorkspace = this.$state.current.name === 'workspace';
48545 this.isHistory = this.$state.current.name === 'history';
48547 HeaderMenuController.prototype.transit = function (state) {
48548 this.$state.go(state);
48550 HeaderMenuController.$inject = ['$state'];
48551 return HeaderMenuController;
48553 directives.HeaderMenuController = HeaderMenuController;
48554 })(directives = app.directives || (app.directives = {}));
48555 })(app || (app = {}));
48559 (function (directives) {
48560 var Option = (function () {
48561 function Option() {
48562 this.restrict = 'E';
48563 this.replace = true;
48564 this.controller = 'optionController';
48565 this.bindToController = {
48570 this.templateUrl = 'templates/option.html';
48571 this.controllerAs = 'ctrl';
48573 Option.Factory = function () {
48574 var directive = function () {
48575 return new Option();
48577 directive.$inject = [];
48582 directives.Option = Option;
48583 var OptionController = (function () {
48584 function OptionController() {
48585 var controller = this;
48586 angular.forEach(controller.info.arg, function (arg) {
48587 if (arg.initialValue) {
48588 if (arg.formType === 'number') {
48589 arg.input = parseInt(arg.initialValue);
48592 arg.input = arg.initialValue;
48597 OptionController.$inject = [];
48598 return OptionController;
48600 directives.OptionController = OptionController;
48601 })(directives = app.directives || (app.directives = {}));
48602 })(app || (app = {}));
48606 (function (directives) {
48607 var Directory = (function () {
48608 function Directory() {
48609 this.restrict = 'E';
48610 this.replace = true;
48611 this.controller = 'directoryController';
48612 this.controllerAs = 'ctrl';
48613 this.bindToController = {
48619 this.templateUrl = 'templates/directory.html';
48621 Directory.Factory = function () {
48622 var directive = function () {
48623 return new Directory();
48629 directives.Directory = Directory;
48630 var DirectoryController = (function () {
48631 function DirectoryController(APIEndPoint, $scope) {
48632 this.APIEndPoint = APIEndPoint;
48633 this.$scope = $scope;
48634 var controller = this;
48636 .getFiles(this.info.fileId)
48638 .then(function (result) {
48639 if (result.status === 'success') {
48640 controller.files = result.info;
48641 angular.forEach(result.info, function (file) {
48642 if (file.fileType === '0') {
48644 if (controller.info.path === '/') {
48645 o.path = '/' + file.name;
48648 o.path = controller.info.path + '/' + file.name;
48650 controller.add()(o, controller.list);
48657 DirectoryController.$inject = ['APIEndPoint', '$scope'];
48658 return DirectoryController;
48660 directives.DirectoryController = DirectoryController;
48661 })(directives = app.directives || (app.directives = {}));
48662 })(app || (app = {}));
48666 (function (directives) {
48667 var Upload = (function () {
48668 function Upload() {
48669 this.restrict = 'E';
48670 this.replace = true;
48672 this.controller = 'UploadController';
48673 this.controllerAs = 'ctrl';
48674 this.bindToController = {
48680 this.templateUrl = 'templates/upload.html';
48681 console.log("templates/upload.html-constructor");
48683 Upload.Factory = function () {
48684 var directive = function () {
48685 return new Upload();
48687 directive.$inject = [];
48692 directives.Upload = Upload;
48693 var UploadController = (function () {
48694 function UploadController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
48695 this.APIEndPoint = APIEndPoint;
48696 this.$scope = $scope;
48697 this.MyModal = MyModal;
48698 this.WebSocket = WebSocket;
48699 this.$window = $window;
48700 this.$rootScope = $rootScope;
48701 this.Console = Console;
48702 var controller = this;
48703 console.log("directive.upload-constructor");
48705 UploadController.prototype.submit = function () {
48706 console.log("submit: function not supported¥n");
48708 UploadController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
48709 return UploadController;
48711 directives.UploadController = UploadController;
48712 })(directives = app.directives || (app.directives = {}));
48713 })(app || (app = {}));
48717 (function (controllers) {
48718 var Execution = (function () {
48719 function Execution(MyModal, $scope) {
48720 this.MyModal = MyModal;
48721 this.$scope = $scope;
48722 this.commandInfoList = [];
48725 Execution.prototype.add = function () {
48726 this.$scope.$broadcast('close');
48727 var commandInfoList = this.commandInfoList;
48728 var commandInstance = this.MyModal.selectCommand();
48731 .then(function (command) {
48732 commandInfoList.push(new app.declares.CommandInfo(command));
48735 Execution.prototype.open = function () {
48736 var result = this.MyModal.open('SelectCommand');
48737 console.log(result);
48739 Execution.prototype.remove = function (index, list) {
48740 list.splice(index, 1);
48742 Execution.prototype.close = function () {
48743 console.log("close");
48745 Execution.$inject = ['MyModal', '$scope'];
48748 controllers.Execution = Execution;
48749 })(controllers = app.controllers || (app.controllers = {}));
48750 })(app || (app = {}));
48754 (function (controllers) {
48755 var Workspace = (function () {
48756 function Workspace($scope, APIEndPoint, MyModal) {
48757 this.$scope = $scope;
48758 this.APIEndPoint = APIEndPoint;
48759 this.MyModal = MyModal;
48760 this.directoryList = [];
48761 var controller = this;
48762 var directoryList = this.directoryList;
48764 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
48772 directoryList.push(o);
48774 Workspace.prototype.addDirectory = function (info, directoryList) {
48775 directoryList.push(info);
48777 Workspace.prototype.upload = function () {
48778 this.MyModal.upload();
48780 Workspace.prototype.debug = function () {
48781 this.MyModal.preview();
48783 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
48786 controllers.Workspace = Workspace;
48787 })(controllers = app.controllers || (app.controllers = {}));
48788 })(app || (app = {}));
48792 (function (controllers) {
48793 var History = (function () {
48794 function History($scope) {
48795 this.page = "History";
48797 History.$inject = ['$scope'];
48800 controllers.History = History;
48801 })(controllers = app.controllers || (app.controllers = {}));
48802 })(app || (app = {}));
48806 (function (controllers) {
48807 var SelectCommand = (function () {
48808 function SelectCommand($scope, APIEndPoint, $modalInstance) {
48809 this.APIEndPoint = APIEndPoint;
48810 this.$modalInstance = $modalInstance;
48811 var controller = this;
48814 .$promise.then(function (result) {
48815 controller.tags = result.info;
48819 .$promise.then(function (result) {
48820 controller.commands = result.info;
48822 this.currentTag = 'all';
48824 SelectCommand.prototype.changeTag = function (tag) {
48825 this.currentTag = tag;
48827 SelectCommand.prototype.selectCommand = function (command) {
48828 this.$modalInstance.close(command);
48830 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
48831 return SelectCommand;
48833 controllers.SelectCommand = SelectCommand;
48834 })(controllers = app.controllers || (app.controllers = {}));
48835 })(app || (app = {}));
48839 (function (controllers) {
48840 var Upload = (function () {
48841 function Upload($scope, APIEndPoint, $modalInstance) {
48842 this.APIEndPoint = APIEndPoint;
48843 this.$modalInstance = $modalInstance;
48844 var controller = this;
48845 console.log('controller.upload-controllers');
48847 Upload.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
48850 controllers.Upload = Upload;
48851 })(controllers = app.controllers || (app.controllers = {}));
48852 })(app || (app = {}));
48856 (function (controllers) {
48857 var Preview = (function () {
48858 function Preview($scope, APIEndPoint, $modalInstance) {
48859 this.APIEndPoint = APIEndPoint;
48860 this.$modalInstance = $modalInstance;
48861 var controller = this;
48862 console.log('preview');
48864 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
48867 controllers.Preview = Preview;
48868 })(controllers = app.controllers || (app.controllers = {}));
48869 })(app || (app = {}));
48871 (function (filters) {
48873 return function (commands, tag) {
48875 angular.forEach(commands, function (command) {
48877 angular.forEach(command.tags, function (value) {
48882 result.push(command);
48888 })(filters || (filters = {}));
48892 var appName = 'zephyr';
48893 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
48894 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
48895 $urlRouterProvider.otherwise('/execution');
48896 $locationProvider.html5Mode({
48901 .state('execution', {
48903 templateUrl: 'templates/execution.html',
48904 controller: 'executionController',
48907 .state('workspace', {
48909 templateUrl: 'templates/workspace.html',
48910 controller: 'workspaceController',
48913 .state('history', {
48915 templateUrl: 'templates/history.html',
48916 controller: 'historyController',
48920 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
48921 app.zephyr.service('MyModal', app.services.MyModal);
48922 app.zephyr.service('WebSocket', app.services.WebSocket);
48923 app.zephyr.service('Console', app.services.Console);
48924 app.zephyr.filter('Tag', filters.Tag);
48925 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
48926 app.zephyr.controller('previewController', app.controllers.Preview);
48927 app.zephyr.controller('uploadController', app.controllers.Upload);
48928 app.zephyr.controller('executionController', app.controllers.Execution);
48929 app.zephyr.controller('workspaceController', app.controllers.Workspace);
48930 app.zephyr.controller('historyController', app.controllers.History);
48931 app.zephyr.controller('commandController', app.directives.CommandController);
48932 app.zephyr.controller('optionController', app.directives.OptionController);
48933 app.zephyr.controller('directoryController', app.directives.DirectoryController);
48934 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
48935 app.zephyr.controller('uploadController', app.directives.UploadController);
48936 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
48937 app.zephyr.directive('command', app.directives.Command.Factory());
48938 app.zephyr.directive('option', app.directives.Option.Factory());
48939 app.zephyr.directive('directory', app.directives.Directory.Factory());
48940 })(app || (app = {}));
48945 /***/ function(module, exports) {
48950 (function (declares) {
48951 var CommandInfo = (function () {
48952 function CommandInfo(name) {
48955 return CommandInfo;
48957 declares.CommandInfo = CommandInfo;
48958 })(declares = app.declares || (app.declares = {}));
48959 })(app || (app = {}));
48963 (function (services) {
48964 var APIEndPoint = (function () {
48965 function APIEndPoint($resource, $http) {
48966 this.$resource = $resource;
48967 this.$http = $http;
48969 APIEndPoint.prototype.resource = function (endPoint, data) {
48970 var customAction = {
48976 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
48978 return this.$resource(endPoint, {}, { execute: execute });
48980 APIEndPoint.prototype.getOptionControlFile = function (command) {
48981 var endPoint = '/api/v1/optionControlFile/' + command;
48982 return this.resource(endPoint, {}).get();
48984 APIEndPoint.prototype.getFiles = function (fileId) {
48985 var endPoint = '/api/v1/workspace';
48987 endPoint += '/' + fileId;
48989 return this.resource(endPoint, {}).get();
48991 APIEndPoint.prototype.getDirectories = function () {
48992 var endPoint = '/api/v1/all/workspace/directory';
48993 return this.resource(endPoint, {}).get();
48995 APIEndPoint.prototype.getTags = function () {
48996 var endPoint = '/api/v1/tagList';
48997 return this.resource(endPoint, {}).get();
48999 APIEndPoint.prototype.getCommands = function () {
49000 var endPoint = '/api/v1/commandList';
49001 return this.resource(endPoint, {}).get();
49003 APIEndPoint.prototype.execute = function (data) {
49004 var endPoint = '/api/v1/execution';
49005 var fd = new FormData();
49006 fd.append('data', data);
49007 return this.$http.post(endPoint, fd, {
49008 headers: { 'Content-Type': undefined },
49009 transformRequest: angular.identity
49012 APIEndPoint.prototype.debug = function () {
49013 var endPoint = '/api/v1/debug';
49014 return this.$http.get(endPoint);
49016 APIEndPoint.prototype.upload = function () {
49017 var endPoint = '/api/v1/upload';
49018 return this.$http.get(endPoint);
49020 APIEndPoint.prototype.help = function (command) {
49021 var endPoint = '/api/v1/help/' + command;
49022 return this.$http.get(endPoint);
49024 return APIEndPoint;
49026 services.APIEndPoint = APIEndPoint;
49027 })(services = app.services || (app.services = {}));
49028 })(app || (app = {}));
49032 (function (services) {
49033 var MyModal = (function () {
49034 function MyModal($uibModal) {
49035 this.$uibModal = $uibModal;
49036 this.modalOption = {
49043 MyModal.prototype.open = function (modalName) {
49044 if (modalName === 'SelectCommand') {
49045 this.modalOption.templateUrl = 'templates/select-command.html';
49046 this.modalOption.size = 'lg';
49048 return this.$uibModal.open(this.modalOption);
49050 MyModal.prototype.selectCommand = function () {
49051 this.modalOption.templateUrl = 'templates/select-command.html';
49052 this.modalOption.controller = 'selectCommandController';
49053 this.modalOption.controllerAs = 'c';
49054 this.modalOption.size = 'lg';
49055 return this.$uibModal.open(this.modalOption);
49057 MyModal.prototype.preview = function () {
49058 this.modalOption.templateUrl = 'templates/preview.html';
49059 this.modalOption.controller = 'previewController';
49060 this.modalOption.controllerAs = 'c';
49061 this.modalOption.size = 'lg';
49062 return this.$uibModal.open(this.modalOption);
49064 MyModal.prototype.upload = function () {
49065 this.modalOption.templateUrl = 'templates/upload.html';
49066 this.modalOption.controller = 'uploadController';
49067 this.modalOption.controllerAs = 'c';
49068 this.modalOption.size = 'lg';
49069 return this.$uibModal.open(this.modalOption);
49071 MyModal.$inject = ['$uibModal'];
49074 services.MyModal = MyModal;
49075 })(services = app.services || (app.services = {}));
49076 })(app || (app = {}));
49080 (function (services) {
49081 var WebSocket = (function () {
49082 function WebSocket($rootScope) {
49083 this.$rootScope = $rootScope;
49084 this.socket = io.connect();
49086 WebSocket.prototype.on = function (eventName, callback) {
49087 var socket = this.socket;
49088 var rootScope = this.$rootScope;
49089 socket.on(eventName, function () {
49090 var args = arguments;
49091 rootScope.$apply(function () {
49092 callback.apply(socket, args);
49096 WebSocket.prototype.emit = function (eventName, data, callback) {
49097 var socket = this.socket;
49098 var rootScope = this.$rootScope;
49099 this.socket.emit(eventName, data, function () {
49100 var args = arguments;
49101 rootScope.$apply(function () {
49103 callback.apply(socket, args);
49109 services.WebSocket = WebSocket;
49110 })(services = app.services || (app.services = {}));
49111 })(app || (app = {}));
49115 (function (services) {
49116 var Console = (function () {
49117 function Console(WebSocket, $rootScope) {
49118 this.WebSocket = WebSocket;
49119 this.$rootScope = $rootScope;
49120 this.WebSocket = WebSocket;
49121 this.$rootScope = $rootScope;
49122 this.directiveIDs = [];
49123 var directiveIDs = this.directiveIDs;
49124 this.WebSocket.on('console', function (d) {
49126 var message = d.message;
49127 if (directiveIDs.indexOf(id) > -1) {
49128 $rootScope.$emit(id, message);
49132 Console.prototype.addDirective = function (id) {
49133 if (!(this.directiveIDs.indexOf(id) > -1)) {
49134 this.directiveIDs.push(id);
49137 Console.prototype.removeDirective = function (id) {
49138 var i = this.directiveIDs.indexOf(id);
49140 this.directiveIDs.splice(i, 1);
49143 Console.prototype.showIDs = function () {
49144 console.log(this.directiveIDs);
49148 services.Console = Console;
49149 })(services = app.services || (app.services = {}));
49150 })(app || (app = {}));
49154 (function (directives) {
49155 var Command = (function () {
49156 function Command() {
49157 this.restrict = 'E';
49158 this.replace = true;
49160 this.controller = 'commandController';
49161 this.controllerAs = 'ctrl';
49162 this.bindToController = {
49168 this.templateUrl = 'templates/command.html';
49170 Command.Factory = function () {
49171 var directive = function () {
49172 return new Command();
49174 directive.$inject = [];
49179 directives.Command = Command;
49180 var CommandController = (function () {
49181 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
49182 this.APIEndPoint = APIEndPoint;
49183 this.$scope = $scope;
49184 this.MyModal = MyModal;
49185 this.WebSocket = WebSocket;
49186 this.$window = $window;
49187 this.$rootScope = $rootScope;
49188 this.Console = Console;
49189 var controller = this;
49191 .getOptionControlFile(this.name)
49193 .then(function (result) {
49194 controller.options = result.info;
49199 .then(function (result) {
49200 controller.dirs = result.info;
49202 this.heading = "[" + this.index + "]: dcdFilePrint";
49203 this.isOpen = true;
49204 this.$scope.$on('close', function () {
49205 controller.isOpen = false;
49209 return Math.floor((1 + Math.random()) * 0x10000)
49213 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
49214 s4() + '-' + s4() + s4() + s4();
49216 this.uuid = guid();
49217 this.Console.addDirective(this.uuid);
49218 this.Console.showIDs();
49220 CommandController.prototype.submit = function () {
49222 angular.forEach(this.options, function (option) {
49224 name: option.option,
49227 angular.forEach(option.arg, function (arg) {
49229 if (typeof arg.input === 'object') {
49230 obj.arguments.push(arg.input.name);
49233 obj.arguments.push(arg.input);
49237 if (obj.arguments.length > 0) {
49242 command: this.name,
49243 workspace: this.workspace.fileId,
49247 .execute(JSON.stringify(execObj))
49248 .then(function (result) {
49249 console.log(result);
49252 CommandController.prototype.removeMySelf = function (index) {
49253 this.$scope.$destroy();
49254 this.Console.removeDirective(this.uuid);
49255 this.remove()(index, this.list);
49256 this.Console.showIDs();
49258 CommandController.prototype.reloadFiles = function () {
49260 var fileId = this.workspace.fileId;
49264 .then(function (result) {
49265 var status = result.status;
49266 if (status === 'success') {
49267 _this.files = result.info;
49270 console.log(result.message);
49274 CommandController.prototype.debug = function () {
49275 var div = angular.element(this.$window.document).find("div");
49278 angular.forEach(div, function (v) {
49279 if (v.className === "panel-body console") {
49282 else if (v.className === "row parameters-console") {
49286 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
49287 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
49288 consoleTag.style.height = consoleHeight;
49289 consoleTag.style.width = consoleWidth;
49291 CommandController.prototype.help = function () {
49294 .then(function (result) {
49295 console.log(result);
49298 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
49299 return CommandController;
49301 directives.CommandController = CommandController;
49302 })(directives = app.directives || (app.directives = {}));
49303 })(app || (app = {}));
49307 (function (directives) {
49308 var HeaderMenu = (function () {
49309 function HeaderMenu() {
49310 this.restrict = 'E';
49311 this.replace = true;
49312 this.templateUrl = 'templates/header-menu.html';
49313 this.controller = 'HeaderMenuController';
49314 this.controllerAs = 'hmc';
49317 HeaderMenu.Factory = function () {
49318 var directive = function () {
49319 return new HeaderMenu();
49325 directives.HeaderMenu = HeaderMenu;
49326 var HeaderMenuController = (function () {
49327 function HeaderMenuController($state) {
49328 this.$state = $state;
49329 this.isExecution = this.$state.current.name === 'execution';
49330 this.isWorkspace = this.$state.current.name === 'workspace';
49331 this.isHistory = this.$state.current.name === 'history';
49333 HeaderMenuController.prototype.transit = function (state) {
49334 this.$state.go(state);
49336 HeaderMenuController.$inject = ['$state'];
49337 return HeaderMenuController;
49339 directives.HeaderMenuController = HeaderMenuController;
49340 })(directives = app.directives || (app.directives = {}));
49341 })(app || (app = {}));
49345 (function (directives) {
49346 var Option = (function () {
49347 function Option() {
49348 this.restrict = 'E';
49349 this.replace = true;
49350 this.controller = 'optionController';
49351 this.bindToController = {
49356 this.templateUrl = 'templates/option.html';
49357 this.controllerAs = 'ctrl';
49359 Option.Factory = function () {
49360 var directive = function () {
49361 return new Option();
49363 directive.$inject = [];
49368 directives.Option = Option;
49369 var OptionController = (function () {
49370 function OptionController() {
49371 var controller = this;
49372 angular.forEach(controller.info.arg, function (arg) {
49373 if (arg.initialValue) {
49374 if (arg.formType === 'number') {
49375 arg.input = parseInt(arg.initialValue);
49378 arg.input = arg.initialValue;
49383 OptionController.$inject = [];
49384 return OptionController;
49386 directives.OptionController = OptionController;
49387 })(directives = app.directives || (app.directives = {}));
49388 })(app || (app = {}));
49392 (function (directives) {
49393 var Directory = (function () {
49394 function Directory() {
49395 this.restrict = 'E';
49396 this.replace = true;
49397 this.controller = 'directoryController';
49398 this.controllerAs = 'ctrl';
49399 this.bindToController = {
49405 this.templateUrl = 'templates/directory.html';
49407 Directory.Factory = function () {
49408 var directive = function () {
49409 return new Directory();
49415 directives.Directory = Directory;
49416 var DirectoryController = (function () {
49417 function DirectoryController(APIEndPoint, $scope) {
49418 this.APIEndPoint = APIEndPoint;
49419 this.$scope = $scope;
49420 var controller = this;
49422 .getFiles(this.info.fileId)
49424 .then(function (result) {
49425 if (result.status === 'success') {
49426 controller.files = result.info;
49427 angular.forEach(result.info, function (file) {
49428 if (file.fileType === '0') {
49430 if (controller.info.path === '/') {
49431 o.path = '/' + file.name;
49434 o.path = controller.info.path + '/' + file.name;
49436 controller.add()(o, controller.list);
49443 DirectoryController.$inject = ['APIEndPoint', '$scope'];
49444 return DirectoryController;
49446 directives.DirectoryController = DirectoryController;
49447 })(directives = app.directives || (app.directives = {}));
49448 })(app || (app = {}));
49452 (function (directives) {
49453 var Upload = (function () {
49454 function Upload() {
49455 this.restrict = 'E';
49456 this.replace = true;
49458 this.controller = 'UploadController';
49459 this.controllerAs = 'ctrl';
49460 this.bindToController = {
49466 this.templateUrl = 'templates/upload.html';
49467 console.log("templates/upload.html-constructor");
49469 Upload.Factory = function () {
49470 var directive = function () {
49471 return new Upload();
49473 directive.$inject = [];
49478 directives.Upload = Upload;
49479 var UploadController = (function () {
49480 function UploadController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
49481 this.APIEndPoint = APIEndPoint;
49482 this.$scope = $scope;
49483 this.MyModal = MyModal;
49484 this.WebSocket = WebSocket;
49485 this.$window = $window;
49486 this.$rootScope = $rootScope;
49487 this.Console = Console;
49488 var controller = this;
49489 console.log("directive.upload-constructor");
49491 UploadController.prototype.submit = function () {
49492 console.log("submit: function not supported¥n");
49494 UploadController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
49495 return UploadController;
49497 directives.UploadController = UploadController;
49498 })(directives = app.directives || (app.directives = {}));
49499 })(app || (app = {}));
49503 (function (controllers) {
49504 var Execution = (function () {
49505 function Execution(MyModal, $scope) {
49506 this.MyModal = MyModal;
49507 this.$scope = $scope;
49508 this.commandInfoList = [];
49511 Execution.prototype.add = function () {
49512 this.$scope.$broadcast('close');
49513 var commandInfoList = this.commandInfoList;
49514 var commandInstance = this.MyModal.selectCommand();
49517 .then(function (command) {
49518 commandInfoList.push(new app.declares.CommandInfo(command));
49521 Execution.prototype.open = function () {
49522 var result = this.MyModal.open('SelectCommand');
49523 console.log(result);
49525 Execution.prototype.remove = function (index, list) {
49526 list.splice(index, 1);
49528 Execution.prototype.close = function () {
49529 console.log("close");
49531 Execution.$inject = ['MyModal', '$scope'];
49534 controllers.Execution = Execution;
49535 })(controllers = app.controllers || (app.controllers = {}));
49536 })(app || (app = {}));
49540 (function (controllers) {
49541 var Workspace = (function () {
49542 function Workspace($scope, APIEndPoint, MyModal) {
49543 this.$scope = $scope;
49544 this.APIEndPoint = APIEndPoint;
49545 this.MyModal = MyModal;
49546 this.directoryList = [];
49547 var controller = this;
49548 var directoryList = this.directoryList;
49550 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
49558 directoryList.push(o);
49560 Workspace.prototype.addDirectory = function (info, directoryList) {
49561 directoryList.push(info);
49563 Workspace.prototype.upload = function () {
49564 this.MyModal.upload();
49566 Workspace.prototype.debug = function () {
49567 this.MyModal.preview();
49569 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
49572 controllers.Workspace = Workspace;
49573 })(controllers = app.controllers || (app.controllers = {}));
49574 })(app || (app = {}));
49578 (function (controllers) {
49579 var History = (function () {
49580 function History($scope) {
49581 this.page = "History";
49583 History.$inject = ['$scope'];
49586 controllers.History = History;
49587 })(controllers = app.controllers || (app.controllers = {}));
49588 })(app || (app = {}));
49592 (function (controllers) {
49593 var SelectCommand = (function () {
49594 function SelectCommand($scope, APIEndPoint, $modalInstance) {
49595 this.APIEndPoint = APIEndPoint;
49596 this.$modalInstance = $modalInstance;
49597 var controller = this;
49600 .$promise.then(function (result) {
49601 controller.tags = result.info;
49605 .$promise.then(function (result) {
49606 controller.commands = result.info;
49608 this.currentTag = 'all';
49610 SelectCommand.prototype.changeTag = function (tag) {
49611 this.currentTag = tag;
49613 SelectCommand.prototype.selectCommand = function (command) {
49614 this.$modalInstance.close(command);
49616 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
49617 return SelectCommand;
49619 controllers.SelectCommand = SelectCommand;
49620 })(controllers = app.controllers || (app.controllers = {}));
49621 })(app || (app = {}));
49625 (function (controllers) {
49626 var Upload = (function () {
49627 function Upload($scope, APIEndPoint, $modalInstance) {
49628 this.APIEndPoint = APIEndPoint;
49629 this.$modalInstance = $modalInstance;
49630 var controller = this;
49631 console.log('controller.upload-controllers');
49633 Upload.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
49636 controllers.Upload = Upload;
49637 })(controllers = app.controllers || (app.controllers = {}));
49638 })(app || (app = {}));
49642 (function (controllers) {
49643 var Preview = (function () {
49644 function Preview($scope, APIEndPoint, $modalInstance) {
49645 this.APIEndPoint = APIEndPoint;
49646 this.$modalInstance = $modalInstance;
49647 var controller = this;
49648 console.log('preview');
49650 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
49653 controllers.Preview = Preview;
49654 })(controllers = app.controllers || (app.controllers = {}));
49655 })(app || (app = {}));
49657 (function (filters) {
49659 return function (commands, tag) {
49661 angular.forEach(commands, function (command) {
49663 angular.forEach(command.tags, function (value) {
49668 result.push(command);
49674 })(filters || (filters = {}));
49678 var appName = 'zephyr';
49679 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
49680 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
49681 $urlRouterProvider.otherwise('/execution');
49682 $locationProvider.html5Mode({
49687 .state('execution', {
49689 templateUrl: 'templates/execution.html',
49690 controller: 'executionController',
49693 .state('workspace', {
49695 templateUrl: 'templates/workspace.html',
49696 controller: 'workspaceController',
49699 .state('history', {
49701 templateUrl: 'templates/history.html',
49702 controller: 'historyController',
49706 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
49707 app.zephyr.service('MyModal', app.services.MyModal);
49708 app.zephyr.service('WebSocket', app.services.WebSocket);
49709 app.zephyr.service('Console', app.services.Console);
49710 app.zephyr.filter('Tag', filters.Tag);
49711 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
49712 app.zephyr.controller('previewController', app.controllers.Preview);
49713 app.zephyr.controller('uploadController', app.controllers.Upload);
49714 app.zephyr.controller('executionController', app.controllers.Execution);
49715 app.zephyr.controller('workspaceController', app.controllers.Workspace);
49716 app.zephyr.controller('historyController', app.controllers.History);
49717 app.zephyr.controller('commandController', app.directives.CommandController);
49718 app.zephyr.controller('optionController', app.directives.OptionController);
49719 app.zephyr.controller('directoryController', app.directives.DirectoryController);
49720 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
49721 app.zephyr.controller('uploadController', app.directives.UploadController);
49722 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
49723 app.zephyr.directive('command', app.directives.Command.Factory());
49724 app.zephyr.directive('option', app.directives.Option.Factory());
49725 app.zephyr.directive('directory', app.directives.Directory.Factory());
49726 })(app || (app = {}));
49731 /***/ function(module, exports) {
49736 (function (declares) {
49737 var CommandInfo = (function () {
49738 function CommandInfo(name) {
49741 return CommandInfo;
49743 declares.CommandInfo = CommandInfo;
49744 })(declares = app.declares || (app.declares = {}));
49745 })(app || (app = {}));
49749 (function (services) {
49750 var APIEndPoint = (function () {
49751 function APIEndPoint($resource, $http) {
49752 this.$resource = $resource;
49753 this.$http = $http;
49755 APIEndPoint.prototype.resource = function (endPoint, data) {
49756 var customAction = {
49762 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
49764 return this.$resource(endPoint, {}, { execute: execute });
49766 APIEndPoint.prototype.getOptionControlFile = function (command) {
49767 var endPoint = '/api/v1/optionControlFile/' + command;
49768 return this.resource(endPoint, {}).get();
49770 APIEndPoint.prototype.getFiles = function (fileId) {
49771 var endPoint = '/api/v1/workspace';
49773 endPoint += '/' + fileId;
49775 return this.resource(endPoint, {}).get();
49777 APIEndPoint.prototype.getDirectories = function () {
49778 var endPoint = '/api/v1/all/workspace/directory';
49779 return this.resource(endPoint, {}).get();
49781 APIEndPoint.prototype.getTags = function () {
49782 var endPoint = '/api/v1/tagList';
49783 return this.resource(endPoint, {}).get();
49785 APIEndPoint.prototype.getCommands = function () {
49786 var endPoint = '/api/v1/commandList';
49787 return this.resource(endPoint, {}).get();
49789 APIEndPoint.prototype.execute = function (data) {
49790 var endPoint = '/api/v1/execution';
49791 var fd = new FormData();
49792 fd.append('data', data);
49793 return this.$http.post(endPoint, fd, {
49794 headers: { 'Content-Type': undefined },
49795 transformRequest: angular.identity
49798 APIEndPoint.prototype.debug = function () {
49799 var endPoint = '/api/v1/debug';
49800 return this.$http.get(endPoint);
49802 APIEndPoint.prototype.upload = function () {
49803 var endPoint = '/api/v1/upload';
49804 return this.$http.get(endPoint);
49806 APIEndPoint.prototype.help = function (command) {
49807 var endPoint = '/api/v1/help/' + command;
49808 return this.$http.get(endPoint);
49810 return APIEndPoint;
49812 services.APIEndPoint = APIEndPoint;
49813 })(services = app.services || (app.services = {}));
49814 })(app || (app = {}));
49818 (function (services) {
49819 var MyModal = (function () {
49820 function MyModal($uibModal) {
49821 this.$uibModal = $uibModal;
49822 this.modalOption = {
49829 MyModal.prototype.open = function (modalName) {
49830 if (modalName === 'SelectCommand') {
49831 this.modalOption.templateUrl = 'templates/select-command.html';
49832 this.modalOption.size = 'lg';
49834 return this.$uibModal.open(this.modalOption);
49836 MyModal.prototype.selectCommand = function () {
49837 this.modalOption.templateUrl = 'templates/select-command.html';
49838 this.modalOption.controller = 'selectCommandController';
49839 this.modalOption.controllerAs = 'c';
49840 this.modalOption.size = 'lg';
49841 return this.$uibModal.open(this.modalOption);
49843 MyModal.prototype.preview = function () {
49844 this.modalOption.templateUrl = 'templates/preview.html';
49845 this.modalOption.controller = 'previewController';
49846 this.modalOption.controllerAs = 'c';
49847 this.modalOption.size = 'lg';
49848 return this.$uibModal.open(this.modalOption);
49850 MyModal.prototype.upload = function () {
49851 this.modalOption.templateUrl = 'templates/upload.html';
49852 this.modalOption.controller = 'uploadController';
49853 this.modalOption.controllerAs = 'c';
49854 this.modalOption.size = 'lg';
49855 return this.$uibModal.open(this.modalOption);
49857 MyModal.$inject = ['$uibModal'];
49860 services.MyModal = MyModal;
49861 })(services = app.services || (app.services = {}));
49862 })(app || (app = {}));
49866 (function (services) {
49867 var WebSocket = (function () {
49868 function WebSocket($rootScope) {
49869 this.$rootScope = $rootScope;
49870 this.socket = io.connect();
49872 WebSocket.prototype.on = function (eventName, callback) {
49873 var socket = this.socket;
49874 var rootScope = this.$rootScope;
49875 socket.on(eventName, function () {
49876 var args = arguments;
49877 rootScope.$apply(function () {
49878 callback.apply(socket, args);
49882 WebSocket.prototype.emit = function (eventName, data, callback) {
49883 var socket = this.socket;
49884 var rootScope = this.$rootScope;
49885 this.socket.emit(eventName, data, function () {
49886 var args = arguments;
49887 rootScope.$apply(function () {
49889 callback.apply(socket, args);
49895 services.WebSocket = WebSocket;
49896 })(services = app.services || (app.services = {}));
49897 })(app || (app = {}));
49901 (function (services) {
49902 var Console = (function () {
49903 function Console(WebSocket, $rootScope) {
49904 this.WebSocket = WebSocket;
49905 this.$rootScope = $rootScope;
49906 this.WebSocket = WebSocket;
49907 this.$rootScope = $rootScope;
49908 this.directiveIDs = [];
49909 var directiveIDs = this.directiveIDs;
49910 this.WebSocket.on('console', function (d) {
49912 var message = d.message;
49913 if (directiveIDs.indexOf(id) > -1) {
49914 $rootScope.$emit(id, message);
49918 Console.prototype.addDirective = function (id) {
49919 if (!(this.directiveIDs.indexOf(id) > -1)) {
49920 this.directiveIDs.push(id);
49923 Console.prototype.removeDirective = function (id) {
49924 var i = this.directiveIDs.indexOf(id);
49926 this.directiveIDs.splice(i, 1);
49929 Console.prototype.showIDs = function () {
49930 console.log(this.directiveIDs);
49934 services.Console = Console;
49935 })(services = app.services || (app.services = {}));
49936 })(app || (app = {}));
49940 (function (directives) {
49941 var Command = (function () {
49942 function Command() {
49943 this.restrict = 'E';
49944 this.replace = true;
49946 this.controller = 'commandController';
49947 this.controllerAs = 'ctrl';
49948 this.bindToController = {
49954 this.templateUrl = 'templates/command.html';
49956 Command.Factory = function () {
49957 var directive = function () {
49958 return new Command();
49960 directive.$inject = [];
49965 directives.Command = Command;
49966 var CommandController = (function () {
49967 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
49968 this.APIEndPoint = APIEndPoint;
49969 this.$scope = $scope;
49970 this.MyModal = MyModal;
49971 this.WebSocket = WebSocket;
49972 this.$window = $window;
49973 this.$rootScope = $rootScope;
49974 this.Console = Console;
49975 var controller = this;
49977 .getOptionControlFile(this.name)
49979 .then(function (result) {
49980 controller.options = result.info;
49985 .then(function (result) {
49986 controller.dirs = result.info;
49988 this.heading = "[" + this.index + "]: dcdFilePrint";
49989 this.isOpen = true;
49990 this.$scope.$on('close', function () {
49991 controller.isOpen = false;
49995 return Math.floor((1 + Math.random()) * 0x10000)
49999 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
50000 s4() + '-' + s4() + s4() + s4();
50002 this.uuid = guid();
50003 this.Console.addDirective(this.uuid);
50004 this.Console.showIDs();
50006 CommandController.prototype.submit = function () {
50008 angular.forEach(this.options, function (option) {
50010 name: option.option,
50013 angular.forEach(option.arg, function (arg) {
50015 if (typeof arg.input === 'object') {
50016 obj.arguments.push(arg.input.name);
50019 obj.arguments.push(arg.input);
50023 if (obj.arguments.length > 0) {
50028 command: this.name,
50029 workspace: this.workspace.fileId,
50033 .execute(JSON.stringify(execObj))
50034 .then(function (result) {
50035 console.log(result);
50038 CommandController.prototype.removeMySelf = function (index) {
50039 this.$scope.$destroy();
50040 this.Console.removeDirective(this.uuid);
50041 this.remove()(index, this.list);
50042 this.Console.showIDs();
50044 CommandController.prototype.reloadFiles = function () {
50046 var fileId = this.workspace.fileId;
50050 .then(function (result) {
50051 var status = result.status;
50052 if (status === 'success') {
50053 _this.files = result.info;
50056 console.log(result.message);
50060 CommandController.prototype.debug = function () {
50061 var div = angular.element(this.$window.document).find("div");
50064 angular.forEach(div, function (v) {
50065 if (v.className === "panel-body console") {
50068 else if (v.className === "row parameters-console") {
50072 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
50073 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
50074 consoleTag.style.height = consoleHeight;
50075 consoleTag.style.width = consoleWidth;
50077 CommandController.prototype.help = function () {
50080 .then(function (result) {
50081 console.log(result);
50084 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
50085 return CommandController;
50087 directives.CommandController = CommandController;
50088 })(directives = app.directives || (app.directives = {}));
50089 })(app || (app = {}));
50093 (function (directives) {
50094 var HeaderMenu = (function () {
50095 function HeaderMenu() {
50096 this.restrict = 'E';
50097 this.replace = true;
50098 this.templateUrl = 'templates/header-menu.html';
50099 this.controller = 'HeaderMenuController';
50100 this.controllerAs = 'hmc';
50103 HeaderMenu.Factory = function () {
50104 var directive = function () {
50105 return new HeaderMenu();
50111 directives.HeaderMenu = HeaderMenu;
50112 var HeaderMenuController = (function () {
50113 function HeaderMenuController($state) {
50114 this.$state = $state;
50115 this.isExecution = this.$state.current.name === 'execution';
50116 this.isWorkspace = this.$state.current.name === 'workspace';
50117 this.isHistory = this.$state.current.name === 'history';
50119 HeaderMenuController.prototype.transit = function (state) {
50120 this.$state.go(state);
50122 HeaderMenuController.$inject = ['$state'];
50123 return HeaderMenuController;
50125 directives.HeaderMenuController = HeaderMenuController;
50126 })(directives = app.directives || (app.directives = {}));
50127 })(app || (app = {}));
50131 (function (directives) {
50132 var Option = (function () {
50133 function Option() {
50134 this.restrict = 'E';
50135 this.replace = true;
50136 this.controller = 'optionController';
50137 this.bindToController = {
50142 this.templateUrl = 'templates/option.html';
50143 this.controllerAs = 'ctrl';
50145 Option.Factory = function () {
50146 var directive = function () {
50147 return new Option();
50149 directive.$inject = [];
50154 directives.Option = Option;
50155 var OptionController = (function () {
50156 function OptionController() {
50157 var controller = this;
50158 angular.forEach(controller.info.arg, function (arg) {
50159 if (arg.initialValue) {
50160 if (arg.formType === 'number') {
50161 arg.input = parseInt(arg.initialValue);
50164 arg.input = arg.initialValue;
50169 OptionController.$inject = [];
50170 return OptionController;
50172 directives.OptionController = OptionController;
50173 })(directives = app.directives || (app.directives = {}));
50174 })(app || (app = {}));
50178 (function (directives) {
50179 var Directory = (function () {
50180 function Directory() {
50181 this.restrict = 'E';
50182 this.replace = true;
50183 this.controller = 'directoryController';
50184 this.controllerAs = 'ctrl';
50185 this.bindToController = {
50191 this.templateUrl = 'templates/directory.html';
50193 Directory.Factory = function () {
50194 var directive = function () {
50195 return new Directory();
50201 directives.Directory = Directory;
50202 var DirectoryController = (function () {
50203 function DirectoryController(APIEndPoint, $scope) {
50204 this.APIEndPoint = APIEndPoint;
50205 this.$scope = $scope;
50206 var controller = this;
50208 .getFiles(this.info.fileId)
50210 .then(function (result) {
50211 if (result.status === 'success') {
50212 controller.files = result.info;
50213 angular.forEach(result.info, function (file) {
50214 if (file.fileType === '0') {
50216 if (controller.info.path === '/') {
50217 o.path = '/' + file.name;
50220 o.path = controller.info.path + '/' + file.name;
50222 controller.add()(o, controller.list);
50229 DirectoryController.$inject = ['APIEndPoint', '$scope'];
50230 return DirectoryController;
50232 directives.DirectoryController = DirectoryController;
50233 })(directives = app.directives || (app.directives = {}));
50234 })(app || (app = {}));
50238 (function (directives) {
50239 var Upload = (function () {
50240 function Upload() {
50241 this.restrict = 'E';
50242 this.replace = true;
50244 this.controller = 'UploadController';
50245 this.controllerAs = 'ctrl';
50246 this.bindToController = {
50252 this.templateUrl = 'templates/upload.html';
50253 console.log("templates/upload.html-constructor");
50255 Upload.Factory = function () {
50256 var directive = function () {
50257 return new Upload();
50259 directive.$inject = [];
50264 directives.Upload = Upload;
50265 var UploadController = (function () {
50266 function UploadController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
50267 this.APIEndPoint = APIEndPoint;
50268 this.$scope = $scope;
50269 this.MyModal = MyModal;
50270 this.WebSocket = WebSocket;
50271 this.$window = $window;
50272 this.$rootScope = $rootScope;
50273 this.Console = Console;
50274 var controller = this;
50275 console.log("directive.upload-constructor");
50277 UploadController.prototype.submit = function () {
50278 console.log("submit: function not supported¥n");
50280 UploadController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
50281 return UploadController;
50283 directives.UploadController = UploadController;
50284 })(directives = app.directives || (app.directives = {}));
50285 })(app || (app = {}));
50289 (function (controllers) {
50290 var Execution = (function () {
50291 function Execution(MyModal, $scope) {
50292 this.MyModal = MyModal;
50293 this.$scope = $scope;
50294 this.commandInfoList = [];
50297 Execution.prototype.add = function () {
50298 this.$scope.$broadcast('close');
50299 var commandInfoList = this.commandInfoList;
50300 var commandInstance = this.MyModal.selectCommand();
50303 .then(function (command) {
50304 commandInfoList.push(new app.declares.CommandInfo(command));
50307 Execution.prototype.open = function () {
50308 var result = this.MyModal.open('SelectCommand');
50309 console.log(result);
50311 Execution.prototype.remove = function (index, list) {
50312 list.splice(index, 1);
50314 Execution.prototype.close = function () {
50315 console.log("close");
50317 Execution.$inject = ['MyModal', '$scope'];
50320 controllers.Execution = Execution;
50321 })(controllers = app.controllers || (app.controllers = {}));
50322 })(app || (app = {}));
50326 (function (controllers) {
50327 var Workspace = (function () {
50328 function Workspace($scope, APIEndPoint, MyModal) {
50329 this.$scope = $scope;
50330 this.APIEndPoint = APIEndPoint;
50331 this.MyModal = MyModal;
50332 this.directoryList = [];
50333 var controller = this;
50334 var directoryList = this.directoryList;
50336 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
50344 directoryList.push(o);
50346 Workspace.prototype.addDirectory = function (info, directoryList) {
50347 directoryList.push(info);
50349 Workspace.prototype.upload = function () {
50350 this.MyModal.upload();
50352 Workspace.prototype.debug = function () {
50353 this.MyModal.preview();
50355 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
50358 controllers.Workspace = Workspace;
50359 })(controllers = app.controllers || (app.controllers = {}));
50360 })(app || (app = {}));
50364 (function (controllers) {
50365 var History = (function () {
50366 function History($scope) {
50367 this.page = "History";
50369 History.$inject = ['$scope'];
50372 controllers.History = History;
50373 })(controllers = app.controllers || (app.controllers = {}));
50374 })(app || (app = {}));
50378 (function (controllers) {
50379 var SelectCommand = (function () {
50380 function SelectCommand($scope, APIEndPoint, $modalInstance) {
50381 this.APIEndPoint = APIEndPoint;
50382 this.$modalInstance = $modalInstance;
50383 var controller = this;
50386 .$promise.then(function (result) {
50387 controller.tags = result.info;
50391 .$promise.then(function (result) {
50392 controller.commands = result.info;
50394 this.currentTag = 'all';
50396 SelectCommand.prototype.changeTag = function (tag) {
50397 this.currentTag = tag;
50399 SelectCommand.prototype.selectCommand = function (command) {
50400 this.$modalInstance.close(command);
50402 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
50403 return SelectCommand;
50405 controllers.SelectCommand = SelectCommand;
50406 })(controllers = app.controllers || (app.controllers = {}));
50407 })(app || (app = {}));
50411 (function (controllers) {
50412 var Upload = (function () {
50413 function Upload($scope, APIEndPoint, $modalInstance) {
50414 this.APIEndPoint = APIEndPoint;
50415 this.$modalInstance = $modalInstance;
50416 var controller = this;
50417 console.log('controller.upload-controllers');
50419 Upload.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
50422 controllers.Upload = Upload;
50423 })(controllers = app.controllers || (app.controllers = {}));
50424 })(app || (app = {}));
50428 (function (controllers) {
50429 var Preview = (function () {
50430 function Preview($scope, APIEndPoint, $modalInstance) {
50431 this.APIEndPoint = APIEndPoint;
50432 this.$modalInstance = $modalInstance;
50433 var controller = this;
50434 console.log('preview');
50436 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
50439 controllers.Preview = Preview;
50440 })(controllers = app.controllers || (app.controllers = {}));
50441 })(app || (app = {}));
50443 (function (filters) {
50445 return function (commands, tag) {
50447 angular.forEach(commands, function (command) {
50449 angular.forEach(command.tags, function (value) {
50454 result.push(command);
50460 })(filters || (filters = {}));
50464 var appName = 'zephyr';
50465 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
50466 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
50467 $urlRouterProvider.otherwise('/execution');
50468 $locationProvider.html5Mode({
50473 .state('execution', {
50475 templateUrl: 'templates/execution.html',
50476 controller: 'executionController',
50479 .state('workspace', {
50481 templateUrl: 'templates/workspace.html',
50482 controller: 'workspaceController',
50485 .state('history', {
50487 templateUrl: 'templates/history.html',
50488 controller: 'historyController',
50492 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
50493 app.zephyr.service('MyModal', app.services.MyModal);
50494 app.zephyr.service('WebSocket', app.services.WebSocket);
50495 app.zephyr.service('Console', app.services.Console);
50496 app.zephyr.filter('Tag', filters.Tag);
50497 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
50498 app.zephyr.controller('previewController', app.controllers.Preview);
50499 app.zephyr.controller('uploadController', app.controllers.Upload);
50500 app.zephyr.controller('executionController', app.controllers.Execution);
50501 app.zephyr.controller('workspaceController', app.controllers.Workspace);
50502 app.zephyr.controller('historyController', app.controllers.History);
50503 app.zephyr.controller('commandController', app.directives.CommandController);
50504 app.zephyr.controller('optionController', app.directives.OptionController);
50505 app.zephyr.controller('directoryController', app.directives.DirectoryController);
50506 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
50507 app.zephyr.controller('uploadController', app.directives.UploadController);
50508 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
50509 app.zephyr.directive('command', app.directives.Command.Factory());
50510 app.zephyr.directive('option', app.directives.Option.Factory());
50511 app.zephyr.directive('directory', app.directives.Directory.Factory());
50512 })(app || (app = {}));
50517 /***/ function(module, exports) {
50522 (function (declares) {
50523 var CommandInfo = (function () {
50524 function CommandInfo(name) {
50527 return CommandInfo;
50529 declares.CommandInfo = CommandInfo;
50530 })(declares = app.declares || (app.declares = {}));
50531 })(app || (app = {}));
50535 (function (services) {
50536 var APIEndPoint = (function () {
50537 function APIEndPoint($resource, $http) {
50538 this.$resource = $resource;
50539 this.$http = $http;
50541 APIEndPoint.prototype.resource = function (endPoint, data) {
50542 var customAction = {
50548 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
50550 return this.$resource(endPoint, {}, { execute: execute });
50552 APIEndPoint.prototype.getOptionControlFile = function (command) {
50553 var endPoint = '/api/v1/optionControlFile/' + command;
50554 return this.resource(endPoint, {}).get();
50556 APIEndPoint.prototype.getFiles = function (fileId) {
50557 var endPoint = '/api/v1/workspace';
50559 endPoint += '/' + fileId;
50561 return this.resource(endPoint, {}).get();
50563 APIEndPoint.prototype.getDirectories = function () {
50564 var endPoint = '/api/v1/all/workspace/directory';
50565 return this.resource(endPoint, {}).get();
50567 APIEndPoint.prototype.getTags = function () {
50568 var endPoint = '/api/v1/tagList';
50569 return this.resource(endPoint, {}).get();
50571 APIEndPoint.prototype.getCommands = function () {
50572 var endPoint = '/api/v1/commandList';
50573 return this.resource(endPoint, {}).get();
50575 APIEndPoint.prototype.execute = function (data) {
50576 var endPoint = '/api/v1/execution';
50577 var fd = new FormData();
50578 fd.append('data', data);
50579 return this.$http.post(endPoint, fd, {
50580 headers: { 'Content-Type': undefined },
50581 transformRequest: angular.identity
50584 APIEndPoint.prototype.debug = function () {
50585 var endPoint = '/api/v1/debug';
50586 return this.$http.get(endPoint);
50588 APIEndPoint.prototype.upload = function () {
50589 var endPoint = '/api/v1/upload';
50590 return this.$http.get(endPoint);
50592 APIEndPoint.prototype.help = function (command) {
50593 var endPoint = '/api/v1/help/' + command;
50594 return this.$http.get(endPoint);
50596 return APIEndPoint;
50598 services.APIEndPoint = APIEndPoint;
50599 })(services = app.services || (app.services = {}));
50600 })(app || (app = {}));
50604 (function (services) {
50605 var MyModal = (function () {
50606 function MyModal($uibModal) {
50607 this.$uibModal = $uibModal;
50608 this.modalOption = {
50615 MyModal.prototype.open = function (modalName) {
50616 if (modalName === 'SelectCommand') {
50617 this.modalOption.templateUrl = 'templates/select-command.html';
50618 this.modalOption.size = 'lg';
50620 return this.$uibModal.open(this.modalOption);
50622 MyModal.prototype.selectCommand = function () {
50623 this.modalOption.templateUrl = 'templates/select-command.html';
50624 this.modalOption.controller = 'selectCommandController';
50625 this.modalOption.controllerAs = 'c';
50626 this.modalOption.size = 'lg';
50627 return this.$uibModal.open(this.modalOption);
50629 MyModal.prototype.preview = function () {
50630 this.modalOption.templateUrl = 'templates/preview.html';
50631 this.modalOption.controller = 'previewController';
50632 this.modalOption.controllerAs = 'c';
50633 this.modalOption.size = 'lg';
50634 return this.$uibModal.open(this.modalOption);
50636 MyModal.prototype.upload = function () {
50637 this.modalOption.templateUrl = 'templates/upload.html';
50638 this.modalOption.controller = 'uploadController';
50639 this.modalOption.controllerAs = 'c';
50640 this.modalOption.size = 'lg';
50641 return this.$uibModal.open(this.modalOption);
50643 MyModal.$inject = ['$uibModal'];
50646 services.MyModal = MyModal;
50647 })(services = app.services || (app.services = {}));
50648 })(app || (app = {}));
50652 (function (services) {
50653 var WebSocket = (function () {
50654 function WebSocket($rootScope) {
50655 this.$rootScope = $rootScope;
50656 this.socket = io.connect();
50658 WebSocket.prototype.on = function (eventName, callback) {
50659 var socket = this.socket;
50660 var rootScope = this.$rootScope;
50661 socket.on(eventName, function () {
50662 var args = arguments;
50663 rootScope.$apply(function () {
50664 callback.apply(socket, args);
50668 WebSocket.prototype.emit = function (eventName, data, callback) {
50669 var socket = this.socket;
50670 var rootScope = this.$rootScope;
50671 this.socket.emit(eventName, data, function () {
50672 var args = arguments;
50673 rootScope.$apply(function () {
50675 callback.apply(socket, args);
50681 services.WebSocket = WebSocket;
50682 })(services = app.services || (app.services = {}));
50683 })(app || (app = {}));
50687 (function (services) {
50688 var Console = (function () {
50689 function Console(WebSocket, $rootScope) {
50690 this.WebSocket = WebSocket;
50691 this.$rootScope = $rootScope;
50692 this.WebSocket = WebSocket;
50693 this.$rootScope = $rootScope;
50694 this.directiveIDs = [];
50695 var directiveIDs = this.directiveIDs;
50696 this.WebSocket.on('console', function (d) {
50698 var message = d.message;
50699 if (directiveIDs.indexOf(id) > -1) {
50700 $rootScope.$emit(id, message);
50704 Console.prototype.addDirective = function (id) {
50705 if (!(this.directiveIDs.indexOf(id) > -1)) {
50706 this.directiveIDs.push(id);
50709 Console.prototype.removeDirective = function (id) {
50710 var i = this.directiveIDs.indexOf(id);
50712 this.directiveIDs.splice(i, 1);
50715 Console.prototype.showIDs = function () {
50716 console.log(this.directiveIDs);
50720 services.Console = Console;
50721 })(services = app.services || (app.services = {}));
50722 })(app || (app = {}));
50726 (function (directives) {
50727 var Command = (function () {
50728 function Command() {
50729 this.restrict = 'E';
50730 this.replace = true;
50732 this.controller = 'commandController';
50733 this.controllerAs = 'ctrl';
50734 this.bindToController = {
50740 this.templateUrl = 'templates/command.html';
50742 Command.Factory = function () {
50743 var directive = function () {
50744 return new Command();
50746 directive.$inject = [];
50751 directives.Command = Command;
50752 var CommandController = (function () {
50753 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
50754 this.APIEndPoint = APIEndPoint;
50755 this.$scope = $scope;
50756 this.MyModal = MyModal;
50757 this.WebSocket = WebSocket;
50758 this.$window = $window;
50759 this.$rootScope = $rootScope;
50760 this.Console = Console;
50761 var controller = this;
50763 .getOptionControlFile(this.name)
50765 .then(function (result) {
50766 controller.options = result.info;
50771 .then(function (result) {
50772 controller.dirs = result.info;
50774 this.heading = "[" + this.index + "]: dcdFilePrint";
50775 this.isOpen = true;
50776 this.$scope.$on('close', function () {
50777 controller.isOpen = false;
50781 return Math.floor((1 + Math.random()) * 0x10000)
50785 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
50786 s4() + '-' + s4() + s4() + s4();
50788 this.uuid = guid();
50789 this.Console.addDirective(this.uuid);
50790 this.Console.showIDs();
50792 CommandController.prototype.submit = function () {
50794 angular.forEach(this.options, function (option) {
50796 name: option.option,
50799 angular.forEach(option.arg, function (arg) {
50801 if (typeof arg.input === 'object') {
50802 obj.arguments.push(arg.input.name);
50805 obj.arguments.push(arg.input);
50809 if (obj.arguments.length > 0) {
50814 command: this.name,
50815 workspace: this.workspace.fileId,
50819 .execute(JSON.stringify(execObj))
50820 .then(function (result) {
50821 console.log(result);
50824 CommandController.prototype.removeMySelf = function (index) {
50825 this.$scope.$destroy();
50826 this.Console.removeDirective(this.uuid);
50827 this.remove()(index, this.list);
50828 this.Console.showIDs();
50830 CommandController.prototype.reloadFiles = function () {
50832 var fileId = this.workspace.fileId;
50836 .then(function (result) {
50837 var status = result.status;
50838 if (status === 'success') {
50839 _this.files = result.info;
50842 console.log(result.message);
50846 CommandController.prototype.debug = function () {
50847 var div = angular.element(this.$window.document).find("div");
50850 angular.forEach(div, function (v) {
50851 if (v.className === "panel-body console") {
50854 else if (v.className === "row parameters-console") {
50858 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
50859 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
50860 consoleTag.style.height = consoleHeight;
50861 consoleTag.style.width = consoleWidth;
50863 CommandController.prototype.help = function () {
50866 .then(function (result) {
50867 console.log(result);
50870 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
50871 return CommandController;
50873 directives.CommandController = CommandController;
50874 })(directives = app.directives || (app.directives = {}));
50875 })(app || (app = {}));
50879 (function (directives) {
50880 var HeaderMenu = (function () {
50881 function HeaderMenu() {
50882 this.restrict = 'E';
50883 this.replace = true;
50884 this.templateUrl = 'templates/header-menu.html';
50885 this.controller = 'HeaderMenuController';
50886 this.controllerAs = 'hmc';
50889 HeaderMenu.Factory = function () {
50890 var directive = function () {
50891 return new HeaderMenu();
50897 directives.HeaderMenu = HeaderMenu;
50898 var HeaderMenuController = (function () {
50899 function HeaderMenuController($state) {
50900 this.$state = $state;
50901 this.isExecution = this.$state.current.name === 'execution';
50902 this.isWorkspace = this.$state.current.name === 'workspace';
50903 this.isHistory = this.$state.current.name === 'history';
50905 HeaderMenuController.prototype.transit = function (state) {
50906 this.$state.go(state);
50908 HeaderMenuController.$inject = ['$state'];
50909 return HeaderMenuController;
50911 directives.HeaderMenuController = HeaderMenuController;
50912 })(directives = app.directives || (app.directives = {}));
50913 })(app || (app = {}));
50917 (function (directives) {
50918 var Option = (function () {
50919 function Option() {
50920 this.restrict = 'E';
50921 this.replace = true;
50922 this.controller = 'optionController';
50923 this.bindToController = {
50928 this.templateUrl = 'templates/option.html';
50929 this.controllerAs = 'ctrl';
50931 Option.Factory = function () {
50932 var directive = function () {
50933 return new Option();
50935 directive.$inject = [];
50940 directives.Option = Option;
50941 var OptionController = (function () {
50942 function OptionController() {
50943 var controller = this;
50944 angular.forEach(controller.info.arg, function (arg) {
50945 if (arg.initialValue) {
50946 if (arg.formType === 'number') {
50947 arg.input = parseInt(arg.initialValue);
50950 arg.input = arg.initialValue;
50955 OptionController.$inject = [];
50956 return OptionController;
50958 directives.OptionController = OptionController;
50959 })(directives = app.directives || (app.directives = {}));
50960 })(app || (app = {}));
50964 (function (directives) {
50965 var Directory = (function () {
50966 function Directory() {
50967 this.restrict = 'E';
50968 this.replace = true;
50969 this.controller = 'directoryController';
50970 this.controllerAs = 'ctrl';
50971 this.bindToController = {
50977 this.templateUrl = 'templates/directory.html';
50979 Directory.Factory = function () {
50980 var directive = function () {
50981 return new Directory();
50987 directives.Directory = Directory;
50988 var DirectoryController = (function () {
50989 function DirectoryController(APIEndPoint, $scope) {
50990 this.APIEndPoint = APIEndPoint;
50991 this.$scope = $scope;
50992 var controller = this;
50994 .getFiles(this.info.fileId)
50996 .then(function (result) {
50997 if (result.status === 'success') {
50998 controller.files = result.info;
50999 angular.forEach(result.info, function (file) {
51000 if (file.fileType === '0') {
51002 if (controller.info.path === '/') {
51003 o.path = '/' + file.name;
51006 o.path = controller.info.path + '/' + file.name;
51008 controller.add()(o, controller.list);
51015 DirectoryController.$inject = ['APIEndPoint', '$scope'];
51016 return DirectoryController;
51018 directives.DirectoryController = DirectoryController;
51019 })(directives = app.directives || (app.directives = {}));
51020 })(app || (app = {}));
51024 (function (directives) {
51025 var Upload = (function () {
51026 function Upload() {
51027 this.restrict = 'E';
51028 this.replace = true;
51030 this.controller = 'UploadController';
51031 this.controllerAs = 'ctrl';
51032 this.bindToController = {
51038 this.templateUrl = 'templates/upload.html';
51039 console.log("templates/upload.html-constructor");
51041 Upload.Factory = function () {
51042 var directive = function () {
51043 return new Upload();
51045 directive.$inject = [];
51050 directives.Upload = Upload;
51051 var UploadController = (function () {
51052 function UploadController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
51053 this.APIEndPoint = APIEndPoint;
51054 this.$scope = $scope;
51055 this.MyModal = MyModal;
51056 this.WebSocket = WebSocket;
51057 this.$window = $window;
51058 this.$rootScope = $rootScope;
51059 this.Console = Console;
51060 var controller = this;
51061 console.log("directive.upload-constructor");
51063 UploadController.prototype.submit = function () {
51064 console.log("submit: function not supported¥n");
51066 UploadController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
51067 return UploadController;
51069 directives.UploadController = UploadController;
51070 })(directives = app.directives || (app.directives = {}));
51071 })(app || (app = {}));
51075 (function (controllers) {
51076 var Execution = (function () {
51077 function Execution(MyModal, $scope) {
51078 this.MyModal = MyModal;
51079 this.$scope = $scope;
51080 this.commandInfoList = [];
51083 Execution.prototype.add = function () {
51084 this.$scope.$broadcast('close');
51085 var commandInfoList = this.commandInfoList;
51086 var commandInstance = this.MyModal.selectCommand();
51089 .then(function (command) {
51090 commandInfoList.push(new app.declares.CommandInfo(command));
51093 Execution.prototype.open = function () {
51094 var result = this.MyModal.open('SelectCommand');
51095 console.log(result);
51097 Execution.prototype.remove = function (index, list) {
51098 list.splice(index, 1);
51100 Execution.prototype.close = function () {
51101 console.log("close");
51103 Execution.$inject = ['MyModal', '$scope'];
51106 controllers.Execution = Execution;
51107 })(controllers = app.controllers || (app.controllers = {}));
51108 })(app || (app = {}));
51112 (function (controllers) {
51113 var Workspace = (function () {
51114 function Workspace($scope, APIEndPoint, MyModal) {
51115 this.$scope = $scope;
51116 this.APIEndPoint = APIEndPoint;
51117 this.MyModal = MyModal;
51118 this.directoryList = [];
51119 var controller = this;
51120 var directoryList = this.directoryList;
51122 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
51130 directoryList.push(o);
51132 Workspace.prototype.addDirectory = function (info, directoryList) {
51133 directoryList.push(info);
51135 Workspace.prototype.upload = function () {
51136 this.MyModal.upload();
51138 Workspace.prototype.debug = function () {
51139 this.MyModal.preview();
51141 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
51144 controllers.Workspace = Workspace;
51145 })(controllers = app.controllers || (app.controllers = {}));
51146 })(app || (app = {}));
51150 (function (controllers) {
51151 var History = (function () {
51152 function History($scope) {
51153 this.page = "History";
51155 History.$inject = ['$scope'];
51158 controllers.History = History;
51159 })(controllers = app.controllers || (app.controllers = {}));
51160 })(app || (app = {}));
51164 (function (controllers) {
51165 var SelectCommand = (function () {
51166 function SelectCommand($scope, APIEndPoint, $modalInstance) {
51167 this.APIEndPoint = APIEndPoint;
51168 this.$modalInstance = $modalInstance;
51169 var controller = this;
51172 .$promise.then(function (result) {
51173 controller.tags = result.info;
51177 .$promise.then(function (result) {
51178 controller.commands = result.info;
51180 this.currentTag = 'all';
51182 SelectCommand.prototype.changeTag = function (tag) {
51183 this.currentTag = tag;
51185 SelectCommand.prototype.selectCommand = function (command) {
51186 this.$modalInstance.close(command);
51188 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
51189 return SelectCommand;
51191 controllers.SelectCommand = SelectCommand;
51192 })(controllers = app.controllers || (app.controllers = {}));
51193 })(app || (app = {}));
51197 (function (controllers) {
51198 var Upload = (function () {
51199 function Upload($scope, APIEndPoint, $modalInstance) {
51200 this.APIEndPoint = APIEndPoint;
51201 this.$modalInstance = $modalInstance;
51202 var controller = this;
51203 console.log('controller.upload-controllers');
51205 Upload.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
51208 controllers.Upload = Upload;
51209 })(controllers = app.controllers || (app.controllers = {}));
51210 })(app || (app = {}));
51214 (function (controllers) {
51215 var Preview = (function () {
51216 function Preview($scope, APIEndPoint, $modalInstance) {
51217 this.APIEndPoint = APIEndPoint;
51218 this.$modalInstance = $modalInstance;
51219 var controller = this;
51220 console.log('preview');
51222 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
51225 controllers.Preview = Preview;
51226 })(controllers = app.controllers || (app.controllers = {}));
51227 })(app || (app = {}));
51229 (function (filters) {
51231 return function (commands, tag) {
51233 angular.forEach(commands, function (command) {
51235 angular.forEach(command.tags, function (value) {
51240 result.push(command);
51246 })(filters || (filters = {}));
51250 var appName = 'zephyr';
51251 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
51252 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
51253 $urlRouterProvider.otherwise('/execution');
51254 $locationProvider.html5Mode({
51259 .state('execution', {
51261 templateUrl: 'templates/execution.html',
51262 controller: 'executionController',
51265 .state('workspace', {
51267 templateUrl: 'templates/workspace.html',
51268 controller: 'workspaceController',
51271 .state('history', {
51273 templateUrl: 'templates/history.html',
51274 controller: 'historyController',
51278 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
51279 app.zephyr.service('MyModal', app.services.MyModal);
51280 app.zephyr.service('WebSocket', app.services.WebSocket);
51281 app.zephyr.service('Console', app.services.Console);
51282 app.zephyr.filter('Tag', filters.Tag);
51283 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
51284 app.zephyr.controller('previewController', app.controllers.Preview);
51285 app.zephyr.controller('uploadController', app.controllers.Upload);
51286 app.zephyr.controller('executionController', app.controllers.Execution);
51287 app.zephyr.controller('workspaceController', app.controllers.Workspace);
51288 app.zephyr.controller('historyController', app.controllers.History);
51289 app.zephyr.controller('commandController', app.directives.CommandController);
51290 app.zephyr.controller('optionController', app.directives.OptionController);
51291 app.zephyr.controller('directoryController', app.directives.DirectoryController);
51292 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
51293 app.zephyr.controller('uploadController', app.directives.UploadController);
51294 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
51295 app.zephyr.directive('command', app.directives.Command.Factory());
51296 app.zephyr.directive('option', app.directives.Option.Factory());
51297 app.zephyr.directive('directory', app.directives.Directory.Factory());
51298 })(app || (app = {}));
51303 /***/ function(module, exports) {
51308 (function (declares) {
51309 var CommandInfo = (function () {
51310 function CommandInfo(name) {
51313 return CommandInfo;
51315 declares.CommandInfo = CommandInfo;
51316 })(declares = app.declares || (app.declares = {}));
51317 })(app || (app = {}));
51321 (function (services) {
51322 var APIEndPoint = (function () {
51323 function APIEndPoint($resource, $http) {
51324 this.$resource = $resource;
51325 this.$http = $http;
51327 APIEndPoint.prototype.resource = function (endPoint, data) {
51328 var customAction = {
51334 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
51336 return this.$resource(endPoint, {}, { execute: execute });
51338 APIEndPoint.prototype.getOptionControlFile = function (command) {
51339 var endPoint = '/api/v1/optionControlFile/' + command;
51340 return this.resource(endPoint, {}).get();
51342 APIEndPoint.prototype.getFiles = function (fileId) {
51343 var endPoint = '/api/v1/workspace';
51345 endPoint += '/' + fileId;
51347 return this.resource(endPoint, {}).get();
51349 APIEndPoint.prototype.getDirectories = function () {
51350 var endPoint = '/api/v1/all/workspace/directory';
51351 return this.resource(endPoint, {}).get();
51353 APIEndPoint.prototype.getTags = function () {
51354 var endPoint = '/api/v1/tagList';
51355 return this.resource(endPoint, {}).get();
51357 APIEndPoint.prototype.getCommands = function () {
51358 var endPoint = '/api/v1/commandList';
51359 return this.resource(endPoint, {}).get();
51361 APIEndPoint.prototype.execute = function (data) {
51362 var endPoint = '/api/v1/execution';
51363 var fd = new FormData();
51364 fd.append('data', data);
51365 return this.$http.post(endPoint, fd, {
51366 headers: { 'Content-Type': undefined },
51367 transformRequest: angular.identity
51370 APIEndPoint.prototype.debug = function () {
51371 var endPoint = '/api/v1/debug';
51372 return this.$http.get(endPoint);
51374 APIEndPoint.prototype.upload = function () {
51375 var endPoint = '/api/v1/upload';
51376 return this.$http.get(endPoint);
51378 APIEndPoint.prototype.help = function (command) {
51379 var endPoint = '/api/v1/help/' + command;
51380 return this.$http.get(endPoint);
51382 return APIEndPoint;
51384 services.APIEndPoint = APIEndPoint;
51385 })(services = app.services || (app.services = {}));
51386 })(app || (app = {}));
51390 (function (services) {
51391 var MyModal = (function () {
51392 function MyModal($uibModal) {
51393 this.$uibModal = $uibModal;
51394 this.modalOption = {
51401 MyModal.prototype.open = function (modalName) {
51402 if (modalName === 'SelectCommand') {
51403 this.modalOption.templateUrl = 'templates/select-command.html';
51404 this.modalOption.size = 'lg';
51406 return this.$uibModal.open(this.modalOption);
51408 MyModal.prototype.selectCommand = function () {
51409 this.modalOption.templateUrl = 'templates/select-command.html';
51410 this.modalOption.controller = 'selectCommandController';
51411 this.modalOption.controllerAs = 'c';
51412 this.modalOption.size = 'lg';
51413 return this.$uibModal.open(this.modalOption);
51415 MyModal.prototype.preview = function () {
51416 this.modalOption.templateUrl = 'templates/preview.html';
51417 this.modalOption.controller = 'previewController';
51418 this.modalOption.controllerAs = 'c';
51419 this.modalOption.size = 'lg';
51420 return this.$uibModal.open(this.modalOption);
51422 MyModal.prototype.upload = function () {
51423 this.modalOption.templateUrl = 'templates/upload.html';
51424 this.modalOption.controller = 'uploadController';
51425 this.modalOption.controllerAs = 'c';
51426 this.modalOption.size = 'lg';
51427 return this.$uibModal.open(this.modalOption);
51429 MyModal.$inject = ['$uibModal'];
51432 services.MyModal = MyModal;
51433 })(services = app.services || (app.services = {}));
51434 })(app || (app = {}));
51438 (function (services) {
51439 var WebSocket = (function () {
51440 function WebSocket($rootScope) {
51441 this.$rootScope = $rootScope;
51442 this.socket = io.connect();
51444 WebSocket.prototype.on = function (eventName, callback) {
51445 var socket = this.socket;
51446 var rootScope = this.$rootScope;
51447 socket.on(eventName, function () {
51448 var args = arguments;
51449 rootScope.$apply(function () {
51450 callback.apply(socket, args);
51454 WebSocket.prototype.emit = function (eventName, data, callback) {
51455 var socket = this.socket;
51456 var rootScope = this.$rootScope;
51457 this.socket.emit(eventName, data, function () {
51458 var args = arguments;
51459 rootScope.$apply(function () {
51461 callback.apply(socket, args);
51467 services.WebSocket = WebSocket;
51468 })(services = app.services || (app.services = {}));
51469 })(app || (app = {}));
51473 (function (services) {
51474 var Console = (function () {
51475 function Console(WebSocket, $rootScope) {
51476 this.WebSocket = WebSocket;
51477 this.$rootScope = $rootScope;
51478 this.WebSocket = WebSocket;
51479 this.$rootScope = $rootScope;
51480 this.directiveIDs = [];
51481 var directiveIDs = this.directiveIDs;
51482 this.WebSocket.on('console', function (d) {
51484 var message = d.message;
51485 if (directiveIDs.indexOf(id) > -1) {
51486 $rootScope.$emit(id, message);
51490 Console.prototype.addDirective = function (id) {
51491 if (!(this.directiveIDs.indexOf(id) > -1)) {
51492 this.directiveIDs.push(id);
51495 Console.prototype.removeDirective = function (id) {
51496 var i = this.directiveIDs.indexOf(id);
51498 this.directiveIDs.splice(i, 1);
51501 Console.prototype.showIDs = function () {
51502 console.log(this.directiveIDs);
51506 services.Console = Console;
51507 })(services = app.services || (app.services = {}));
51508 })(app || (app = {}));
51512 (function (directives) {
51513 var Command = (function () {
51514 function Command() {
51515 this.restrict = 'E';
51516 this.replace = true;
51518 this.controller = 'commandController';
51519 this.controllerAs = 'ctrl';
51520 this.bindToController = {
51526 this.templateUrl = 'templates/command.html';
51528 Command.Factory = function () {
51529 var directive = function () {
51530 return new Command();
51532 directive.$inject = [];
51537 directives.Command = Command;
51538 var CommandController = (function () {
51539 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
51540 this.APIEndPoint = APIEndPoint;
51541 this.$scope = $scope;
51542 this.MyModal = MyModal;
51543 this.WebSocket = WebSocket;
51544 this.$window = $window;
51545 this.$rootScope = $rootScope;
51546 this.Console = Console;
51547 var controller = this;
51549 .getOptionControlFile(this.name)
51551 .then(function (result) {
51552 controller.options = result.info;
51557 .then(function (result) {
51558 controller.dirs = result.info;
51560 this.heading = "[" + this.index + "]: dcdFilePrint";
51561 this.isOpen = true;
51562 this.$scope.$on('close', function () {
51563 controller.isOpen = false;
51567 return Math.floor((1 + Math.random()) * 0x10000)
51571 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
51572 s4() + '-' + s4() + s4() + s4();
51574 this.uuid = guid();
51575 this.Console.addDirective(this.uuid);
51576 this.Console.showIDs();
51578 CommandController.prototype.submit = function () {
51580 angular.forEach(this.options, function (option) {
51582 name: option.option,
51585 angular.forEach(option.arg, function (arg) {
51587 if (typeof arg.input === 'object') {
51588 obj.arguments.push(arg.input.name);
51591 obj.arguments.push(arg.input);
51595 if (obj.arguments.length > 0) {
51600 command: this.name,
51601 workspace: this.workspace.fileId,
51605 .execute(JSON.stringify(execObj))
51606 .then(function (result) {
51607 console.log(result);
51610 CommandController.prototype.removeMySelf = function (index) {
51611 this.$scope.$destroy();
51612 this.Console.removeDirective(this.uuid);
51613 this.remove()(index, this.list);
51614 this.Console.showIDs();
51616 CommandController.prototype.reloadFiles = function () {
51618 var fileId = this.workspace.fileId;
51622 .then(function (result) {
51623 var status = result.status;
51624 if (status === 'success') {
51625 _this.files = result.info;
51628 console.log(result.message);
51632 CommandController.prototype.debug = function () {
51633 var div = angular.element(this.$window.document).find("div");
51636 angular.forEach(div, function (v) {
51637 if (v.className === "panel-body console") {
51640 else if (v.className === "row parameters-console") {
51644 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
51645 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
51646 consoleTag.style.height = consoleHeight;
51647 consoleTag.style.width = consoleWidth;
51649 CommandController.prototype.help = function () {
51652 .then(function (result) {
51653 console.log(result);
51656 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
51657 return CommandController;
51659 directives.CommandController = CommandController;
51660 })(directives = app.directives || (app.directives = {}));
51661 })(app || (app = {}));
51665 (function (directives) {
51666 var HeaderMenu = (function () {
51667 function HeaderMenu() {
51668 this.restrict = 'E';
51669 this.replace = true;
51670 this.templateUrl = 'templates/header-menu.html';
51671 this.controller = 'HeaderMenuController';
51672 this.controllerAs = 'hmc';
51675 HeaderMenu.Factory = function () {
51676 var directive = function () {
51677 return new HeaderMenu();
51683 directives.HeaderMenu = HeaderMenu;
51684 var HeaderMenuController = (function () {
51685 function HeaderMenuController($state) {
51686 this.$state = $state;
51687 this.isExecution = this.$state.current.name === 'execution';
51688 this.isWorkspace = this.$state.current.name === 'workspace';
51689 this.isHistory = this.$state.current.name === 'history';
51691 HeaderMenuController.prototype.transit = function (state) {
51692 this.$state.go(state);
51694 HeaderMenuController.$inject = ['$state'];
51695 return HeaderMenuController;
51697 directives.HeaderMenuController = HeaderMenuController;
51698 })(directives = app.directives || (app.directives = {}));
51699 })(app || (app = {}));
51703 (function (directives) {
51704 var Option = (function () {
51705 function Option() {
51706 this.restrict = 'E';
51707 this.replace = true;
51708 this.controller = 'optionController';
51709 this.bindToController = {
51714 this.templateUrl = 'templates/option.html';
51715 this.controllerAs = 'ctrl';
51717 Option.Factory = function () {
51718 var directive = function () {
51719 return new Option();
51721 directive.$inject = [];
51726 directives.Option = Option;
51727 var OptionController = (function () {
51728 function OptionController() {
51729 var controller = this;
51730 angular.forEach(controller.info.arg, function (arg) {
51731 if (arg.initialValue) {
51732 if (arg.formType === 'number') {
51733 arg.input = parseInt(arg.initialValue);
51736 arg.input = arg.initialValue;
51741 OptionController.$inject = [];
51742 return OptionController;
51744 directives.OptionController = OptionController;
51745 })(directives = app.directives || (app.directives = {}));
51746 })(app || (app = {}));
51750 (function (directives) {
51751 var Directory = (function () {
51752 function Directory() {
51753 this.restrict = 'E';
51754 this.replace = true;
51755 this.controller = 'directoryController';
51756 this.controllerAs = 'ctrl';
51757 this.bindToController = {
51763 this.templateUrl = 'templates/directory.html';
51765 Directory.Factory = function () {
51766 var directive = function () {
51767 return new Directory();
51773 directives.Directory = Directory;
51774 var DirectoryController = (function () {
51775 function DirectoryController(APIEndPoint, $scope) {
51776 this.APIEndPoint = APIEndPoint;
51777 this.$scope = $scope;
51778 var controller = this;
51780 .getFiles(this.info.fileId)
51782 .then(function (result) {
51783 if (result.status === 'success') {
51784 controller.files = result.info;
51785 angular.forEach(result.info, function (file) {
51786 if (file.fileType === '0') {
51788 if (controller.info.path === '/') {
51789 o.path = '/' + file.name;
51792 o.path = controller.info.path + '/' + file.name;
51794 controller.add()(o, controller.list);
51801 DirectoryController.$inject = ['APIEndPoint', '$scope'];
51802 return DirectoryController;
51804 directives.DirectoryController = DirectoryController;
51805 })(directives = app.directives || (app.directives = {}));
51806 })(app || (app = {}));
51810 (function (directives) {
51811 var Upload = (function () {
51812 function Upload() {
51813 this.restrict = 'E';
51814 this.replace = true;
51816 this.controller = 'UploadController';
51817 this.controllerAs = 'ctrl';
51818 this.bindToController = {
51824 this.templateUrl = 'templates/upload.html';
51825 console.log("templates/upload.html-constructor");
51827 Upload.Factory = function () {
51828 var directive = function () {
51829 return new Upload();
51831 directive.$inject = [];
51836 directives.Upload = Upload;
51837 var UploadController = (function () {
51838 function UploadController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
51839 this.APIEndPoint = APIEndPoint;
51840 this.$scope = $scope;
51841 this.MyModal = MyModal;
51842 this.WebSocket = WebSocket;
51843 this.$window = $window;
51844 this.$rootScope = $rootScope;
51845 this.Console = Console;
51846 var controller = this;
51847 console.log("directive.upload-constructor");
51849 UploadController.prototype.submit = function () {
51850 console.log("submit: function not supported¥n");
51852 UploadController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
51853 return UploadController;
51855 directives.UploadController = UploadController;
51856 })(directives = app.directives || (app.directives = {}));
51857 })(app || (app = {}));
51861 (function (controllers) {
51862 var Execution = (function () {
51863 function Execution(MyModal, $scope) {
51864 this.MyModal = MyModal;
51865 this.$scope = $scope;
51866 this.commandInfoList = [];
51869 Execution.prototype.add = function () {
51870 this.$scope.$broadcast('close');
51871 var commandInfoList = this.commandInfoList;
51872 var commandInstance = this.MyModal.selectCommand();
51875 .then(function (command) {
51876 commandInfoList.push(new app.declares.CommandInfo(command));
51879 Execution.prototype.open = function () {
51880 var result = this.MyModal.open('SelectCommand');
51881 console.log(result);
51883 Execution.prototype.remove = function (index, list) {
51884 list.splice(index, 1);
51886 Execution.prototype.close = function () {
51887 console.log("close");
51889 Execution.$inject = ['MyModal', '$scope'];
51892 controllers.Execution = Execution;
51893 })(controllers = app.controllers || (app.controllers = {}));
51894 })(app || (app = {}));
51898 (function (controllers) {
51899 var Workspace = (function () {
51900 function Workspace($scope, APIEndPoint, MyModal) {
51901 this.$scope = $scope;
51902 this.APIEndPoint = APIEndPoint;
51903 this.MyModal = MyModal;
51904 this.directoryList = [];
51905 var controller = this;
51906 var directoryList = this.directoryList;
51908 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
51916 directoryList.push(o);
51918 Workspace.prototype.addDirectory = function (info, directoryList) {
51919 directoryList.push(info);
51921 Workspace.prototype.upload = function () {
51922 this.MyModal.upload();
51924 Workspace.prototype.debug = function () {
51925 this.MyModal.preview();
51927 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
51930 controllers.Workspace = Workspace;
51931 })(controllers = app.controllers || (app.controllers = {}));
51932 })(app || (app = {}));
51936 (function (controllers) {
51937 var History = (function () {
51938 function History($scope) {
51939 this.page = "History";
51941 History.$inject = ['$scope'];
51944 controllers.History = History;
51945 })(controllers = app.controllers || (app.controllers = {}));
51946 })(app || (app = {}));
51950 (function (controllers) {
51951 var SelectCommand = (function () {
51952 function SelectCommand($scope, APIEndPoint, $modalInstance) {
51953 this.APIEndPoint = APIEndPoint;
51954 this.$modalInstance = $modalInstance;
51955 var controller = this;
51958 .$promise.then(function (result) {
51959 controller.tags = result.info;
51963 .$promise.then(function (result) {
51964 controller.commands = result.info;
51966 this.currentTag = 'all';
51968 SelectCommand.prototype.changeTag = function (tag) {
51969 this.currentTag = tag;
51971 SelectCommand.prototype.selectCommand = function (command) {
51972 this.$modalInstance.close(command);
51974 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
51975 return SelectCommand;
51977 controllers.SelectCommand = SelectCommand;
51978 })(controllers = app.controllers || (app.controllers = {}));
51979 })(app || (app = {}));
51983 (function (controllers) {
51984 var Upload = (function () {
51985 function Upload($scope, APIEndPoint, $modalInstance) {
51986 this.APIEndPoint = APIEndPoint;
51987 this.$modalInstance = $modalInstance;
51988 var controller = this;
51989 console.log('controller.upload-controllers');
51991 Upload.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
51994 controllers.Upload = Upload;
51995 })(controllers = app.controllers || (app.controllers = {}));
51996 })(app || (app = {}));
52000 (function (controllers) {
52001 var Preview = (function () {
52002 function Preview($scope, APIEndPoint, $modalInstance) {
52003 this.APIEndPoint = APIEndPoint;
52004 this.$modalInstance = $modalInstance;
52005 var controller = this;
52006 console.log('preview');
52008 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
52011 controllers.Preview = Preview;
52012 })(controllers = app.controllers || (app.controllers = {}));
52013 })(app || (app = {}));
52015 (function (filters) {
52017 return function (commands, tag) {
52019 angular.forEach(commands, function (command) {
52021 angular.forEach(command.tags, function (value) {
52026 result.push(command);
52032 })(filters || (filters = {}));
52036 var appName = 'zephyr';
52037 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
52038 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
52039 $urlRouterProvider.otherwise('/execution');
52040 $locationProvider.html5Mode({
52045 .state('execution', {
52047 templateUrl: 'templates/execution.html',
52048 controller: 'executionController',
52051 .state('workspace', {
52053 templateUrl: 'templates/workspace.html',
52054 controller: 'workspaceController',
52057 .state('history', {
52059 templateUrl: 'templates/history.html',
52060 controller: 'historyController',
52064 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
52065 app.zephyr.service('MyModal', app.services.MyModal);
52066 app.zephyr.service('WebSocket', app.services.WebSocket);
52067 app.zephyr.service('Console', app.services.Console);
52068 app.zephyr.filter('Tag', filters.Tag);
52069 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
52070 app.zephyr.controller('previewController', app.controllers.Preview);
52071 app.zephyr.controller('uploadController', app.controllers.Upload);
52072 app.zephyr.controller('executionController', app.controllers.Execution);
52073 app.zephyr.controller('workspaceController', app.controllers.Workspace);
52074 app.zephyr.controller('historyController', app.controllers.History);
52075 app.zephyr.controller('commandController', app.directives.CommandController);
52076 app.zephyr.controller('optionController', app.directives.OptionController);
52077 app.zephyr.controller('directoryController', app.directives.DirectoryController);
52078 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
52079 app.zephyr.controller('uploadController', app.directives.UploadController);
52080 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
52081 app.zephyr.directive('command', app.directives.Command.Factory());
52082 app.zephyr.directive('option', app.directives.Option.Factory());
52083 app.zephyr.directive('directory', app.directives.Directory.Factory());
52084 })(app || (app = {}));
52089 /***/ function(module, exports) {
52094 (function (declares) {
52095 var CommandInfo = (function () {
52096 function CommandInfo(name) {
52099 return CommandInfo;
52101 declares.CommandInfo = CommandInfo;
52102 })(declares = app.declares || (app.declares = {}));
52103 })(app || (app = {}));
52107 (function (services) {
52108 var APIEndPoint = (function () {
52109 function APIEndPoint($resource, $http) {
52110 this.$resource = $resource;
52111 this.$http = $http;
52113 APIEndPoint.prototype.resource = function (endPoint, data) {
52114 var customAction = {
52120 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
52122 return this.$resource(endPoint, {}, { execute: execute });
52124 APIEndPoint.prototype.getOptionControlFile = function (command) {
52125 var endPoint = '/api/v1/optionControlFile/' + command;
52126 return this.resource(endPoint, {}).get();
52128 APIEndPoint.prototype.getFiles = function (fileId) {
52129 var endPoint = '/api/v1/workspace';
52131 endPoint += '/' + fileId;
52133 return this.resource(endPoint, {}).get();
52135 APIEndPoint.prototype.getDirectories = function () {
52136 var endPoint = '/api/v1/all/workspace/directory';
52137 return this.resource(endPoint, {}).get();
52139 APIEndPoint.prototype.getTags = function () {
52140 var endPoint = '/api/v1/tagList';
52141 return this.resource(endPoint, {}).get();
52143 APIEndPoint.prototype.getCommands = function () {
52144 var endPoint = '/api/v1/commandList';
52145 return this.resource(endPoint, {}).get();
52147 APIEndPoint.prototype.execute = function (data) {
52148 var endPoint = '/api/v1/execution';
52149 var fd = new FormData();
52150 fd.append('data', data);
52151 return this.$http.post(endPoint, fd, {
52152 headers: { 'Content-Type': undefined },
52153 transformRequest: angular.identity
52156 APIEndPoint.prototype.debug = function () {
52157 var endPoint = '/api/v1/debug';
52158 return this.$http.get(endPoint);
52160 APIEndPoint.prototype.upload = function () {
52161 var endPoint = '/api/v1/upload';
52162 return this.$http.get(endPoint);
52164 APIEndPoint.prototype.help = function (command) {
52165 var endPoint = '/api/v1/help/' + command;
52166 return this.$http.get(endPoint);
52168 return APIEndPoint;
52170 services.APIEndPoint = APIEndPoint;
52171 })(services = app.services || (app.services = {}));
52172 })(app || (app = {}));
52176 (function (services) {
52177 var MyModal = (function () {
52178 function MyModal($uibModal) {
52179 this.$uibModal = $uibModal;
52180 this.modalOption = {
52187 MyModal.prototype.open = function (modalName) {
52188 if (modalName === 'SelectCommand') {
52189 this.modalOption.templateUrl = 'templates/select-command.html';
52190 this.modalOption.size = 'lg';
52192 return this.$uibModal.open(this.modalOption);
52194 MyModal.prototype.selectCommand = function () {
52195 this.modalOption.templateUrl = 'templates/select-command.html';
52196 this.modalOption.controller = 'selectCommandController';
52197 this.modalOption.controllerAs = 'c';
52198 this.modalOption.size = 'lg';
52199 return this.$uibModal.open(this.modalOption);
52201 MyModal.prototype.preview = function () {
52202 this.modalOption.templateUrl = 'templates/preview.html';
52203 this.modalOption.controller = 'previewController';
52204 this.modalOption.controllerAs = 'c';
52205 this.modalOption.size = 'lg';
52206 return this.$uibModal.open(this.modalOption);
52208 MyModal.prototype.upload = function () {
52209 this.modalOption.templateUrl = 'templates/upload.html';
52210 this.modalOption.controller = 'uploadController';
52211 this.modalOption.controllerAs = 'c';
52212 this.modalOption.size = 'lg';
52213 return this.$uibModal.open(this.modalOption);
52215 MyModal.$inject = ['$uibModal'];
52218 services.MyModal = MyModal;
52219 })(services = app.services || (app.services = {}));
52220 })(app || (app = {}));
52224 (function (services) {
52225 var WebSocket = (function () {
52226 function WebSocket($rootScope) {
52227 this.$rootScope = $rootScope;
52228 this.socket = io.connect();
52230 WebSocket.prototype.on = function (eventName, callback) {
52231 var socket = this.socket;
52232 var rootScope = this.$rootScope;
52233 socket.on(eventName, function () {
52234 var args = arguments;
52235 rootScope.$apply(function () {
52236 callback.apply(socket, args);
52240 WebSocket.prototype.emit = function (eventName, data, callback) {
52241 var socket = this.socket;
52242 var rootScope = this.$rootScope;
52243 this.socket.emit(eventName, data, function () {
52244 var args = arguments;
52245 rootScope.$apply(function () {
52247 callback.apply(socket, args);
52253 services.WebSocket = WebSocket;
52254 })(services = app.services || (app.services = {}));
52255 })(app || (app = {}));
52259 (function (services) {
52260 var Console = (function () {
52261 function Console(WebSocket, $rootScope) {
52262 this.WebSocket = WebSocket;
52263 this.$rootScope = $rootScope;
52264 this.WebSocket = WebSocket;
52265 this.$rootScope = $rootScope;
52266 this.directiveIDs = [];
52267 var directiveIDs = this.directiveIDs;
52268 this.WebSocket.on('console', function (d) {
52270 var message = d.message;
52271 if (directiveIDs.indexOf(id) > -1) {
52272 $rootScope.$emit(id, message);
52276 Console.prototype.addDirective = function (id) {
52277 if (!(this.directiveIDs.indexOf(id) > -1)) {
52278 this.directiveIDs.push(id);
52281 Console.prototype.removeDirective = function (id) {
52282 var i = this.directiveIDs.indexOf(id);
52284 this.directiveIDs.splice(i, 1);
52287 Console.prototype.showIDs = function () {
52288 console.log(this.directiveIDs);
52292 services.Console = Console;
52293 })(services = app.services || (app.services = {}));
52294 })(app || (app = {}));
52298 (function (directives) {
52299 var Command = (function () {
52300 function Command() {
52301 this.restrict = 'E';
52302 this.replace = true;
52304 this.controller = 'commandController';
52305 this.controllerAs = 'ctrl';
52306 this.bindToController = {
52312 this.templateUrl = 'templates/command.html';
52314 Command.Factory = function () {
52315 var directive = function () {
52316 return new Command();
52318 directive.$inject = [];
52323 directives.Command = Command;
52324 var CommandController = (function () {
52325 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
52326 this.APIEndPoint = APIEndPoint;
52327 this.$scope = $scope;
52328 this.MyModal = MyModal;
52329 this.WebSocket = WebSocket;
52330 this.$window = $window;
52331 this.$rootScope = $rootScope;
52332 this.Console = Console;
52333 var controller = this;
52335 .getOptionControlFile(this.name)
52337 .then(function (result) {
52338 controller.options = result.info;
52343 .then(function (result) {
52344 controller.dirs = result.info;
52346 this.heading = "[" + this.index + "]: dcdFilePrint";
52347 this.isOpen = true;
52348 this.$scope.$on('close', function () {
52349 controller.isOpen = false;
52353 return Math.floor((1 + Math.random()) * 0x10000)
52357 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
52358 s4() + '-' + s4() + s4() + s4();
52360 this.uuid = guid();
52361 this.Console.addDirective(this.uuid);
52362 this.Console.showIDs();
52364 CommandController.prototype.submit = function () {
52366 angular.forEach(this.options, function (option) {
52368 name: option.option,
52371 angular.forEach(option.arg, function (arg) {
52373 if (typeof arg.input === 'object') {
52374 obj.arguments.push(arg.input.name);
52377 obj.arguments.push(arg.input);
52381 if (obj.arguments.length > 0) {
52386 command: this.name,
52387 workspace: this.workspace.fileId,
52391 .execute(JSON.stringify(execObj))
52392 .then(function (result) {
52393 console.log(result);
52396 CommandController.prototype.removeMySelf = function (index) {
52397 this.$scope.$destroy();
52398 this.Console.removeDirective(this.uuid);
52399 this.remove()(index, this.list);
52400 this.Console.showIDs();
52402 CommandController.prototype.reloadFiles = function () {
52404 var fileId = this.workspace.fileId;
52408 .then(function (result) {
52409 var status = result.status;
52410 if (status === 'success') {
52411 _this.files = result.info;
52414 console.log(result.message);
52418 CommandController.prototype.debug = function () {
52419 var div = angular.element(this.$window.document).find("div");
52422 angular.forEach(div, function (v) {
52423 if (v.className === "panel-body console") {
52426 else if (v.className === "row parameters-console") {
52430 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
52431 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
52432 consoleTag.style.height = consoleHeight;
52433 consoleTag.style.width = consoleWidth;
52435 CommandController.prototype.help = function () {
52438 .then(function (result) {
52439 console.log(result);
52442 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
52443 return CommandController;
52445 directives.CommandController = CommandController;
52446 })(directives = app.directives || (app.directives = {}));
52447 })(app || (app = {}));
52451 (function (directives) {
52452 var HeaderMenu = (function () {
52453 function HeaderMenu() {
52454 this.restrict = 'E';
52455 this.replace = true;
52456 this.templateUrl = 'templates/header-menu.html';
52457 this.controller = 'HeaderMenuController';
52458 this.controllerAs = 'hmc';
52461 HeaderMenu.Factory = function () {
52462 var directive = function () {
52463 return new HeaderMenu();
52469 directives.HeaderMenu = HeaderMenu;
52470 var HeaderMenuController = (function () {
52471 function HeaderMenuController($state) {
52472 this.$state = $state;
52473 this.isExecution = this.$state.current.name === 'execution';
52474 this.isWorkspace = this.$state.current.name === 'workspace';
52475 this.isHistory = this.$state.current.name === 'history';
52477 HeaderMenuController.prototype.transit = function (state) {
52478 this.$state.go(state);
52480 HeaderMenuController.$inject = ['$state'];
52481 return HeaderMenuController;
52483 directives.HeaderMenuController = HeaderMenuController;
52484 })(directives = app.directives || (app.directives = {}));
52485 })(app || (app = {}));
52489 (function (directives) {
52490 var Option = (function () {
52491 function Option() {
52492 this.restrict = 'E';
52493 this.replace = true;
52494 this.controller = 'optionController';
52495 this.bindToController = {
52500 this.templateUrl = 'templates/option.html';
52501 this.controllerAs = 'ctrl';
52503 Option.Factory = function () {
52504 var directive = function () {
52505 return new Option();
52507 directive.$inject = [];
52512 directives.Option = Option;
52513 var OptionController = (function () {
52514 function OptionController() {
52515 var controller = this;
52516 angular.forEach(controller.info.arg, function (arg) {
52517 if (arg.initialValue) {
52518 if (arg.formType === 'number') {
52519 arg.input = parseInt(arg.initialValue);
52522 arg.input = arg.initialValue;
52527 OptionController.$inject = [];
52528 return OptionController;
52530 directives.OptionController = OptionController;
52531 })(directives = app.directives || (app.directives = {}));
52532 })(app || (app = {}));
52536 (function (directives) {
52537 var Directory = (function () {
52538 function Directory() {
52539 this.restrict = 'E';
52540 this.replace = true;
52541 this.controller = 'directoryController';
52542 this.controllerAs = 'ctrl';
52543 this.bindToController = {
52549 this.templateUrl = 'templates/directory.html';
52551 Directory.Factory = function () {
52552 var directive = function () {
52553 return new Directory();
52559 directives.Directory = Directory;
52560 var DirectoryController = (function () {
52561 function DirectoryController(APIEndPoint, $scope) {
52562 this.APIEndPoint = APIEndPoint;
52563 this.$scope = $scope;
52564 var controller = this;
52566 .getFiles(this.info.fileId)
52568 .then(function (result) {
52569 if (result.status === 'success') {
52570 controller.files = result.info;
52571 angular.forEach(result.info, function (file) {
52572 if (file.fileType === '0') {
52574 if (controller.info.path === '/') {
52575 o.path = '/' + file.name;
52578 o.path = controller.info.path + '/' + file.name;
52580 controller.add()(o, controller.list);
52587 DirectoryController.$inject = ['APIEndPoint', '$scope'];
52588 return DirectoryController;
52590 directives.DirectoryController = DirectoryController;
52591 })(directives = app.directives || (app.directives = {}));
52592 })(app || (app = {}));
52596 (function (directives) {
52597 var Upload = (function () {
52598 function Upload() {
52599 this.restrict = 'E';
52600 this.replace = true;
52602 this.controller = 'UploadController';
52603 this.controllerAs = 'ctrl';
52604 this.bindToController = {
52610 this.templateUrl = 'templates/upload.html';
52611 console.log("templates/upload.html-constructor");
52613 Upload.Factory = function () {
52614 var directive = function () {
52615 return new Upload();
52617 directive.$inject = [];
52622 directives.Upload = Upload;
52623 var UploadController = (function () {
52624 function UploadController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
52625 this.APIEndPoint = APIEndPoint;
52626 this.$scope = $scope;
52627 this.MyModal = MyModal;
52628 this.WebSocket = WebSocket;
52629 this.$window = $window;
52630 this.$rootScope = $rootScope;
52631 this.Console = Console;
52632 var controller = this;
52633 console.log("directive.upload-constructor");
52635 UploadController.prototype.submit = function () {
52636 console.log("submit: function not supported¥n");
52638 UploadController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
52639 return UploadController;
52641 directives.UploadController = UploadController;
52642 })(directives = app.directives || (app.directives = {}));
52643 })(app || (app = {}));
52647 (function (controllers) {
52648 var Execution = (function () {
52649 function Execution(MyModal, $scope) {
52650 this.MyModal = MyModal;
52651 this.$scope = $scope;
52652 this.commandInfoList = [];
52655 Execution.prototype.add = function () {
52656 this.$scope.$broadcast('close');
52657 var commandInfoList = this.commandInfoList;
52658 var commandInstance = this.MyModal.selectCommand();
52661 .then(function (command) {
52662 commandInfoList.push(new app.declares.CommandInfo(command));
52665 Execution.prototype.open = function () {
52666 var result = this.MyModal.open('SelectCommand');
52667 console.log(result);
52669 Execution.prototype.remove = function (index, list) {
52670 list.splice(index, 1);
52672 Execution.prototype.close = function () {
52673 console.log("close");
52675 Execution.$inject = ['MyModal', '$scope'];
52678 controllers.Execution = Execution;
52679 })(controllers = app.controllers || (app.controllers = {}));
52680 })(app || (app = {}));
52684 (function (controllers) {
52685 var Workspace = (function () {
52686 function Workspace($scope, APIEndPoint, MyModal) {
52687 this.$scope = $scope;
52688 this.APIEndPoint = APIEndPoint;
52689 this.MyModal = MyModal;
52690 this.directoryList = [];
52691 var controller = this;
52692 var directoryList = this.directoryList;
52694 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
52702 directoryList.push(o);
52704 Workspace.prototype.addDirectory = function (info, directoryList) {
52705 directoryList.push(info);
52707 Workspace.prototype.upload = function () {
52708 this.MyModal.upload();
52710 Workspace.prototype.debug = function () {
52711 this.MyModal.preview();
52713 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
52716 controllers.Workspace = Workspace;
52717 })(controllers = app.controllers || (app.controllers = {}));
52718 })(app || (app = {}));
52722 (function (controllers) {
52723 var History = (function () {
52724 function History($scope) {
52725 this.page = "History";
52727 History.$inject = ['$scope'];
52730 controllers.History = History;
52731 })(controllers = app.controllers || (app.controllers = {}));
52732 })(app || (app = {}));
52736 (function (controllers) {
52737 var SelectCommand = (function () {
52738 function SelectCommand($scope, APIEndPoint, $modalInstance) {
52739 this.APIEndPoint = APIEndPoint;
52740 this.$modalInstance = $modalInstance;
52741 var controller = this;
52744 .$promise.then(function (result) {
52745 controller.tags = result.info;
52749 .$promise.then(function (result) {
52750 controller.commands = result.info;
52752 this.currentTag = 'all';
52754 SelectCommand.prototype.changeTag = function (tag) {
52755 this.currentTag = tag;
52757 SelectCommand.prototype.selectCommand = function (command) {
52758 this.$modalInstance.close(command);
52760 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
52761 return SelectCommand;
52763 controllers.SelectCommand = SelectCommand;
52764 })(controllers = app.controllers || (app.controllers = {}));
52765 })(app || (app = {}));
52769 (function (controllers) {
52770 var Upload = (function () {
52771 function Upload($scope, APIEndPoint, $modalInstance) {
52772 this.APIEndPoint = APIEndPoint;
52773 this.$modalInstance = $modalInstance;
52774 var controller = this;
52775 console.log('controller.upload-controllers');
52777 Upload.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
52780 controllers.Upload = Upload;
52781 })(controllers = app.controllers || (app.controllers = {}));
52782 })(app || (app = {}));
52786 (function (controllers) {
52787 var Preview = (function () {
52788 function Preview($scope, APIEndPoint, $modalInstance) {
52789 this.APIEndPoint = APIEndPoint;
52790 this.$modalInstance = $modalInstance;
52791 var controller = this;
52792 console.log('preview');
52794 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
52797 controllers.Preview = Preview;
52798 })(controllers = app.controllers || (app.controllers = {}));
52799 })(app || (app = {}));
52801 (function (filters) {
52803 return function (commands, tag) {
52805 angular.forEach(commands, function (command) {
52807 angular.forEach(command.tags, function (value) {
52812 result.push(command);
52818 })(filters || (filters = {}));
52822 var appName = 'zephyr';
52823 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
52824 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
52825 $urlRouterProvider.otherwise('/execution');
52826 $locationProvider.html5Mode({
52831 .state('execution', {
52833 templateUrl: 'templates/execution.html',
52834 controller: 'executionController',
52837 .state('workspace', {
52839 templateUrl: 'templates/workspace.html',
52840 controller: 'workspaceController',
52843 .state('history', {
52845 templateUrl: 'templates/history.html',
52846 controller: 'historyController',
52850 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
52851 app.zephyr.service('MyModal', app.services.MyModal);
52852 app.zephyr.service('WebSocket', app.services.WebSocket);
52853 app.zephyr.service('Console', app.services.Console);
52854 app.zephyr.filter('Tag', filters.Tag);
52855 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
52856 app.zephyr.controller('previewController', app.controllers.Preview);
52857 app.zephyr.controller('uploadController', app.controllers.Upload);
52858 app.zephyr.controller('executionController', app.controllers.Execution);
52859 app.zephyr.controller('workspaceController', app.controllers.Workspace);
52860 app.zephyr.controller('historyController', app.controllers.History);
52861 app.zephyr.controller('commandController', app.directives.CommandController);
52862 app.zephyr.controller('optionController', app.directives.OptionController);
52863 app.zephyr.controller('directoryController', app.directives.DirectoryController);
52864 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
52865 app.zephyr.controller('uploadController', app.directives.UploadController);
52866 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
52867 app.zephyr.directive('command', app.directives.Command.Factory());
52868 app.zephyr.directive('option', app.directives.Option.Factory());
52869 app.zephyr.directive('directory', app.directives.Directory.Factory());
52870 })(app || (app = {}));
52875 /***/ function(module, exports) {
52880 (function (declares) {
52881 var CommandInfo = (function () {
52882 function CommandInfo(name) {
52885 return CommandInfo;
52887 declares.CommandInfo = CommandInfo;
52888 })(declares = app.declares || (app.declares = {}));
52889 })(app || (app = {}));
52893 (function (services) {
52894 var APIEndPoint = (function () {
52895 function APIEndPoint($resource, $http) {
52896 this.$resource = $resource;
52897 this.$http = $http;
52899 APIEndPoint.prototype.resource = function (endPoint, data) {
52900 var customAction = {
52906 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
52908 return this.$resource(endPoint, {}, { execute: execute });
52910 APIEndPoint.prototype.getOptionControlFile = function (command) {
52911 var endPoint = '/api/v1/optionControlFile/' + command;
52912 return this.resource(endPoint, {}).get();
52914 APIEndPoint.prototype.getFiles = function (fileId) {
52915 var endPoint = '/api/v1/workspace';
52917 endPoint += '/' + fileId;
52919 return this.resource(endPoint, {}).get();
52921 APIEndPoint.prototype.getDirectories = function () {
52922 var endPoint = '/api/v1/all/workspace/directory';
52923 return this.resource(endPoint, {}).get();
52925 APIEndPoint.prototype.getTags = function () {
52926 var endPoint = '/api/v1/tagList';
52927 return this.resource(endPoint, {}).get();
52929 APIEndPoint.prototype.getCommands = function () {
52930 var endPoint = '/api/v1/commandList';
52931 return this.resource(endPoint, {}).get();
52933 APIEndPoint.prototype.execute = function (data) {
52934 var endPoint = '/api/v1/execution';
52935 var fd = new FormData();
52936 fd.append('data', data);
52937 return this.$http.post(endPoint, fd, {
52938 headers: { 'Content-Type': undefined },
52939 transformRequest: angular.identity
52942 APIEndPoint.prototype.debug = function () {
52943 var endPoint = '/api/v1/debug';
52944 return this.$http.get(endPoint);
52946 APIEndPoint.prototype.upload = function () {
52947 var endPoint = '/api/v1/upload';
52948 return this.$http.get(endPoint);
52950 APIEndPoint.prototype.help = function (command) {
52951 var endPoint = '/api/v1/help/' + command;
52952 return this.$http.get(endPoint);
52954 return APIEndPoint;
52956 services.APIEndPoint = APIEndPoint;
52957 })(services = app.services || (app.services = {}));
52958 })(app || (app = {}));
52962 (function (services) {
52963 var MyModal = (function () {
52964 function MyModal($uibModal) {
52965 this.$uibModal = $uibModal;
52966 this.modalOption = {
52973 MyModal.prototype.open = function (modalName) {
52974 if (modalName === 'SelectCommand') {
52975 this.modalOption.templateUrl = 'templates/select-command.html';
52976 this.modalOption.size = 'lg';
52978 return this.$uibModal.open(this.modalOption);
52980 MyModal.prototype.selectCommand = function () {
52981 this.modalOption.templateUrl = 'templates/select-command.html';
52982 this.modalOption.controller = 'selectCommandController';
52983 this.modalOption.controllerAs = 'c';
52984 this.modalOption.size = 'lg';
52985 return this.$uibModal.open(this.modalOption);
52987 MyModal.prototype.preview = function () {
52988 this.modalOption.templateUrl = 'templates/preview.html';
52989 this.modalOption.controller = 'previewController';
52990 this.modalOption.controllerAs = 'c';
52991 this.modalOption.size = 'lg';
52992 return this.$uibModal.open(this.modalOption);
52994 MyModal.prototype.upload = function () {
52995 this.modalOption.templateUrl = 'templates/upload.html';
52996 this.modalOption.controller = 'uploadController';
52997 this.modalOption.controllerAs = 'c';
52998 this.modalOption.size = 'lg';
52999 return this.$uibModal.open(this.modalOption);
53001 MyModal.$inject = ['$uibModal'];
53004 services.MyModal = MyModal;
53005 })(services = app.services || (app.services = {}));
53006 })(app || (app = {}));
53010 (function (services) {
53011 var WebSocket = (function () {
53012 function WebSocket($rootScope) {
53013 this.$rootScope = $rootScope;
53014 this.socket = io.connect();
53016 WebSocket.prototype.on = function (eventName, callback) {
53017 var socket = this.socket;
53018 var rootScope = this.$rootScope;
53019 socket.on(eventName, function () {
53020 var args = arguments;
53021 rootScope.$apply(function () {
53022 callback.apply(socket, args);
53026 WebSocket.prototype.emit = function (eventName, data, callback) {
53027 var socket = this.socket;
53028 var rootScope = this.$rootScope;
53029 this.socket.emit(eventName, data, function () {
53030 var args = arguments;
53031 rootScope.$apply(function () {
53033 callback.apply(socket, args);
53039 services.WebSocket = WebSocket;
53040 })(services = app.services || (app.services = {}));
53041 })(app || (app = {}));
53045 (function (services) {
53046 var Console = (function () {
53047 function Console(WebSocket, $rootScope) {
53048 this.WebSocket = WebSocket;
53049 this.$rootScope = $rootScope;
53050 this.WebSocket = WebSocket;
53051 this.$rootScope = $rootScope;
53052 this.directiveIDs = [];
53053 var directiveIDs = this.directiveIDs;
53054 this.WebSocket.on('console', function (d) {
53056 var message = d.message;
53057 if (directiveIDs.indexOf(id) > -1) {
53058 $rootScope.$emit(id, message);
53062 Console.prototype.addDirective = function (id) {
53063 if (!(this.directiveIDs.indexOf(id) > -1)) {
53064 this.directiveIDs.push(id);
53067 Console.prototype.removeDirective = function (id) {
53068 var i = this.directiveIDs.indexOf(id);
53070 this.directiveIDs.splice(i, 1);
53073 Console.prototype.showIDs = function () {
53074 console.log(this.directiveIDs);
53078 services.Console = Console;
53079 })(services = app.services || (app.services = {}));
53080 })(app || (app = {}));
53084 (function (directives) {
53085 var Command = (function () {
53086 function Command() {
53087 this.restrict = 'E';
53088 this.replace = true;
53090 this.controller = 'commandController';
53091 this.controllerAs = 'ctrl';
53092 this.bindToController = {
53098 this.templateUrl = 'templates/command.html';
53100 Command.Factory = function () {
53101 var directive = function () {
53102 return new Command();
53104 directive.$inject = [];
53109 directives.Command = Command;
53110 var CommandController = (function () {
53111 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
53112 this.APIEndPoint = APIEndPoint;
53113 this.$scope = $scope;
53114 this.MyModal = MyModal;
53115 this.WebSocket = WebSocket;
53116 this.$window = $window;
53117 this.$rootScope = $rootScope;
53118 this.Console = Console;
53119 var controller = this;
53121 .getOptionControlFile(this.name)
53123 .then(function (result) {
53124 controller.options = result.info;
53129 .then(function (result) {
53130 controller.dirs = result.info;
53132 this.heading = "[" + this.index + "]: dcdFilePrint";
53133 this.isOpen = true;
53134 this.$scope.$on('close', function () {
53135 controller.isOpen = false;
53139 return Math.floor((1 + Math.random()) * 0x10000)
53143 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
53144 s4() + '-' + s4() + s4() + s4();
53146 this.uuid = guid();
53147 this.Console.addDirective(this.uuid);
53148 this.Console.showIDs();
53150 CommandController.prototype.submit = function () {
53152 angular.forEach(this.options, function (option) {
53154 name: option.option,
53157 angular.forEach(option.arg, function (arg) {
53159 if (typeof arg.input === 'object') {
53160 obj.arguments.push(arg.input.name);
53163 obj.arguments.push(arg.input);
53167 if (obj.arguments.length > 0) {
53172 command: this.name,
53173 workspace: this.workspace.fileId,
53177 .execute(JSON.stringify(execObj))
53178 .then(function (result) {
53179 console.log(result);
53182 CommandController.prototype.removeMySelf = function (index) {
53183 this.$scope.$destroy();
53184 this.Console.removeDirective(this.uuid);
53185 this.remove()(index, this.list);
53186 this.Console.showIDs();
53188 CommandController.prototype.reloadFiles = function () {
53190 var fileId = this.workspace.fileId;
53194 .then(function (result) {
53195 var status = result.status;
53196 if (status === 'success') {
53197 _this.files = result.info;
53200 console.log(result.message);
53204 CommandController.prototype.debug = function () {
53205 var div = angular.element(this.$window.document).find("div");
53208 angular.forEach(div, function (v) {
53209 if (v.className === "panel-body console") {
53212 else if (v.className === "row parameters-console") {
53216 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
53217 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
53218 consoleTag.style.height = consoleHeight;
53219 consoleTag.style.width = consoleWidth;
53221 CommandController.prototype.help = function () {
53224 .then(function (result) {
53225 console.log(result);
53228 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
53229 return CommandController;
53231 directives.CommandController = CommandController;
53232 })(directives = app.directives || (app.directives = {}));
53233 })(app || (app = {}));
53237 (function (directives) {
53238 var HeaderMenu = (function () {
53239 function HeaderMenu() {
53240 this.restrict = 'E';
53241 this.replace = true;
53242 this.templateUrl = 'templates/header-menu.html';
53243 this.controller = 'HeaderMenuController';
53244 this.controllerAs = 'hmc';
53247 HeaderMenu.Factory = function () {
53248 var directive = function () {
53249 return new HeaderMenu();
53255 directives.HeaderMenu = HeaderMenu;
53256 var HeaderMenuController = (function () {
53257 function HeaderMenuController($state) {
53258 this.$state = $state;
53259 this.isExecution = this.$state.current.name === 'execution';
53260 this.isWorkspace = this.$state.current.name === 'workspace';
53261 this.isHistory = this.$state.current.name === 'history';
53263 HeaderMenuController.prototype.transit = function (state) {
53264 this.$state.go(state);
53266 HeaderMenuController.$inject = ['$state'];
53267 return HeaderMenuController;
53269 directives.HeaderMenuController = HeaderMenuController;
53270 })(directives = app.directives || (app.directives = {}));
53271 })(app || (app = {}));
53275 (function (directives) {
53276 var Option = (function () {
53277 function Option() {
53278 this.restrict = 'E';
53279 this.replace = true;
53280 this.controller = 'optionController';
53281 this.bindToController = {
53286 this.templateUrl = 'templates/option.html';
53287 this.controllerAs = 'ctrl';
53289 Option.Factory = function () {
53290 var directive = function () {
53291 return new Option();
53293 directive.$inject = [];
53298 directives.Option = Option;
53299 var OptionController = (function () {
53300 function OptionController() {
53301 var controller = this;
53302 angular.forEach(controller.info.arg, function (arg) {
53303 if (arg.initialValue) {
53304 if (arg.formType === 'number') {
53305 arg.input = parseInt(arg.initialValue);
53308 arg.input = arg.initialValue;
53313 OptionController.$inject = [];
53314 return OptionController;
53316 directives.OptionController = OptionController;
53317 })(directives = app.directives || (app.directives = {}));
53318 })(app || (app = {}));
53322 (function (directives) {
53323 var Directory = (function () {
53324 function Directory() {
53325 this.restrict = 'E';
53326 this.replace = true;
53327 this.controller = 'directoryController';
53328 this.controllerAs = 'ctrl';
53329 this.bindToController = {
53335 this.templateUrl = 'templates/directory.html';
53337 Directory.Factory = function () {
53338 var directive = function () {
53339 return new Directory();
53345 directives.Directory = Directory;
53346 var DirectoryController = (function () {
53347 function DirectoryController(APIEndPoint, $scope) {
53348 this.APIEndPoint = APIEndPoint;
53349 this.$scope = $scope;
53350 var controller = this;
53352 .getFiles(this.info.fileId)
53354 .then(function (result) {
53355 if (result.status === 'success') {
53356 controller.files = result.info;
53357 angular.forEach(result.info, function (file) {
53358 if (file.fileType === '0') {
53360 if (controller.info.path === '/') {
53361 o.path = '/' + file.name;
53364 o.path = controller.info.path + '/' + file.name;
53366 controller.add()(o, controller.list);
53373 DirectoryController.$inject = ['APIEndPoint', '$scope'];
53374 return DirectoryController;
53376 directives.DirectoryController = DirectoryController;
53377 })(directives = app.directives || (app.directives = {}));
53378 })(app || (app = {}));
53382 (function (directives) {
53383 var Upload = (function () {
53384 function Upload() {
53385 this.restrict = 'E';
53386 this.replace = true;
53388 this.controller = 'UploadController';
53389 this.controllerAs = 'ctrl';
53390 this.bindToController = {
53396 this.templateUrl = 'templates/upload.html';
53397 console.log("templates/upload.html-constructor");
53399 Upload.Factory = function () {
53400 var directive = function () {
53401 return new Upload();
53403 directive.$inject = [];
53408 directives.Upload = Upload;
53409 var UploadController = (function () {
53410 function UploadController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
53411 this.APIEndPoint = APIEndPoint;
53412 this.$scope = $scope;
53413 this.MyModal = MyModal;
53414 this.WebSocket = WebSocket;
53415 this.$window = $window;
53416 this.$rootScope = $rootScope;
53417 this.Console = Console;
53418 var controller = this;
53419 console.log("directive.upload-constructor");
53421 UploadController.prototype.submit = function () {
53422 console.log("submit: function not supported¥n");
53424 UploadController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
53425 return UploadController;
53427 directives.UploadController = UploadController;
53428 })(directives = app.directives || (app.directives = {}));
53429 })(app || (app = {}));
53433 (function (controllers) {
53434 var Execution = (function () {
53435 function Execution(MyModal, $scope) {
53436 this.MyModal = MyModal;
53437 this.$scope = $scope;
53438 this.commandInfoList = [];
53441 Execution.prototype.add = function () {
53442 this.$scope.$broadcast('close');
53443 var commandInfoList = this.commandInfoList;
53444 var commandInstance = this.MyModal.selectCommand();
53447 .then(function (command) {
53448 commandInfoList.push(new app.declares.CommandInfo(command));
53451 Execution.prototype.open = function () {
53452 var result = this.MyModal.open('SelectCommand');
53453 console.log(result);
53455 Execution.prototype.remove = function (index, list) {
53456 list.splice(index, 1);
53458 Execution.prototype.close = function () {
53459 console.log("close");
53461 Execution.$inject = ['MyModal', '$scope'];
53464 controllers.Execution = Execution;
53465 })(controllers = app.controllers || (app.controllers = {}));
53466 })(app || (app = {}));
53470 (function (controllers) {
53471 var Workspace = (function () {
53472 function Workspace($scope, APIEndPoint, MyModal) {
53473 this.$scope = $scope;
53474 this.APIEndPoint = APIEndPoint;
53475 this.MyModal = MyModal;
53476 this.directoryList = [];
53477 var controller = this;
53478 var directoryList = this.directoryList;
53480 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
53488 directoryList.push(o);
53490 Workspace.prototype.addDirectory = function (info, directoryList) {
53491 directoryList.push(info);
53493 Workspace.prototype.upload = function () {
53494 this.MyModal.upload();
53496 Workspace.prototype.debug = function () {
53497 this.MyModal.preview();
53499 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
53502 controllers.Workspace = Workspace;
53503 })(controllers = app.controllers || (app.controllers = {}));
53504 })(app || (app = {}));
53508 (function (controllers) {
53509 var History = (function () {
53510 function History($scope) {
53511 this.page = "History";
53513 History.$inject = ['$scope'];
53516 controllers.History = History;
53517 })(controllers = app.controllers || (app.controllers = {}));
53518 })(app || (app = {}));
53522 (function (controllers) {
53523 var SelectCommand = (function () {
53524 function SelectCommand($scope, APIEndPoint, $modalInstance) {
53525 this.APIEndPoint = APIEndPoint;
53526 this.$modalInstance = $modalInstance;
53527 var controller = this;
53530 .$promise.then(function (result) {
53531 controller.tags = result.info;
53535 .$promise.then(function (result) {
53536 controller.commands = result.info;
53538 this.currentTag = 'all';
53540 SelectCommand.prototype.changeTag = function (tag) {
53541 this.currentTag = tag;
53543 SelectCommand.prototype.selectCommand = function (command) {
53544 this.$modalInstance.close(command);
53546 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
53547 return SelectCommand;
53549 controllers.SelectCommand = SelectCommand;
53550 })(controllers = app.controllers || (app.controllers = {}));
53551 })(app || (app = {}));
53555 (function (controllers) {
53556 var Upload = (function () {
53557 function Upload($scope, APIEndPoint, $modalInstance) {
53558 this.APIEndPoint = APIEndPoint;
53559 this.$modalInstance = $modalInstance;
53560 var controller = this;
53561 console.log('controller.upload-controllers');
53563 Upload.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
53566 controllers.Upload = Upload;
53567 })(controllers = app.controllers || (app.controllers = {}));
53568 })(app || (app = {}));
53572 (function (controllers) {
53573 var Preview = (function () {
53574 function Preview($scope, APIEndPoint, $modalInstance) {
53575 this.APIEndPoint = APIEndPoint;
53576 this.$modalInstance = $modalInstance;
53577 var controller = this;
53578 console.log('preview');
53580 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
53583 controllers.Preview = Preview;
53584 })(controllers = app.controllers || (app.controllers = {}));
53585 })(app || (app = {}));
53587 (function (filters) {
53589 return function (commands, tag) {
53591 angular.forEach(commands, function (command) {
53593 angular.forEach(command.tags, function (value) {
53598 result.push(command);
53604 })(filters || (filters = {}));
53608 var appName = 'zephyr';
53609 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
53610 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
53611 $urlRouterProvider.otherwise('/execution');
53612 $locationProvider.html5Mode({
53617 .state('execution', {
53619 templateUrl: 'templates/execution.html',
53620 controller: 'executionController',
53623 .state('workspace', {
53625 templateUrl: 'templates/workspace.html',
53626 controller: 'workspaceController',
53629 .state('history', {
53631 templateUrl: 'templates/history.html',
53632 controller: 'historyController',
53636 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
53637 app.zephyr.service('MyModal', app.services.MyModal);
53638 app.zephyr.service('WebSocket', app.services.WebSocket);
53639 app.zephyr.service('Console', app.services.Console);
53640 app.zephyr.filter('Tag', filters.Tag);
53641 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
53642 app.zephyr.controller('previewController', app.controllers.Preview);
53643 app.zephyr.controller('uploadController', app.controllers.Upload);
53644 app.zephyr.controller('executionController', app.controllers.Execution);
53645 app.zephyr.controller('workspaceController', app.controllers.Workspace);
53646 app.zephyr.controller('historyController', app.controllers.History);
53647 app.zephyr.controller('commandController', app.directives.CommandController);
53648 app.zephyr.controller('optionController', app.directives.OptionController);
53649 app.zephyr.controller('directoryController', app.directives.DirectoryController);
53650 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
53651 app.zephyr.controller('uploadController', app.directives.UploadController);
53652 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
53653 app.zephyr.directive('command', app.directives.Command.Factory());
53654 app.zephyr.directive('option', app.directives.Option.Factory());
53655 app.zephyr.directive('directory', app.directives.Directory.Factory());
53656 })(app || (app = {}));
53661 /***/ function(module, exports) {
53666 (function (declares) {
53667 var CommandInfo = (function () {
53668 function CommandInfo(name) {
53671 return CommandInfo;
53673 declares.CommandInfo = CommandInfo;
53674 })(declares = app.declares || (app.declares = {}));
53675 })(app || (app = {}));
53679 (function (services) {
53680 var APIEndPoint = (function () {
53681 function APIEndPoint($resource, $http) {
53682 this.$resource = $resource;
53683 this.$http = $http;
53685 APIEndPoint.prototype.resource = function (endPoint, data) {
53686 var customAction = {
53692 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
53694 return this.$resource(endPoint, {}, { execute: execute });
53696 APIEndPoint.prototype.getOptionControlFile = function (command) {
53697 var endPoint = '/api/v1/optionControlFile/' + command;
53698 return this.resource(endPoint, {}).get();
53700 APIEndPoint.prototype.getFiles = function (fileId) {
53701 var endPoint = '/api/v1/workspace';
53703 endPoint += '/' + fileId;
53705 return this.resource(endPoint, {}).get();
53707 APIEndPoint.prototype.getDirectories = function () {
53708 var endPoint = '/api/v1/all/workspace/directory';
53709 return this.resource(endPoint, {}).get();
53711 APIEndPoint.prototype.getTags = function () {
53712 var endPoint = '/api/v1/tagList';
53713 return this.resource(endPoint, {}).get();
53715 APIEndPoint.prototype.getCommands = function () {
53716 var endPoint = '/api/v1/commandList';
53717 return this.resource(endPoint, {}).get();
53719 APIEndPoint.prototype.execute = function (data) {
53720 var endPoint = '/api/v1/execution';
53721 var fd = new FormData();
53722 fd.append('data', data);
53723 return this.$http.post(endPoint, fd, {
53724 headers: { 'Content-Type': undefined },
53725 transformRequest: angular.identity
53728 APIEndPoint.prototype.debug = function () {
53729 var endPoint = '/api/v1/debug';
53730 return this.$http.get(endPoint);
53732 APIEndPoint.prototype.upload = function () {
53733 var endPoint = '/api/v1/upload';
53734 return this.$http.get(endPoint);
53736 APIEndPoint.prototype.help = function (command) {
53737 var endPoint = '/api/v1/help/' + command;
53738 return this.$http.get(endPoint);
53740 return APIEndPoint;
53742 services.APIEndPoint = APIEndPoint;
53743 })(services = app.services || (app.services = {}));
53744 })(app || (app = {}));
53748 (function (services) {
53749 var MyModal = (function () {
53750 function MyModal($uibModal) {
53751 this.$uibModal = $uibModal;
53752 this.modalOption = {
53759 MyModal.prototype.open = function (modalName) {
53760 if (modalName === 'SelectCommand') {
53761 this.modalOption.templateUrl = 'templates/select-command.html';
53762 this.modalOption.size = 'lg';
53764 return this.$uibModal.open(this.modalOption);
53766 MyModal.prototype.selectCommand = function () {
53767 this.modalOption.templateUrl = 'templates/select-command.html';
53768 this.modalOption.controller = 'selectCommandController';
53769 this.modalOption.controllerAs = 'c';
53770 this.modalOption.size = 'lg';
53771 return this.$uibModal.open(this.modalOption);
53773 MyModal.prototype.preview = function () {
53774 this.modalOption.templateUrl = 'templates/preview.html';
53775 this.modalOption.controller = 'previewController';
53776 this.modalOption.controllerAs = 'c';
53777 this.modalOption.size = 'lg';
53778 return this.$uibModal.open(this.modalOption);
53780 MyModal.prototype.upload = function () {
53781 this.modalOption.templateUrl = 'templates/upload.html';
53782 this.modalOption.controller = 'uploadController';
53783 this.modalOption.controllerAs = 'c';
53784 this.modalOption.size = 'lg';
53785 return this.$uibModal.open(this.modalOption);
53787 MyModal.$inject = ['$uibModal'];
53790 services.MyModal = MyModal;
53791 })(services = app.services || (app.services = {}));
53792 })(app || (app = {}));
53796 (function (services) {
53797 var WebSocket = (function () {
53798 function WebSocket($rootScope) {
53799 this.$rootScope = $rootScope;
53800 this.socket = io.connect();
53802 WebSocket.prototype.on = function (eventName, callback) {
53803 var socket = this.socket;
53804 var rootScope = this.$rootScope;
53805 socket.on(eventName, function () {
53806 var args = arguments;
53807 rootScope.$apply(function () {
53808 callback.apply(socket, args);
53812 WebSocket.prototype.emit = function (eventName, data, callback) {
53813 var socket = this.socket;
53814 var rootScope = this.$rootScope;
53815 this.socket.emit(eventName, data, function () {
53816 var args = arguments;
53817 rootScope.$apply(function () {
53819 callback.apply(socket, args);
53825 services.WebSocket = WebSocket;
53826 })(services = app.services || (app.services = {}));
53827 })(app || (app = {}));
53831 (function (services) {
53832 var Console = (function () {
53833 function Console(WebSocket, $rootScope) {
53834 this.WebSocket = WebSocket;
53835 this.$rootScope = $rootScope;
53836 this.WebSocket = WebSocket;
53837 this.$rootScope = $rootScope;
53838 this.directiveIDs = [];
53839 var directiveIDs = this.directiveIDs;
53840 this.WebSocket.on('console', function (d) {
53842 var message = d.message;
53843 if (directiveIDs.indexOf(id) > -1) {
53844 $rootScope.$emit(id, message);
53848 Console.prototype.addDirective = function (id) {
53849 if (!(this.directiveIDs.indexOf(id) > -1)) {
53850 this.directiveIDs.push(id);
53853 Console.prototype.removeDirective = function (id) {
53854 var i = this.directiveIDs.indexOf(id);
53856 this.directiveIDs.splice(i, 1);
53859 Console.prototype.showIDs = function () {
53860 console.log(this.directiveIDs);
53864 services.Console = Console;
53865 })(services = app.services || (app.services = {}));
53866 })(app || (app = {}));
53870 (function (directives) {
53871 var Command = (function () {
53872 function Command() {
53873 this.restrict = 'E';
53874 this.replace = true;
53876 this.controller = 'commandController';
53877 this.controllerAs = 'ctrl';
53878 this.bindToController = {
53884 this.templateUrl = 'templates/command.html';
53886 Command.Factory = function () {
53887 var directive = function () {
53888 return new Command();
53890 directive.$inject = [];
53895 directives.Command = Command;
53896 var CommandController = (function () {
53897 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
53898 this.APIEndPoint = APIEndPoint;
53899 this.$scope = $scope;
53900 this.MyModal = MyModal;
53901 this.WebSocket = WebSocket;
53902 this.$window = $window;
53903 this.$rootScope = $rootScope;
53904 this.Console = Console;
53905 var controller = this;
53907 .getOptionControlFile(this.name)
53909 .then(function (result) {
53910 controller.options = result.info;
53915 .then(function (result) {
53916 controller.dirs = result.info;
53918 this.heading = "[" + this.index + "]: dcdFilePrint";
53919 this.isOpen = true;
53920 this.$scope.$on('close', function () {
53921 controller.isOpen = false;
53925 return Math.floor((1 + Math.random()) * 0x10000)
53929 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
53930 s4() + '-' + s4() + s4() + s4();
53932 this.uuid = guid();
53933 this.Console.addDirective(this.uuid);
53934 this.Console.showIDs();
53936 CommandController.prototype.submit = function () {
53938 angular.forEach(this.options, function (option) {
53940 name: option.option,
53943 angular.forEach(option.arg, function (arg) {
53945 if (typeof arg.input === 'object') {
53946 obj.arguments.push(arg.input.name);
53949 obj.arguments.push(arg.input);
53953 if (obj.arguments.length > 0) {
53958 command: this.name,
53959 workspace: this.workspace.fileId,
53963 .execute(JSON.stringify(execObj))
53964 .then(function (result) {
53965 console.log(result);
53968 CommandController.prototype.removeMySelf = function (index) {
53969 this.$scope.$destroy();
53970 this.Console.removeDirective(this.uuid);
53971 this.remove()(index, this.list);
53972 this.Console.showIDs();
53974 CommandController.prototype.reloadFiles = function () {
53976 var fileId = this.workspace.fileId;
53980 .then(function (result) {
53981 var status = result.status;
53982 if (status === 'success') {
53983 _this.files = result.info;
53986 console.log(result.message);
53990 CommandController.prototype.debug = function () {
53991 var div = angular.element(this.$window.document).find("div");
53994 angular.forEach(div, function (v) {
53995 if (v.className === "panel-body console") {
53998 else if (v.className === "row parameters-console") {
54002 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
54003 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
54004 consoleTag.style.height = consoleHeight;
54005 consoleTag.style.width = consoleWidth;
54007 CommandController.prototype.help = function () {
54010 .then(function (result) {
54011 console.log(result);
54014 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
54015 return CommandController;
54017 directives.CommandController = CommandController;
54018 })(directives = app.directives || (app.directives = {}));
54019 })(app || (app = {}));
54023 (function (directives) {
54024 var HeaderMenu = (function () {
54025 function HeaderMenu() {
54026 this.restrict = 'E';
54027 this.replace = true;
54028 this.templateUrl = 'templates/header-menu.html';
54029 this.controller = 'HeaderMenuController';
54030 this.controllerAs = 'hmc';
54033 HeaderMenu.Factory = function () {
54034 var directive = function () {
54035 return new HeaderMenu();
54041 directives.HeaderMenu = HeaderMenu;
54042 var HeaderMenuController = (function () {
54043 function HeaderMenuController($state) {
54044 this.$state = $state;
54045 this.isExecution = this.$state.current.name === 'execution';
54046 this.isWorkspace = this.$state.current.name === 'workspace';
54047 this.isHistory = this.$state.current.name === 'history';
54049 HeaderMenuController.prototype.transit = function (state) {
54050 this.$state.go(state);
54052 HeaderMenuController.$inject = ['$state'];
54053 return HeaderMenuController;
54055 directives.HeaderMenuController = HeaderMenuController;
54056 })(directives = app.directives || (app.directives = {}));
54057 })(app || (app = {}));
54061 (function (directives) {
54062 var Option = (function () {
54063 function Option() {
54064 this.restrict = 'E';
54065 this.replace = true;
54066 this.controller = 'optionController';
54067 this.bindToController = {
54072 this.templateUrl = 'templates/option.html';
54073 this.controllerAs = 'ctrl';
54075 Option.Factory = function () {
54076 var directive = function () {
54077 return new Option();
54079 directive.$inject = [];
54084 directives.Option = Option;
54085 var OptionController = (function () {
54086 function OptionController() {
54087 var controller = this;
54088 angular.forEach(controller.info.arg, function (arg) {
54089 if (arg.initialValue) {
54090 if (arg.formType === 'number') {
54091 arg.input = parseInt(arg.initialValue);
54094 arg.input = arg.initialValue;
54099 OptionController.$inject = [];
54100 return OptionController;
54102 directives.OptionController = OptionController;
54103 })(directives = app.directives || (app.directives = {}));
54104 })(app || (app = {}));
54108 (function (directives) {
54109 var Directory = (function () {
54110 function Directory() {
54111 this.restrict = 'E';
54112 this.replace = true;
54113 this.controller = 'directoryController';
54114 this.controllerAs = 'ctrl';
54115 this.bindToController = {
54121 this.templateUrl = 'templates/directory.html';
54123 Directory.Factory = function () {
54124 var directive = function () {
54125 return new Directory();
54131 directives.Directory = Directory;
54132 var DirectoryController = (function () {
54133 function DirectoryController(APIEndPoint, $scope) {
54134 this.APIEndPoint = APIEndPoint;
54135 this.$scope = $scope;
54136 var controller = this;
54138 .getFiles(this.info.fileId)
54140 .then(function (result) {
54141 if (result.status === 'success') {
54142 controller.files = result.info;
54143 angular.forEach(result.info, function (file) {
54144 if (file.fileType === '0') {
54146 if (controller.info.path === '/') {
54147 o.path = '/' + file.name;
54150 o.path = controller.info.path + '/' + file.name;
54152 controller.add()(o, controller.list);
54159 DirectoryController.$inject = ['APIEndPoint', '$scope'];
54160 return DirectoryController;
54162 directives.DirectoryController = DirectoryController;
54163 })(directives = app.directives || (app.directives = {}));
54164 })(app || (app = {}));
54168 (function (directives) {
54169 var Upload = (function () {
54170 function Upload() {
54171 this.restrict = 'E';
54172 this.replace = true;
54174 this.controller = 'UploadController';
54175 this.controllerAs = 'ctrl';
54176 this.bindToController = {
54182 this.templateUrl = 'templates/upload.html';
54183 console.log("templates/upload.html-constructor");
54185 Upload.Factory = function () {
54186 var directive = function () {
54187 return new Upload();
54189 directive.$inject = [];
54194 directives.Upload = Upload;
54195 var UploadController = (function () {
54196 function UploadController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
54197 this.APIEndPoint = APIEndPoint;
54198 this.$scope = $scope;
54199 this.MyModal = MyModal;
54200 this.WebSocket = WebSocket;
54201 this.$window = $window;
54202 this.$rootScope = $rootScope;
54203 this.Console = Console;
54204 var controller = this;
54205 console.log("directive.upload-constructor");
54207 UploadController.prototype.submit = function () {
54208 console.log("submit: function not supported¥n");
54210 UploadController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
54211 return UploadController;
54213 directives.UploadController = UploadController;
54214 })(directives = app.directives || (app.directives = {}));
54215 })(app || (app = {}));
54219 (function (controllers) {
54220 var Execution = (function () {
54221 function Execution(MyModal, $scope) {
54222 this.MyModal = MyModal;
54223 this.$scope = $scope;
54224 this.commandInfoList = [];
54227 Execution.prototype.add = function () {
54228 this.$scope.$broadcast('close');
54229 var commandInfoList = this.commandInfoList;
54230 var commandInstance = this.MyModal.selectCommand();
54233 .then(function (command) {
54234 commandInfoList.push(new app.declares.CommandInfo(command));
54237 Execution.prototype.open = function () {
54238 var result = this.MyModal.open('SelectCommand');
54239 console.log(result);
54241 Execution.prototype.remove = function (index, list) {
54242 list.splice(index, 1);
54244 Execution.prototype.close = function () {
54245 console.log("close");
54247 Execution.$inject = ['MyModal', '$scope'];
54250 controllers.Execution = Execution;
54251 })(controllers = app.controllers || (app.controllers = {}));
54252 })(app || (app = {}));
54256 (function (controllers) {
54257 var Workspace = (function () {
54258 function Workspace($scope, APIEndPoint, MyModal) {
54259 this.$scope = $scope;
54260 this.APIEndPoint = APIEndPoint;
54261 this.MyModal = MyModal;
54262 this.directoryList = [];
54263 var controller = this;
54264 var directoryList = this.directoryList;
54266 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
54274 directoryList.push(o);
54276 Workspace.prototype.addDirectory = function (info, directoryList) {
54277 directoryList.push(info);
54279 Workspace.prototype.upload = function () {
54280 this.MyModal.upload();
54282 Workspace.prototype.debug = function () {
54283 this.MyModal.preview();
54285 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
54288 controllers.Workspace = Workspace;
54289 })(controllers = app.controllers || (app.controllers = {}));
54290 })(app || (app = {}));
54294 (function (controllers) {
54295 var History = (function () {
54296 function History($scope) {
54297 this.page = "History";
54299 History.$inject = ['$scope'];
54302 controllers.History = History;
54303 })(controllers = app.controllers || (app.controllers = {}));
54304 })(app || (app = {}));
54308 (function (controllers) {
54309 var SelectCommand = (function () {
54310 function SelectCommand($scope, APIEndPoint, $modalInstance) {
54311 this.APIEndPoint = APIEndPoint;
54312 this.$modalInstance = $modalInstance;
54313 var controller = this;
54316 .$promise.then(function (result) {
54317 controller.tags = result.info;
54321 .$promise.then(function (result) {
54322 controller.commands = result.info;
54324 this.currentTag = 'all';
54326 SelectCommand.prototype.changeTag = function (tag) {
54327 this.currentTag = tag;
54329 SelectCommand.prototype.selectCommand = function (command) {
54330 this.$modalInstance.close(command);
54332 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
54333 return SelectCommand;
54335 controllers.SelectCommand = SelectCommand;
54336 })(controllers = app.controllers || (app.controllers = {}));
54337 })(app || (app = {}));
54341 (function (controllers) {
54342 var Upload = (function () {
54343 function Upload($scope, APIEndPoint, $modalInstance) {
54344 this.APIEndPoint = APIEndPoint;
54345 this.$modalInstance = $modalInstance;
54346 var controller = this;
54347 console.log('controller.upload-controllers');
54349 Upload.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
54352 controllers.Upload = Upload;
54353 })(controllers = app.controllers || (app.controllers = {}));
54354 })(app || (app = {}));
54358 (function (controllers) {
54359 var Preview = (function () {
54360 function Preview($scope, APIEndPoint, $modalInstance) {
54361 this.APIEndPoint = APIEndPoint;
54362 this.$modalInstance = $modalInstance;
54363 var controller = this;
54364 console.log('preview');
54366 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
54369 controllers.Preview = Preview;
54370 })(controllers = app.controllers || (app.controllers = {}));
54371 })(app || (app = {}));
54373 (function (filters) {
54375 return function (commands, tag) {
54377 angular.forEach(commands, function (command) {
54379 angular.forEach(command.tags, function (value) {
54384 result.push(command);
54390 })(filters || (filters = {}));
54394 var appName = 'zephyr';
54395 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
54396 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
54397 $urlRouterProvider.otherwise('/execution');
54398 $locationProvider.html5Mode({
54403 .state('execution', {
54405 templateUrl: 'templates/execution.html',
54406 controller: 'executionController',
54409 .state('workspace', {
54411 templateUrl: 'templates/workspace.html',
54412 controller: 'workspaceController',
54415 .state('history', {
54417 templateUrl: 'templates/history.html',
54418 controller: 'historyController',
54422 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
54423 app.zephyr.service('MyModal', app.services.MyModal);
54424 app.zephyr.service('WebSocket', app.services.WebSocket);
54425 app.zephyr.service('Console', app.services.Console);
54426 app.zephyr.filter('Tag', filters.Tag);
54427 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
54428 app.zephyr.controller('previewController', app.controllers.Preview);
54429 app.zephyr.controller('uploadController', app.controllers.Upload);
54430 app.zephyr.controller('executionController', app.controllers.Execution);
54431 app.zephyr.controller('workspaceController', app.controllers.Workspace);
54432 app.zephyr.controller('historyController', app.controllers.History);
54433 app.zephyr.controller('commandController', app.directives.CommandController);
54434 app.zephyr.controller('optionController', app.directives.OptionController);
54435 app.zephyr.controller('directoryController', app.directives.DirectoryController);
54436 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
54437 app.zephyr.controller('uploadController', app.directives.UploadController);
54438 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
54439 app.zephyr.directive('command', app.directives.Command.Factory());
54440 app.zephyr.directive('option', app.directives.Option.Factory());
54441 app.zephyr.directive('directory', app.directives.Directory.Factory());
54442 })(app || (app = {}));
54447 /***/ function(module, exports) {
54452 (function (declares) {
54453 var CommandInfo = (function () {
54454 function CommandInfo(name) {
54457 return CommandInfo;
54459 declares.CommandInfo = CommandInfo;
54460 })(declares = app.declares || (app.declares = {}));
54461 })(app || (app = {}));
54465 (function (services) {
54466 var APIEndPoint = (function () {
54467 function APIEndPoint($resource, $http) {
54468 this.$resource = $resource;
54469 this.$http = $http;
54471 APIEndPoint.prototype.resource = function (endPoint, data) {
54472 var customAction = {
54478 headers: { 'Content-Type': undefined, enctype: 'multipart/form-data' }
54480 return this.$resource(endPoint, {}, { execute: execute });
54482 APIEndPoint.prototype.getOptionControlFile = function (command) {
54483 var endPoint = '/api/v1/optionControlFile/' + command;
54484 return this.resource(endPoint, {}).get();
54486 APIEndPoint.prototype.getFiles = function (fileId) {
54487 var endPoint = '/api/v1/workspace';
54489 endPoint += '/' + fileId;
54491 return this.resource(endPoint, {}).get();
54493 APIEndPoint.prototype.getDirectories = function () {
54494 var endPoint = '/api/v1/all/workspace/directory';
54495 return this.resource(endPoint, {}).get();
54497 APIEndPoint.prototype.getTags = function () {
54498 var endPoint = '/api/v1/tagList';
54499 return this.resource(endPoint, {}).get();
54501 APIEndPoint.prototype.getCommands = function () {
54502 var endPoint = '/api/v1/commandList';
54503 return this.resource(endPoint, {}).get();
54505 APIEndPoint.prototype.execute = function (data) {
54506 var endPoint = '/api/v1/execution';
54507 var fd = new FormData();
54508 fd.append('data', data);
54509 return this.$http.post(endPoint, fd, {
54510 headers: { 'Content-Type': undefined },
54511 transformRequest: angular.identity
54514 APIEndPoint.prototype.debug = function () {
54515 var endPoint = '/api/v1/debug';
54516 return this.$http.get(endPoint);
54518 APIEndPoint.prototype.upload = function () {
54519 var endPoint = '/api/v1/upload';
54520 return this.$http.get(endPoint);
54522 APIEndPoint.prototype.help = function (command) {
54523 var endPoint = '/api/v1/help/' + command;
54524 return this.$http.get(endPoint);
54526 return APIEndPoint;
54528 services.APIEndPoint = APIEndPoint;
54529 })(services = app.services || (app.services = {}));
54530 })(app || (app = {}));
54534 (function (services) {
54535 var MyModal = (function () {
54536 function MyModal($uibModal) {
54537 this.$uibModal = $uibModal;
54538 this.modalOption = {
54545 MyModal.prototype.open = function (modalName) {
54546 if (modalName === 'SelectCommand') {
54547 this.modalOption.templateUrl = 'templates/select-command.html';
54548 this.modalOption.size = 'lg';
54550 return this.$uibModal.open(this.modalOption);
54552 MyModal.prototype.selectCommand = function () {
54553 this.modalOption.templateUrl = 'templates/select-command.html';
54554 this.modalOption.controller = 'selectCommandController';
54555 this.modalOption.controllerAs = 'c';
54556 this.modalOption.size = 'lg';
54557 return this.$uibModal.open(this.modalOption);
54559 MyModal.prototype.preview = function () {
54560 this.modalOption.templateUrl = 'templates/preview.html';
54561 this.modalOption.controller = 'previewController';
54562 this.modalOption.controllerAs = 'c';
54563 this.modalOption.size = 'lg';
54564 return this.$uibModal.open(this.modalOption);
54566 MyModal.prototype.upload = function () {
54567 this.modalOption.templateUrl = 'templates/upload.html';
54568 this.modalOption.controller = 'uploadController';
54569 this.modalOption.controllerAs = 'c';
54570 this.modalOption.size = 'lg';
54571 return this.$uibModal.open(this.modalOption);
54573 MyModal.$inject = ['$uibModal'];
54576 services.MyModal = MyModal;
54577 })(services = app.services || (app.services = {}));
54578 })(app || (app = {}));
54582 (function (services) {
54583 var WebSocket = (function () {
54584 function WebSocket($rootScope) {
54585 this.$rootScope = $rootScope;
54586 this.socket = io.connect();
54588 WebSocket.prototype.on = function (eventName, callback) {
54589 var socket = this.socket;
54590 var rootScope = this.$rootScope;
54591 socket.on(eventName, function () {
54592 var args = arguments;
54593 rootScope.$apply(function () {
54594 callback.apply(socket, args);
54598 WebSocket.prototype.emit = function (eventName, data, callback) {
54599 var socket = this.socket;
54600 var rootScope = this.$rootScope;
54601 this.socket.emit(eventName, data, function () {
54602 var args = arguments;
54603 rootScope.$apply(function () {
54605 callback.apply(socket, args);
54611 services.WebSocket = WebSocket;
54612 })(services = app.services || (app.services = {}));
54613 })(app || (app = {}));
54617 (function (services) {
54618 var Console = (function () {
54619 function Console(WebSocket, $rootScope) {
54620 this.WebSocket = WebSocket;
54621 this.$rootScope = $rootScope;
54622 this.WebSocket = WebSocket;
54623 this.$rootScope = $rootScope;
54624 this.directiveIDs = [];
54625 var directiveIDs = this.directiveIDs;
54626 this.WebSocket.on('console', function (d) {
54628 var message = d.message;
54629 if (directiveIDs.indexOf(id) > -1) {
54630 $rootScope.$emit(id, message);
54634 Console.prototype.addDirective = function (id) {
54635 if (!(this.directiveIDs.indexOf(id) > -1)) {
54636 this.directiveIDs.push(id);
54639 Console.prototype.removeDirective = function (id) {
54640 var i = this.directiveIDs.indexOf(id);
54642 this.directiveIDs.splice(i, 1);
54645 Console.prototype.showIDs = function () {
54646 console.log(this.directiveIDs);
54650 services.Console = Console;
54651 })(services = app.services || (app.services = {}));
54652 })(app || (app = {}));
54656 (function (directives) {
54657 var Command = (function () {
54658 function Command() {
54659 this.restrict = 'E';
54660 this.replace = true;
54662 this.controller = 'commandController';
54663 this.controllerAs = 'ctrl';
54664 this.bindToController = {
54670 this.templateUrl = 'templates/command.html';
54672 Command.Factory = function () {
54673 var directive = function () {
54674 return new Command();
54676 directive.$inject = [];
54681 directives.Command = Command;
54682 var CommandController = (function () {
54683 function CommandController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
54684 this.APIEndPoint = APIEndPoint;
54685 this.$scope = $scope;
54686 this.MyModal = MyModal;
54687 this.WebSocket = WebSocket;
54688 this.$window = $window;
54689 this.$rootScope = $rootScope;
54690 this.Console = Console;
54691 var controller = this;
54693 .getOptionControlFile(this.name)
54695 .then(function (result) {
54696 controller.options = result.info;
54701 .then(function (result) {
54702 controller.dirs = result.info;
54704 this.heading = "[" + this.index + "]: dcdFilePrint";
54705 this.isOpen = true;
54706 this.$scope.$on('close', function () {
54707 controller.isOpen = false;
54711 return Math.floor((1 + Math.random()) * 0x10000)
54715 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
54716 s4() + '-' + s4() + s4() + s4();
54718 this.uuid = guid();
54719 this.Console.addDirective(this.uuid);
54720 this.Console.showIDs();
54722 CommandController.prototype.submit = function () {
54724 angular.forEach(this.options, function (option) {
54726 name: option.option,
54729 angular.forEach(option.arg, function (arg) {
54731 if (typeof arg.input === 'object') {
54732 obj.arguments.push(arg.input.name);
54735 obj.arguments.push(arg.input);
54739 if (obj.arguments.length > 0) {
54744 command: this.name,
54745 workspace: this.workspace.fileId,
54749 .execute(JSON.stringify(execObj))
54750 .then(function (result) {
54751 console.log(result);
54754 CommandController.prototype.removeMySelf = function (index) {
54755 this.$scope.$destroy();
54756 this.Console.removeDirective(this.uuid);
54757 this.remove()(index, this.list);
54758 this.Console.showIDs();
54760 CommandController.prototype.reloadFiles = function () {
54762 var fileId = this.workspace.fileId;
54766 .then(function (result) {
54767 var status = result.status;
54768 if (status === 'success') {
54769 _this.files = result.info;
54772 console.log(result.message);
54776 CommandController.prototype.debug = function () {
54777 var div = angular.element(this.$window.document).find("div");
54780 angular.forEach(div, function (v) {
54781 if (v.className === "panel-body console") {
54784 else if (v.className === "row parameters-console") {
54788 var consoleHeight = parseInt(parametersTag.clientHeight.toString().replace('px', '')) - 150 + 'px';
54789 var consoleWidth = parseInt(parametersTag.clientWidth.toString().replace('px', '')) / 3 * 2 - 150 + 'px';
54790 consoleTag.style.height = consoleHeight;
54791 consoleTag.style.width = consoleWidth;
54793 CommandController.prototype.help = function () {
54796 .then(function (result) {
54797 console.log(result);
54800 CommandController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
54801 return CommandController;
54803 directives.CommandController = CommandController;
54804 })(directives = app.directives || (app.directives = {}));
54805 })(app || (app = {}));
54809 (function (directives) {
54810 var HeaderMenu = (function () {
54811 function HeaderMenu() {
54812 this.restrict = 'E';
54813 this.replace = true;
54814 this.templateUrl = 'templates/header-menu.html';
54815 this.controller = 'HeaderMenuController';
54816 this.controllerAs = 'hmc';
54819 HeaderMenu.Factory = function () {
54820 var directive = function () {
54821 return new HeaderMenu();
54827 directives.HeaderMenu = HeaderMenu;
54828 var HeaderMenuController = (function () {
54829 function HeaderMenuController($state) {
54830 this.$state = $state;
54831 this.isExecution = this.$state.current.name === 'execution';
54832 this.isWorkspace = this.$state.current.name === 'workspace';
54833 this.isHistory = this.$state.current.name === 'history';
54835 HeaderMenuController.prototype.transit = function (state) {
54836 this.$state.go(state);
54838 HeaderMenuController.$inject = ['$state'];
54839 return HeaderMenuController;
54841 directives.HeaderMenuController = HeaderMenuController;
54842 })(directives = app.directives || (app.directives = {}));
54843 })(app || (app = {}));
54847 (function (directives) {
54848 var Option = (function () {
54849 function Option() {
54850 this.restrict = 'E';
54851 this.replace = true;
54852 this.controller = 'optionController';
54853 this.bindToController = {
54858 this.templateUrl = 'templates/option.html';
54859 this.controllerAs = 'ctrl';
54861 Option.Factory = function () {
54862 var directive = function () {
54863 return new Option();
54865 directive.$inject = [];
54870 directives.Option = Option;
54871 var OptionController = (function () {
54872 function OptionController() {
54873 var controller = this;
54874 angular.forEach(controller.info.arg, function (arg) {
54875 if (arg.initialValue) {
54876 if (arg.formType === 'number') {
54877 arg.input = parseInt(arg.initialValue);
54880 arg.input = arg.initialValue;
54885 OptionController.$inject = [];
54886 return OptionController;
54888 directives.OptionController = OptionController;
54889 })(directives = app.directives || (app.directives = {}));
54890 })(app || (app = {}));
54894 (function (directives) {
54895 var Directory = (function () {
54896 function Directory() {
54897 this.restrict = 'E';
54898 this.replace = true;
54899 this.controller = 'directoryController';
54900 this.controllerAs = 'ctrl';
54901 this.bindToController = {
54907 this.templateUrl = 'templates/directory.html';
54909 Directory.Factory = function () {
54910 var directive = function () {
54911 return new Directory();
54917 directives.Directory = Directory;
54918 var DirectoryController = (function () {
54919 function DirectoryController(APIEndPoint, $scope) {
54920 this.APIEndPoint = APIEndPoint;
54921 this.$scope = $scope;
54922 var controller = this;
54924 .getFiles(this.info.fileId)
54926 .then(function (result) {
54927 if (result.status === 'success') {
54928 controller.files = result.info;
54929 angular.forEach(result.info, function (file) {
54930 if (file.fileType === '0') {
54932 if (controller.info.path === '/') {
54933 o.path = '/' + file.name;
54936 o.path = controller.info.path + '/' + file.name;
54938 controller.add()(o, controller.list);
54945 DirectoryController.$inject = ['APIEndPoint', '$scope'];
54946 return DirectoryController;
54948 directives.DirectoryController = DirectoryController;
54949 })(directives = app.directives || (app.directives = {}));
54950 })(app || (app = {}));
54954 (function (directives) {
54955 var Upload = (function () {
54956 function Upload() {
54957 this.restrict = 'E';
54958 this.replace = true;
54960 this.controller = 'UploadController';
54961 this.controllerAs = 'ctrl';
54962 this.bindToController = {
54968 this.templateUrl = 'templates/upload.html';
54969 console.log("templates/upload.html-constructor");
54971 Upload.Factory = function () {
54972 var directive = function () {
54973 return new Upload();
54975 directive.$inject = [];
54980 directives.Upload = Upload;
54981 var UploadController = (function () {
54982 function UploadController(APIEndPoint, $scope, MyModal, WebSocket, $window, $rootScope, Console) {
54983 this.APIEndPoint = APIEndPoint;
54984 this.$scope = $scope;
54985 this.MyModal = MyModal;
54986 this.WebSocket = WebSocket;
54987 this.$window = $window;
54988 this.$rootScope = $rootScope;
54989 this.Console = Console;
54990 var controller = this;
54991 console.log("directive.upload-constructor");
54993 UploadController.prototype.submit = function () {
54994 console.log("submit: function not supported¥n");
54996 UploadController.$inject = ['APIEndPoint', '$scope', 'MyModal', 'WebSocket', '$window', '$rootScope', 'Console'];
54997 return UploadController;
54999 directives.UploadController = UploadController;
55000 })(directives = app.directives || (app.directives = {}));
55001 })(app || (app = {}));
55005 (function (controllers) {
55006 var Execution = (function () {
55007 function Execution(MyModal, $scope) {
55008 this.MyModal = MyModal;
55009 this.$scope = $scope;
55010 this.commandInfoList = [];
55013 Execution.prototype.add = function () {
55014 this.$scope.$broadcast('close');
55015 var commandInfoList = this.commandInfoList;
55016 var commandInstance = this.MyModal.selectCommand();
55019 .then(function (command) {
55020 commandInfoList.push(new app.declares.CommandInfo(command));
55023 Execution.prototype.open = function () {
55024 var result = this.MyModal.open('SelectCommand');
55025 console.log(result);
55027 Execution.prototype.remove = function (index, list) {
55028 list.splice(index, 1);
55030 Execution.prototype.close = function () {
55031 console.log("close");
55033 Execution.$inject = ['MyModal', '$scope'];
55036 controllers.Execution = Execution;
55037 })(controllers = app.controllers || (app.controllers = {}));
55038 })(app || (app = {}));
55042 (function (controllers) {
55043 var Workspace = (function () {
55044 function Workspace($scope, APIEndPoint, MyModal) {
55045 this.$scope = $scope;
55046 this.APIEndPoint = APIEndPoint;
55047 this.MyModal = MyModal;
55048 this.directoryList = [];
55049 var controller = this;
55050 var directoryList = this.directoryList;
55052 fileId: '1f83f620-c1ed-11e5-9657-7942989daa00',
55060 directoryList.push(o);
55062 Workspace.prototype.addDirectory = function (info, directoryList) {
55063 directoryList.push(info);
55065 Workspace.prototype.upload = function () {
55066 this.MyModal.upload();
55068 Workspace.prototype.debug = function () {
55069 this.MyModal.preview();
55071 Workspace.$inject = ['$scope', 'APIEndPoint', 'MyModal'];
55074 controllers.Workspace = Workspace;
55075 })(controllers = app.controllers || (app.controllers = {}));
55076 })(app || (app = {}));
55080 (function (controllers) {
55081 var History = (function () {
55082 function History($scope) {
55083 this.page = "History";
55085 History.$inject = ['$scope'];
55088 controllers.History = History;
55089 })(controllers = app.controllers || (app.controllers = {}));
55090 })(app || (app = {}));
55094 (function (controllers) {
55095 var SelectCommand = (function () {
55096 function SelectCommand($scope, APIEndPoint, $modalInstance) {
55097 this.APIEndPoint = APIEndPoint;
55098 this.$modalInstance = $modalInstance;
55099 var controller = this;
55102 .$promise.then(function (result) {
55103 controller.tags = result.info;
55107 .$promise.then(function (result) {
55108 controller.commands = result.info;
55110 this.currentTag = 'all';
55112 SelectCommand.prototype.changeTag = function (tag) {
55113 this.currentTag = tag;
55115 SelectCommand.prototype.selectCommand = function (command) {
55116 this.$modalInstance.close(command);
55118 SelectCommand.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
55119 return SelectCommand;
55121 controllers.SelectCommand = SelectCommand;
55122 })(controllers = app.controllers || (app.controllers = {}));
55123 })(app || (app = {}));
55127 (function (controllers) {
55128 var Upload = (function () {
55129 function Upload($scope, APIEndPoint, $modalInstance) {
55130 this.APIEndPoint = APIEndPoint;
55131 this.$modalInstance = $modalInstance;
55132 var controller = this;
55133 console.log('controller.upload-controllers');
55135 Upload.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
55138 controllers.Upload = Upload;
55139 })(controllers = app.controllers || (app.controllers = {}));
55140 })(app || (app = {}));
55144 (function (controllers) {
55145 var Preview = (function () {
55146 function Preview($scope, APIEndPoint, $modalInstance) {
55147 this.APIEndPoint = APIEndPoint;
55148 this.$modalInstance = $modalInstance;
55149 var controller = this;
55150 console.log('preview');
55152 Preview.$inject = ['$scope', 'APIEndPoint', '$uibModalInstance'];
55155 controllers.Preview = Preview;
55156 })(controllers = app.controllers || (app.controllers = {}));
55157 })(app || (app = {}));
55159 (function (filters) {
55161 return function (commands, tag) {
55163 angular.forEach(commands, function (command) {
55165 angular.forEach(command.tags, function (value) {
55170 result.push(command);
55176 })(filters || (filters = {}));
55180 var appName = 'zephyr';
55181 app.zephyr = angular.module(appName, ['ui.router', 'ngResource', 'ui.bootstrap']);
55182 app.zephyr.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
55183 $urlRouterProvider.otherwise('/execution');
55184 $locationProvider.html5Mode({
55189 .state('execution', {
55191 templateUrl: 'templates/execution.html',
55192 controller: 'executionController',
55195 .state('workspace', {
55197 templateUrl: 'templates/workspace.html',
55198 controller: 'workspaceController',
55201 .state('history', {
55203 templateUrl: 'templates/history.html',
55204 controller: 'historyController',
55208 app.zephyr.service('APIEndPoint', app.services.APIEndPoint);
55209 app.zephyr.service('MyModal', app.services.MyModal);
55210 app.zephyr.service('WebSocket', app.services.WebSocket);
55211 app.zephyr.service('Console', app.services.Console);
55212 app.zephyr.filter('Tag', filters.Tag);
55213 app.zephyr.controller('selectCommandController', app.controllers.SelectCommand);
55214 app.zephyr.controller('previewController', app.controllers.Preview);
55215 app.zephyr.controller('uploadController', app.controllers.Upload);
55216 app.zephyr.controller('executionController', app.controllers.Execution);
55217 app.zephyr.controller('workspaceController', app.controllers.Workspace);
55218 app.zephyr.controller('historyController', app.controllers.History);
55219 app.zephyr.controller('commandController', app.directives.CommandController);
55220 app.zephyr.controller('optionController', app.directives.OptionController);
55221 app.zephyr.controller('directoryController', app.directives.DirectoryController);
55222 app.zephyr.controller('HeaderMenuController', app.directives.HeaderMenuController);
55223 app.zephyr.controller('uploadController', app.directives.UploadController);
55224 app.zephyr.directive('headerMenu', app.directives.HeaderMenu.Factory());
55225 app.zephyr.directive('command', app.directives.Command.Factory());
55226 app.zephyr.directive('option', app.directives.Option.Factory());
55227 app.zephyr.directive('directory', app.directives.Directory.Factory());
55228 })(app || (app = {}));